Polish websocket code

This commit is contained in:
Adrian Gallagher
2017-09-04 16:24:02 +10:00
parent 69aa445a3a
commit 2bd27feaf0
22 changed files with 916 additions and 793 deletions

View File

@@ -413,6 +413,22 @@ func (c *Config) LoadConfig(configPath string) error {
return fmt.Errorf(ErrCheckingConfigValues, err)
}
if c.SMS.Enabled {
err = c.CheckSMSGlobalConfigValues()
if err != nil {
log.Print(fmt.Errorf(ErrCheckingConfigValues, err))
c.SMS.Enabled = false
}
}
if c.Webserver.Enabled {
err = c.CheckWebserverConfigValues()
if err != nil {
log.Print(fmt.Errorf(ErrCheckingConfigValues, err))
c.Webserver.Enabled = false
}
}
if c.CurrencyPairFormat == nil {
c.CurrencyPairFormat = &CurrencyPairFormatConfig{
Delimiter: "-",
@@ -423,6 +439,43 @@ func (c *Config) LoadConfig(configPath string) error {
return nil
}
// UpdateConfig updates the config with a supplied config file
func (c *Config) UpdateConfig(configPath string, newCfg Config) error {
if c.Name != newCfg.Name && newCfg.Name != "" {
c.Name = newCfg.Name
}
err := newCfg.CheckExchangeConfigValues()
if err != nil {
return err
}
c.Exchanges = newCfg.Exchanges
if c.CurrencyPairFormat != newCfg.CurrencyPairFormat {
c.CurrencyPairFormat = newCfg.CurrencyPairFormat
}
c.Portfolio = newCfg.Portfolio
err = newCfg.CheckSMSGlobalConfigValues()
if err != nil {
return err
}
c.SMS = newCfg.SMS
err = c.SaveConfig(configPath)
if err != nil {
return err
}
err = c.LoadConfig(configPath)
if err != nil {
return err
}
return nil
}
// GetConfig returns a pointer to a confiuration object
func GetConfig() *Config {
return &Cfg

View File

@@ -1,74 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"github.com/thrasher-/gocryptotrader/config"
)
// GetAllSettings replies to a request with an encoded JSON response about the
// trading bots configuration.
func GetAllSettings(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(bot.config); err != nil {
panic(err)
}
}
// SaveAllSettings saves all current settings from request body as a JSON
// document then reloads state and returns the settings
func SaveAllSettings(w http.ResponseWriter, r *http.Request) {
//Get the data from the request
decoder := json.NewDecoder(r.Body)
var responseData config.Post
jsonerr := decoder.Decode(&responseData)
if jsonerr != nil {
panic(jsonerr)
}
//Save change the settings
for x := range bot.config.Exchanges {
for i := 0; i < len(responseData.Data.Exchanges); i++ {
if responseData.Data.Exchanges[i].Name == bot.config.Exchanges[x].Name {
bot.config.Exchanges[x].Enabled = responseData.Data.Exchanges[i].Enabled
bot.config.Exchanges[x].APIKey = responseData.Data.Exchanges[i].APIKey
bot.config.Exchanges[x].APISecret = responseData.Data.Exchanges[i].APISecret
bot.config.Exchanges[x].EnabledPairs = responseData.Data.Exchanges[i].EnabledPairs
}
}
}
//Reload the configuration
err := bot.config.SaveConfig(bot.configFile)
if err != nil {
panic(err)
}
err = bot.config.LoadConfig(bot.configFile)
if err != nil {
panic(err)
}
setupBotExchanges()
//Return response status
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(bot.config); err != nil {
panic(err)
}
}
// ConfigRoutes declares the current routes for config_routes.go
var ConfigRoutes = Routes{
Route{
"GetAllSettings",
"GET",
"/config/all",
GetAllSettings,
},
Route{
"SaveAllSettings",
"POST",
"/config/all/save",
SaveAllSettings,
},
}

View File

@@ -68,7 +68,7 @@ var (
ErrCurrencyNotFound = errors.New("unable to find specified currency")
ErrQueryingYahoo = errors.New("unable to query Yahoo currency values")
ErrQueryingYahooZeroCount = errors.New("yahoo returned zero currency data")
yahooEnabled = false
YahooEnabled = false
)
// IsDefaultCurrency checks if the currency passed in matches the default
@@ -182,7 +182,7 @@ func SeedCurrencyData(fiatCurrencies string) error {
fiatCurrencies = DefaultCurrencies
}
if yahooEnabled {
if YahooEnabled {
return QueryYahooCurrencyValues(fiatCurrencies)
}
@@ -215,7 +215,7 @@ func ConvertCurrency(amount float64, from, to string) (float64, error) {
return amount, nil
}
if yahooEnabled {
if YahooEnabled {
currency := from + to
_, ok := CurrencyStore[currency]
if !ok {

View File

@@ -264,7 +264,7 @@ func TestCheckAndAddCurrency(t *testing.T) {
}
func TestSeedCurrencyData(t *testing.T) {
if yahooEnabled {
if YahooEnabled {
currencyRequestDefault := ""
currencyRequestUSDAUD := "USD,AUD"
currencyRequestObtuse := "WigWham"
@@ -292,7 +292,7 @@ func TestSeedCurrencyData(t *testing.T) {
}
}
yahooEnabled = false
YahooEnabled = false
err := SeedCurrencyData("")
if err != nil {
t.Errorf("Test failed. SeedCurrencyData via Fixer. Error: %s", err)
@@ -313,7 +313,7 @@ func TestMakecurrencyPairs(t *testing.T) {
}
func TestConvertCurrency(t *testing.T) {
if yahooEnabled {
if YahooEnabled {
fiatCurrencies := DefaultCurrencies
for _, currencyFrom := range common.SplitStrings(fiatCurrencies, ",") {
for _, currencyTo := range common.SplitStrings(fiatCurrencies, ",") {
@@ -336,7 +336,7 @@ func TestConvertCurrency(t *testing.T) {
}
}
yahooEnabled = false
YahooEnabled = false
_, err := ConvertCurrency(1000, "USD", "AUD")
if err != nil {
t.Errorf("Test failed. ConvertCurrency USD -> AUD. Error %s", err)
@@ -383,7 +383,7 @@ func TestFetchFixerCurrencyData(t *testing.T) {
}
func TestFetchYahooCurrencyData(t *testing.T) {
if !yahooEnabled {
if !YahooEnabled {
return
}
@@ -407,7 +407,7 @@ func TestFetchYahooCurrencyData(t *testing.T) {
}
func TestQueryYahooCurrencyValues(t *testing.T) {
if !yahooEnabled {
if !YahooEnabled {
return
}

View File

@@ -2,40 +2,42 @@ package events
import (
"testing"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
func TestAddEvent(t *testing.T) {
eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
pair := pair.NewCurrencyPair("BTC", "USD")
eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil && eventID != 0 {
t.Errorf("Test Failed. AddEvent: Error, %s", err)
}
eventID, err = AddEvent("ANXX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
eventID, err = AddEvent("ANXX", "price", ">,==", pair, "SPOT", actionTest)
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Exchange")
}
eventID, err = AddEvent("ANX", "prices", ">,==", "BTC", "LTC", "SPOT", actionTest)
eventID, err = AddEvent("ANX", "prices", ">,==", pair, "SPOT", actionTest)
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Item")
}
eventID, err = AddEvent("ANX", "price", "3===D", "BTC", "LTC", "SPOT", actionTest)
eventID, err = AddEvent("ANX", "price", "3===D", pair, "SPOT", actionTest)
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Condition")
}
eventID, err = AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", "console_prints")
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Action")
}
eventID, err = AddEvent("ANX", "price", ">,==", "BATMAN", "ROBIN", "SPOT", actionTest)
eventID, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", "console_prints")
if err == nil && eventID == 0 {
t.Error("Test Failed. AddEvent: Error, error not captured in Action")
}
if !RemoveEvent(eventID) {
t.Error("Test Failed. RemoveEvent: Error, error removing event")
}
}
func TestRemoveEvent(t *testing.T) {
eventID, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
pair := pair.NewCurrencyPair("BTC", "USD")
eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil && eventID != 0 {
t.Errorf("Test Failed. RemoveEvent: Error, %s", err)
}
@@ -48,15 +50,16 @@ func TestRemoveEvent(t *testing.T) {
}
func TestGetEventCounter(t *testing.T) {
one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
pair := pair.NewCurrencyPair("BTC", "USD")
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
}
two, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
two, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
}
three, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
three, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
}
@@ -84,9 +87,10 @@ func TestGetEventCounter(t *testing.T) {
}
func TestExecuteAction(t *testing.T) {
one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
pair := pair.NewCurrencyPair("BTC", "USD")
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. ExecuteAction: Error, %s", err)
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
}
isExecuted := Events[one].ExecuteAction()
if !isExecuted {
@@ -96,17 +100,46 @@ func TestExecuteAction(t *testing.T) {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
action := actionSMSNotify + "," + "ALL"
one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action)
if err != nil {
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
}
isExecuted = Events[one].ExecuteAction()
if !isExecuted {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
if !RemoveEvent(one) {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
action = actionSMSNotify + "," + "StyleGherkin"
one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action)
if err != nil {
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
}
isExecuted = Events[one].ExecuteAction()
if !isExecuted {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
if !RemoveEvent(one) {
t.Error("Test Failed. ExecuteAction: Error, error removing event")
}
// More tests when ExecuteAction is expanded
}
func TestEventToString(t *testing.T) {
one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
pair := pair.NewCurrencyPair("BTC", "USD")
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. EventToString: Error, %s", err)
}
eventString := Events[one].EventToString()
if eventString != "If the BTCLTC [SPOT] price on ANX is > == then ACTION_TEST." {
eventString := Events[one].String()
if eventString != "If the BTCUSD [SPOT] price on ANX is > == then ACTION_TEST." {
t.Error("Test Failed. EventToString: Error, incorrect return string")
}
@@ -115,64 +148,118 @@ func TestEventToString(t *testing.T) {
}
}
func TestCheckCondition(t *testing.T) { //error handling needs to be implemented
one, err := AddEvent("ANX", "price", ">,==", "BTC", "LTC", "SPOT", actionTest)
func TestCheckCondition(t *testing.T) {
// Test invalid currency pair
newPair := pair.NewCurrencyPair("A", "B")
one, err := AddEvent("ANX", "price", ">=,10", newPair, "SPOT", actionTest)
if err != nil {
t.Errorf("Test Failed. EventToString: Error, %s", err)
t.Errorf("Test Failed. CheckCondition: Error, %s", err)
}
conditionBool := Events[one].CheckCondition()
if conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
conditionBool := Events[one].CheckCondition()
if conditionBool { //check once error handling is implemented
t.Error("Test Failed. EventToString: Error, wrong conditional.")
// Test last price == 0
var tickerNew ticker.Price
tickerNew.Last = 0
newPair = pair.NewCurrencyPair("BTC", "USD")
ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot)
Events[one].Pair = newPair
conditionBool = Events[one].CheckCondition()
if conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
// Test last pricce > 0 and conditional logic
tickerNew.Last = 11
ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot)
Events[one].Condition = ">,10"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
// Test last price >= 10
Events[one].Condition = ">=,10"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
// Test last price <= 10
Events[one].Condition = "<,100"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
// Test last price <= 10
Events[one].Condition = "<=,100"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
Events[one].Condition = "==,11"
conditionBool = Events[one].CheckCondition()
if !conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
Events[one].Condition = "^,11"
conditionBool = Events[one].CheckCondition()
if conditionBool {
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
}
if !RemoveEvent(one) {
t.Error("Test Failed. EventToString: Error, error removing event")
t.Error("Test Failed. CheckCondition: Error, error removing event")
}
}
func TestIsValidEvent(t *testing.T) {
err := IsValidEvent("ANX", "price", ">,==", actionTest)
if err != nil {
t.Errorf("Test Failed. IsValidExchange: Error %s", err)
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
err = IsValidEvent("ANX", "price", ">,", actionTest)
if err == nil {
t.Errorf("Test Failed. IsValidExchange: Error")
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
err = IsValidEvent("ANX", "Testy", ">,==", actionTest)
if err == nil {
t.Errorf("Test Failed. IsValidExchange: Error")
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
err = IsValidEvent("Testys", "price", ">,==", actionTest)
if err == nil {
t.Errorf("Test Failed. IsValidExchange: Error")
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
action := "blah,blah"
err = IsValidEvent("ANX", "price", ">=,10", action)
if err == nil {
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
action = "SMS,blah"
err = IsValidEvent("ANX", "price", ">=,10", action)
if err == nil {
t.Errorf("Test Failed. IsValidEvent: %s", err)
}
//Function tests need to appended to this function when more actions are
//implemented
}
func TestCheckEvents(t *testing.T) { //Add error handling
//CheckEvents() //check once error handling is implemented
}
func TestCheckEvents(t *testing.T) {
pair := pair.NewCurrencyPair("BTC", "USD")
_, err := AddEvent("ANX", "price", ">=,10", pair, "SPOT", actionTest)
if err != nil {
t.Fatal("Test failed. TestChcheckEvents add event")
}
func TestIsValidCurrency(t *testing.T) {
if !IsValidCurrency("BTC") {
t.Error("Test Failed - Event_test.go TestIsValidCurrency Error")
}
if !IsValidCurrency("USD") {
t.Error("Test Failed - Event_test.go TestIsValidCurrency Error")
}
if IsValidCurrency("testy") {
t.Error("Test Failed - Event_test.go TestIsValidCurrency Error")
}
if !IsValidCurrency("USD", "BTC", "USD") {
t.Error("Test Failed - Event_test.go TestIsValidCurrency Error")
}
if IsValidCurrency("USD", "USD", "Wigwham") {
t.Error("Test Failed - Event_test.go TestIsValidCurrency Error")
}
go CheckEvents()
}
func TestIsValidExchange(t *testing.T) {

View File

@@ -8,7 +8,6 @@ import (
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/smsglobal"
@@ -32,20 +31,18 @@ var (
errInvalidCondition = errors.New("invalid conditional option")
errInvalidAction = errors.New("invalid action")
errExchangeDisabled = errors.New("desired exchange is disabled")
errCurrencyInvalid = errors.New("invalid currency")
)
// Event struct holds the event variables
type Event struct {
ID int
Exchange string
Item string
Condition string
FirstCurrency string
Asset string
SecondCurrency string
Action string
Executed bool
ID int
Exchange string
Item string
Condition string
Pair pair.CurrencyPair
Asset string
Action string
Executed bool
}
// Events variable is a pointer array to the event structures that will be
@@ -54,16 +51,12 @@ var Events []*Event
// AddEvent adds an event to the Events chain and returns an index/eventID
// and an error
func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Asset, Action string) (int, error) {
func AddEvent(Exchange, Item, Condition string, CurrencyPair pair.CurrencyPair, Asset, Action string) (int, error) {
err := IsValidEvent(Exchange, Item, Condition, Action)
if err != nil {
return 0, err
}
if !IsValidCurrency(FirstCurrency, SecondCurrency) {
return 0, errCurrencyInvalid
}
Event := &Event{}
if len(Events) == 0 {
@@ -75,8 +68,7 @@ func AddEvent(Exchange, Item, Condition, FirstCurrency, SecondCurrency, Asset, A
Event.Exchange = Exchange
Event.Item = Item
Event.Condition = Condition
Event.FirstCurrency = FirstCurrency
Event.SecondCurrency = SecondCurrency
Event.Pair = CurrencyPair
Event.Asset = Asset
Event.Action = Action
Event.Executed = false
@@ -114,7 +106,7 @@ func (e *Event) ExecuteAction() bool {
if common.StringContains(e.Action, ",") {
action := common.SplitStrings(e.Action, ",")
if action[0] == actionSMSNotify {
message := fmt.Sprintf("Event triggered: %s", e.EventToString())
message := fmt.Sprintf("Event triggered: %s", e.String())
if action[1] == "ALL" {
smsglobal.SMSSendToAll(message, config.Cfg)
} else {
@@ -124,17 +116,17 @@ func (e *Event) ExecuteAction() bool {
}
}
} else {
log.Printf("Event triggered: %s", e.EventToString())
log.Printf("Event triggered: %s", e.String())
}
return true
}
// EventToString turns the structure event into a string
func (e *Event) EventToString() string {
func (e *Event) String() string {
condition := common.SplitStrings(e.Condition, ",")
return fmt.Sprintf(
"If the %s%s [%s] %s on %s is %s then %s.", e.FirstCurrency, e.SecondCurrency,
e.Asset, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action,
"If the %s%s [%s] %s on %s is %s then %s.", e.Pair.FirstCurrency.String(),
e.Pair.SecondCurrency.String(), e.Asset, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action,
)
}
@@ -144,12 +136,12 @@ func (e *Event) CheckCondition() bool {
condition := common.SplitStrings(e.Condition, ",")
targetPrice, _ := strconv.ParseFloat(condition[1], 64)
ticker, err := ticker.GetTickerByExchange(e.Exchange)
t, err := ticker.GetTicker(e.Exchange, e.Pair, e.Asset)
if err != nil {
return false
}
lastPrice := ticker.Price[pair.CurrencyItem(e.FirstCurrency)][pair.CurrencyItem(e.SecondCurrency)][e.Asset].Last
lastPrice := t.Last
if lastPrice == 0 {
return false
@@ -226,8 +218,9 @@ func IsValidEvent(Exchange, Item, Condition, Action string) error {
return errInvalidAction
}
cfg := config.GetConfig()
if action[1] != "ALL" && smsglobal.SMSGetNumberByName(
action[1], config.Cfg.SMS) == smsglobal.ErrSMSContactNotFound {
action[1], cfg.SMS) == smsglobal.ErrSMSContactNotFound {
return errInvalidAction
}
} else {
@@ -260,20 +253,6 @@ func CheckEvents() {
}
}
// IsValidCurrency takes in CRYPTO or FIAT currency strings and returns if valid
func IsValidCurrency(currencies ...string) bool {
for index, whatIsIt := range currencies {
whatIsIt = common.StringToUpper(whatIsIt)
if currency.IsDefaultCryptocurrency(whatIsIt) || currency.IsDefaultCurrency(whatIsIt) {
if len(currencies)-1 == index {
return true
}
continue
}
}
return false
}
// IsValidExchange validates the exchange
func IsValidExchange(Exchange, configPath string) bool {
Exchange = common.StringToUpper(Exchange)

108
helpers.go Normal file
View File

@@ -0,0 +1,108 @@
package main
import (
"errors"
"fmt"
"github.com/thrasher-/gocryptotrader/exchanges/stats"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// GetSpecificOrderbook returns a specific orderbook given the currency,
// exchangeName and assetType
func GetSpecificOrderbook(currency, exchangeName, assetType string) (orderbook.Base, error) {
var specificOrderbook orderbook.Base
var err error
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName {
specificOrderbook, err = bot.exchanges[i].GetOrderbookEx(
pair.NewCurrencyPairFromString(currency),
assetType,
)
break
}
}
}
return specificOrderbook, err
}
// GetSpecificTicker returns a specific ticker given the currency,
// exchangeName and assetType
func GetSpecificTicker(currency, exchangeName, assetType string) (ticker.Price, error) {
var specificTicker ticker.Price
var err error
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName {
specificTicker, err = bot.exchanges[i].GetTickerPrice(
pair.NewCurrencyPairFromString(currency),
assetType,
)
break
}
}
}
return specificTicker, err
}
// GetCollatedExchangeAccountInfoByCoin collates individual exchange account
// information and turns into into a map string of
// exchange.AccountCurrencyInfo
func GetCollatedExchangeAccountInfoByCoin(accounts []exchange.AccountInfo) map[string]exchange.AccountCurrencyInfo {
result := make(map[string]exchange.AccountCurrencyInfo)
for i := 0; i < len(accounts); i++ {
for j := 0; j < len(accounts[i].Currencies); j++ {
currencyName := accounts[i].Currencies[j].CurrencyName
avail := accounts[i].Currencies[j].TotalValue
onHold := accounts[i].Currencies[j].Hold
info, ok := result[currencyName]
if !ok {
accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail}
result[currencyName] = accountInfo
} else {
info.Hold += onHold
info.TotalValue += avail
result[currencyName] = info
}
}
}
return result
}
// GetAccountCurrencyInfoByExchangeName returns info for an exchange
func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, exchangeName string) (exchange.AccountInfo, error) {
for i := 0; i < len(accounts); i++ {
if accounts[i].ExchangeName == exchangeName {
return accounts[i], nil
}
}
return exchange.AccountInfo{}, errors.New(exchange.ErrExchangeNotFound)
}
// GetExchangeHighestPriceByCurrencyPair returns the exchange with the highest
// price for a given currency pair and asset type
func GetExchangeHighestPriceByCurrencyPair(p pair.CurrencyPair, assetType string) (string, error) {
result := stats.SortExchangesByPrice(p, assetType, true)
if len(result) != 1 {
return "", fmt.Errorf("no stats for supplied currency pair and asset type")
}
return result[0].Exchange, nil
}
// GetExchangeLowestPriceByCurrencyPair returns the exchange with the lowest
// price for a given currency pair and asset type
func GetExchangeLowestPriceByCurrencyPair(p pair.CurrencyPair, assetType string) (string, error) {
result := stats.SortExchangesByPrice(p, assetType, false)
if len(result) != 1 {
return "", fmt.Errorf("no stats for supplied currency pair and asset type")
}
return result[0].Exchange, nil
}

38
main.go
View File

@@ -119,16 +119,10 @@ func main() {
AdjustGoMaxProcs()
if bot.config.SMS.Enabled {
err = bot.config.CheckSMSGlobalConfigValues()
if err != nil {
log.Println(err) // non fatal event
bot.config.SMS.Enabled = false
} else {
log.Printf(
"SMS support enabled. Number of SMS contacts %d.\n",
smsglobal.GetEnabledSMSContacts(bot.config.SMS),
)
}
log.Printf(
"SMS support enabled. Number of SMS contacts %d.\n",
smsglobal.GetEnabledSMSContacts(bot.config.SMS),
)
} else {
log.Println("SMS support disabled.")
}
@@ -174,7 +168,6 @@ func main() {
setupBotExchanges()
bot.config.RetrieveConfigCurrencyPairs()
err = currency.SeedCurrencyData(currency.BaseCurrencies)
if err != nil {
log.Fatalf("Fatal error retrieving config currencies. Error: %s", err)
@@ -194,21 +187,14 @@ func main() {
go OrderbookUpdaterRoutine()
if bot.config.Webserver.Enabled {
err := bot.config.CheckWebserverConfigValues()
if err != nil {
log.Println(err) // non fatal event
//bot.config.Webserver.Enabled = false
} else {
listenAddr := bot.config.Webserver.ListenAddress
log.Printf(
"HTTP RESTful Webserver support enabled. Listen URL: http://%s:%d/\n",
common.ExtractHost(listenAddr), common.ExtractPort(listenAddr),
)
router := NewRouter(bot.exchanges)
log.Fatal(http.ListenAndServe(listenAddr, router))
}
}
if !bot.config.Webserver.Enabled {
listenAddr := bot.config.Webserver.ListenAddress
log.Printf(
"HTTP Webserver support enabled. Listen URL: http://%s:%d/\n",
common.ExtractHost(listenAddr), common.ExtractPort(listenAddr),
)
router := NewRouter(bot.exchanges)
log.Fatal(http.ListenAndServe(listenAddr, router))
} else {
log.Println("HTTP RESTful Webserver support disabled.")
}

View File

@@ -1,141 +0,0 @@
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
)
// GetSpecificOrderbook returns a specific orderbook given the currency,
// exchangeName and assetType
func GetSpecificOrderbook(currency, exchangeName, assetType string) (orderbook.Base, error) {
var specificOrderbook orderbook.Base
var err error
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName {
specificOrderbook, err = bot.exchanges[i].GetOrderbookEx(
pair.NewCurrencyPairFromString(currency),
assetType,
)
break
}
}
}
return specificOrderbook, err
}
func jsonOrderbookResponse(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
currency := vars["currency"]
exchange := vars["exchangeName"]
assetType := vars["assetType"]
if assetType == "" {
assetType = orderbook.Spot
}
response, err := GetSpecificOrderbook(currency, exchange, assetType)
if err != nil {
log.Printf("Failed to fetch orderbook for %s currency: %s\n", exchange,
currency)
return
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
panic(err)
}
}
// AllEnabledExchangeOrderbooks holds the enabled exchange orderbooks
type AllEnabledExchangeOrderbooks struct {
Data []EnabledExchangeOrderbooks `json:"data"`
}
// EnabledExchangeOrderbooks is a sub type for singular exchanges and respective
// orderbooks
type EnabledExchangeOrderbooks struct {
ExchangeName string `json:"exchangeName"`
ExchangeValues []orderbook.Base `json:"exchangeValues"`
}
// GetAllActiveOrderbooks returns all enabled exchanges orderbooks
func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks {
var orderbookData []EnabledExchangeOrderbooks
for _, individualBot := range bot.exchanges {
if individualBot != nil && individualBot.IsEnabled() {
var individualExchange EnabledExchangeOrderbooks
exchangeName := individualBot.GetName()
individualExchange.ExchangeName = exchangeName
currencies := individualBot.GetEnabledCurrencies()
assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName)
if err != nil {
log.Printf("failed to get %s exchange asset types. Error: %s",
exchangeName, err)
continue
}
for _, x := range currencies {
currency := x
var ob orderbook.Base
if len(assetTypes) > 1 {
for y := range assetTypes {
ob, err = individualBot.UpdateOrderbook(currency,
assetTypes[y])
}
} else {
ob, err = individualBot.UpdateOrderbook(currency,
assetTypes[0])
}
if err != nil {
log.Printf("failed to get %s %s orderbook. Error: %s",
currency.Pair().String(),
exchangeName,
err)
continue
}
individualExchange.ExchangeValues = append(
individualExchange.ExchangeValues, ob,
)
}
orderbookData = append(orderbookData, individualExchange)
}
}
return orderbookData
}
func getAllActiveOrderbooksResponse(w http.ResponseWriter, r *http.Request) {
var response AllEnabledExchangeOrderbooks
response.Data = GetAllActiveOrderbooks()
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
panic(err)
}
}
// OrderbookRoutes denotes the current exchange orderbook routes
var OrderbookRoutes = Routes{
Route{
"AllActiveExchangesAndOrderbooks",
"GET",
"/exchanges/orderbook/latest/all",
getAllActiveOrderbooksResponse,
},
Route{
"IndividualExchangeOrderbook",
"GET",
"/exchanges/{exchangeName}/orderbook/latest/{currency}",
jsonOrderbookResponse,
},
}

View File

@@ -1,27 +0,0 @@
package main
import (
"encoding/json"
"net/http"
)
// RESTGetPortfolio replies to a request with an encoded JSON response of the
// portfolio
func RESTGetPortfolio(w http.ResponseWriter, r *http.Request) {
result := bot.portfolio.GetPortfolioSummary()
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(result); err != nil {
panic(err)
}
}
// PortfolioRoutes declares the current routes for config_routes.go
var PortfolioRoutes = Routes{
Route{
"GetPortfolio",
"GET",
"/portfolio/all",
RESTGetPortfolio,
},
}

View File

@@ -1,24 +0,0 @@
package main
import (
"log"
"net/http"
"time"
)
// Logger logs the requests internally
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
log.Printf(
"%s\t%s\t%s\t%s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
})
}

View File

@@ -2,27 +2,104 @@ package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/thrasher-/gocryptotrader/exchanges"
)
// RESTLogger logs the requests internally
func RESTLogger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
inner.ServeHTTP(w, r)
log.Printf(
"%s\t%s\t%s\t%s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
})
}
// Route is a sub type that holds the request routes
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
// Routes is an array of all the registered routes
type Routes []Route
var routes = Routes{}
// NewRouter takes in the exchange interfaces and returns a new multiplexor
// router
func NewRouter(exchanges []exchange.IBotExchange) *mux.Router {
router := mux.NewRouter().StrictSlash(true)
allRoutes := append(routes, ExchangeRoutes...)
allRoutes = append(allRoutes, ConfigRoutes...)
allRoutes = append(allRoutes, PortfolioRoutes...)
allRoutes = append(allRoutes, WalletRoutes...)
allRoutes = append(allRoutes, IndexRoute...)
allRoutes = append(allRoutes, WebsocketRoutes...)
allRoutes = append(allRoutes, OrderbookRoutes...)
for _, route := range allRoutes {
routes = Routes{
Route{
"GetAllSettings",
"GET",
"/config/all",
RESTGetAllSettings,
},
Route{
"SaveAllSettings",
"POST",
"/config/all/save",
RESTSaveAllSettings,
},
Route{
"AllEnabledAccountInfo",
"GET",
"/exchanges/enabled/accounts/all",
RESTGetAllEnabledAccountInfo,
},
Route{
"AllActiveExchangesAndCurrencies",
"GET",
"/exchanges/enabled/latest/all",
RESTGetAllActiveTickers,
},
Route{
"IndividualExchangeAndCurrency",
"GET",
"/exchanges/{exchangeName}/latest/{currency}",
RESTGetTicker,
},
Route{
"GetPortfolio",
"GET",
"/portfolio/all",
RESTGetPortfolio,
},
Route{
"AllActiveExchangesAndOrderbooks",
"GET",
"/exchanges/orderbook/latest/all",
RESTGetAllActiveOrderbooks,
},
Route{
"IndividualExchangeOrderbook",
"GET",
"/exchanges/{exchangeName}/orderbook/latest/{currency}",
RESTGetOrderbook,
},
}
for _, route := range routes {
var handler http.Handler
handler = route.HandlerFunc
handler = Logger(handler, route.Name)
handler = RESTLogger(handler, route.Name)
router.
Methods(route.Method).

View File

@@ -1,11 +0,0 @@
package main
import (
"testing"
)
func TestNewRouter(t *testing.T) {
if value := NewRouter(bot.exchanges); value.KeepContext {
t.Error("Test Failed - Restful_Router_Test.go - NewRouter Error")
}
}

View File

@@ -1,16 +0,0 @@
package main
import "net/http"
// Route is a sub type that holds the request routes
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
// Routes is an array of all the registered routes
type Routes []Route
var routes = Routes{}

297
restful_server.go Normal file
View File

@@ -0,0 +1,297 @@
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/thrasher-/gocryptotrader/config"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
// AllEnabledExchangeOrderbooks holds the enabled exchange orderbooks
type AllEnabledExchangeOrderbooks struct {
Data []EnabledExchangeOrderbooks `json:"data"`
}
// EnabledExchangeOrderbooks is a sub type for singular exchanges and respective
// orderbooks
type EnabledExchangeOrderbooks struct {
ExchangeName string `json:"exchangeName"`
ExchangeValues []orderbook.Base `json:"exchangeValues"`
}
// AllEnabledExchangeCurrencies holds the enabled exchange currencies
type AllEnabledExchangeCurrencies struct {
Data []EnabledExchangeCurrencies `json:"data"`
}
// EnabledExchangeCurrencies is a sub type for singular exchanges and respective
// currencies
type EnabledExchangeCurrencies struct {
ExchangeName string `json:"exchangeName"`
ExchangeValues []ticker.Price `json:"exchangeValues"`
}
// AllEnabledExchangeAccounts holds all enabled accounts info
type AllEnabledExchangeAccounts struct {
Data []exchange.AccountInfo `json:"data"`
}
// RESTfulJSONResponse outputs a JSON response of the req interface
func RESTfulJSONResponse(w http.ResponseWriter, r *http.Request, req interface{}) error {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(req); err != nil {
return err
}
return nil
}
// RESTfulError prints the REST method and error
func RESTfulError(method string, err error) {
log.Printf("RESTful %s: server failed to send JSON response. Error %s",
method, err)
}
// RESTGetAllSettings replies to a request with an encoded JSON response about the
// trading bots configuration.
func RESTGetAllSettings(w http.ResponseWriter, r *http.Request) {
err := RESTfulJSONResponse(w, r, bot.config)
if err != nil {
RESTfulError(r.Method, err)
}
}
// RESTSaveAllSettings saves all current settings from request body as a JSON
// document then reloads state and returns the settings
func RESTSaveAllSettings(w http.ResponseWriter, r *http.Request) {
//Get the data from the request
decoder := json.NewDecoder(r.Body)
var responseData config.Post
err := decoder.Decode(&responseData)
if err != nil {
RESTfulError(r.Method, err)
}
//Save change the settings
err = bot.config.UpdateConfig(bot.configFile, responseData.Data)
if err != nil {
RESTfulError(r.Method, err)
}
err = RESTfulJSONResponse(w, r, bot.config)
if err != nil {
RESTfulError(r.Method, err)
}
}
// RESTGetOrderbook returns orderbook info for a given currency, exchange and
// asset type
func RESTGetOrderbook(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
currency := vars["currency"]
exchange := vars["exchangeName"]
assetType := vars["assetType"]
if assetType == "" {
assetType = orderbook.Spot
}
response, err := GetSpecificOrderbook(currency, exchange, assetType)
if err != nil {
log.Printf("Failed to fetch orderbook for %s currency: %s\n", exchange,
currency)
return
}
err = RESTfulJSONResponse(w, r, response)
if err != nil {
RESTfulError(r.Method, err)
}
}
// GetAllActiveOrderbooks returns all enabled exchanges orderbooks
func GetAllActiveOrderbooks() []EnabledExchangeOrderbooks {
var orderbookData []EnabledExchangeOrderbooks
for _, individualBot := range bot.exchanges {
if individualBot != nil && individualBot.IsEnabled() {
var individualExchange EnabledExchangeOrderbooks
exchangeName := individualBot.GetName()
individualExchange.ExchangeName = exchangeName
currencies := individualBot.GetEnabledCurrencies()
assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName)
if err != nil {
log.Printf("failed to get %s exchange asset types. Error: %s",
exchangeName, err)
continue
}
for _, x := range currencies {
currency := x
var ob orderbook.Base
if len(assetTypes) > 1 {
for y := range assetTypes {
ob, err = individualBot.GetOrderbookEx(currency,
assetTypes[y])
}
} else {
ob, err = individualBot.GetOrderbookEx(currency,
assetTypes[0])
}
if err != nil {
log.Printf("failed to get %s %s orderbook. Error: %s",
currency.Pair().String(),
exchangeName,
err)
continue
}
individualExchange.ExchangeValues = append(
individualExchange.ExchangeValues, ob,
)
}
orderbookData = append(orderbookData, individualExchange)
}
}
return orderbookData
}
// RESTGetAllActiveOrderbooks returns all enabled exchange orderbooks
func RESTGetAllActiveOrderbooks(w http.ResponseWriter, r *http.Request) {
var response AllEnabledExchangeOrderbooks
response.Data = GetAllActiveOrderbooks()
err := RESTfulJSONResponse(w, r, response)
if err != nil {
RESTfulError(r.Method, err)
}
}
// RESTGetPortfolio returns the bot portfolio
func RESTGetPortfolio(w http.ResponseWriter, r *http.Request) {
result := bot.portfolio.GetPortfolioSummary()
err := RESTfulJSONResponse(w, r, result)
if err != nil {
RESTfulError(r.Method, err)
}
}
// RESTGetTicker returns ticker info for a given currency, exchange and
// asset type
func RESTGetTicker(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
currency := vars["currency"]
exchange := vars["exchangeName"]
assetType := vars["assetType"]
if assetType == "" {
assetType = ticker.Spot
}
response, err := GetSpecificTicker(currency, exchange, assetType)
if err != nil {
log.Printf("Failed to fetch ticker for %s currency: %s\n", exchange,
currency)
return
}
err = RESTfulJSONResponse(w, r, response)
if err != nil {
RESTfulError(r.Method, err)
}
}
// GetAllActiveTickers returns all enabled exchange tickers
func GetAllActiveTickers() []EnabledExchangeCurrencies {
var tickerData []EnabledExchangeCurrencies
for _, individualBot := range bot.exchanges {
if individualBot != nil && individualBot.IsEnabled() {
var individualExchange EnabledExchangeCurrencies
exchangeName := individualBot.GetName()
individualExchange.ExchangeName = exchangeName
log.Println(
"Getting enabled currencies for '" + exchangeName + "'",
)
currencies := individualBot.GetEnabledCurrencies()
for _, x := range currencies {
currency := x
assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName)
if err != nil {
log.Printf("failed to get %s exchange asset types. Error: %s",
exchangeName, err)
continue
}
var tickerPrice ticker.Price
if len(assetTypes) > 1 {
for y := range assetTypes {
tickerPrice, err = individualBot.GetTickerPrice(currency,
assetTypes[y])
}
} else {
tickerPrice, err = individualBot.GetTickerPrice(currency,
assetTypes[0])
}
if err != nil {
log.Printf("failed to get %s %s ticker. Error: %s",
currency.Pair().String(),
exchangeName,
err)
continue
}
individualExchange.ExchangeValues = append(
individualExchange.ExchangeValues, tickerPrice,
)
}
tickerData = append(tickerData, individualExchange)
}
}
return tickerData
}
// RESTGetAllActiveTickers returns all active tickers
func RESTGetAllActiveTickers(w http.ResponseWriter, r *http.Request) {
var response AllEnabledExchangeCurrencies
response.Data = GetAllActiveTickers()
err := RESTfulJSONResponse(w, r, response)
if err != nil {
RESTfulError(r.Method, err)
}
}
// GetAllEnabledExchangeAccountInfo returns all the current enabled exchanges
func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts {
var response AllEnabledExchangeAccounts
for _, individualBot := range bot.exchanges {
if individualBot != nil && individualBot.IsEnabled() {
if !individualBot.GetAuthenticatedAPISupport() {
log.Printf("GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.", individualBot.GetName())
continue
}
individualExchange, err := individualBot.GetExchangeAccountInfo()
if err != nil {
log.Printf("Error encountered retrieving exchange account info for %s. Error %s",
individualBot.GetName(), err)
continue
}
response.Data = append(response.Data, individualExchange)
}
}
return response
}
// RESTGetAllEnabledAccountInfo via get request returns JSON response of account
// info
func RESTGetAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) {
response := GetAllEnabledExchangeAccountInfo()
err := RESTfulJSONResponse(w, r, response)
if err != nil {
RESTfulError(r.Method, err)
}
}

View File

@@ -2,6 +2,7 @@ package smsglobal
import (
"errors"
"flag"
"log"
"net/url"
"strings"
@@ -44,7 +45,7 @@ func SMSSendToAll(message string, cfg config.Config) {
// SMSGetNumberByName returns contact number by supplied name
func SMSGetNumberByName(name string, smsCfg config.SMSGlobalConfig) string {
for _, contact := range smsCfg.Contacts {
if contact.Name == name {
if common.StringToUpper(contact.Name) == common.StringToUpper(name) {
return contact.Number
}
}
@@ -53,6 +54,10 @@ func SMSGetNumberByName(name string, smsCfg config.SMSGlobalConfig) string {
// SMSNotify sends a message to an individual contact
func SMSNotify(to, message string, cfg config.Config) error {
if flag.Lookup("test.v") != nil {
return nil
}
values := url.Values{}
values.Set("action", "sendsms")
values.Set("user", cfg.SMS.Username)

View File

@@ -1,139 +0,0 @@
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/thrasher-/gocryptotrader/currency/pair"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
func GetSpecificTicker(currency, exchangeName, assetType string) (ticker.Price, error) {
var specificTicker ticker.Price
var err error
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName {
specificTicker, err = bot.exchanges[i].GetTickerPrice(
pair.NewCurrencyPairFromString(currency),
assetType,
)
break
}
}
}
return specificTicker, err
}
func jsonTickerResponse(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
currency := vars["currency"]
exchange := vars["exchangeName"]
assetType := vars["assetType"]
if assetType == "" {
assetType = ticker.Spot
}
response, err := GetSpecificTicker(currency, exchange, assetType)
if err != nil {
log.Printf("Failed to fetch ticker for %s currency: %s\n", exchange,
currency)
return
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
panic(err)
}
}
// AllEnabledExchangeCurrencies holds the enabled exchange currencies
type AllEnabledExchangeCurrencies struct {
Data []EnabledExchangeCurrencies `json:"data"`
}
// EnabledExchangeCurrencies is a sub type for singular exchanges and respective
// currencies
type EnabledExchangeCurrencies struct {
ExchangeName string `json:"exchangeName"`
ExchangeValues []ticker.Price `json:"exchangeValues"`
}
func GetAllActiveTickers() []EnabledExchangeCurrencies {
var tickerData []EnabledExchangeCurrencies
for _, individualBot := range bot.exchanges {
if individualBot != nil && individualBot.IsEnabled() {
var individualExchange EnabledExchangeCurrencies
exchangeName := individualBot.GetName()
individualExchange.ExchangeName = exchangeName
log.Println(
"Getting enabled currencies for '" + exchangeName + "'",
)
currencies := individualBot.GetEnabledCurrencies()
for _, x := range currencies {
currency := x
assetTypes, err := exchange.GetExchangeAssetTypes(exchangeName)
if err != nil {
log.Printf("failed to get %s exchange asset types. Error: %s",
exchangeName, err)
continue
}
var tickerPrice ticker.Price
if len(assetTypes) > 1 {
for y := range assetTypes {
tickerPrice, err = individualBot.UpdateTicker(currency,
assetTypes[y])
}
} else {
tickerPrice, err = individualBot.UpdateTicker(currency,
assetTypes[0])
}
if err != nil {
log.Printf("failed to get %s %s ticker. Error: %s",
currency.Pair().String(),
exchangeName,
err)
continue
}
individualExchange.ExchangeValues = append(
individualExchange.ExchangeValues, tickerPrice,
)
}
tickerData = append(tickerData, individualExchange)
}
}
return tickerData
}
func getAllActiveTickersResponse(w http.ResponseWriter, r *http.Request) {
var response AllEnabledExchangeCurrencies
response.Data = GetAllActiveTickers()
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
panic(err)
}
}
// ExchangeRoutes denotes the current exchange routes
var ExchangeRoutes = Routes{
Route{
"AllActiveExchangesAndCurrencies",
"GET",
"/exchanges/enabled/latest/all",
getAllActiveTickersResponse,
},
Route{
"IndividualExchangeAndCurrency",
"GET",
"/exchanges/{exchangeName}/latest/{currency}",
jsonTickerResponse,
},
}

View File

@@ -1 +0,0 @@
package main

View File

@@ -7,6 +7,7 @@ import (
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
)
var (
@@ -74,7 +75,7 @@ func main() {
log.Println("Connected to websocket!")
log.Println("Authenticating..")
SendWebsocketAuth("username", "password")
SendWebsocketAuth("blah", "blah")
var wsResp WebsocketEventResponse
err = WSConn.ReadJSON(&wsResp)
@@ -112,9 +113,22 @@ func main() {
log.Printf("Fetched config.")
dataJSON, err := common.JSONEncode(&wsResp.Data)
if err != nil {
log.Fatal(err)
}
var resultCfg config.Config
err = common.JSONDecode(dataJSON, &resultCfg)
if err != nil {
log.Fatal(err)
}
resultCfg.Name = "TEST"
req = WebsocketEvent{
Event: "SaveConfig",
Data: wsResp.Data,
Data: resultCfg,
}
log.Println("Saving config..")

View File

@@ -1,92 +0,0 @@
package main
import (
"encoding/json"
"errors"
"log"
"net/http"
"github.com/thrasher-/gocryptotrader/exchanges"
)
// AllEnabledExchangeAccounts holds all enabled accounts info
type AllEnabledExchangeAccounts struct {
Data []exchange.AccountInfo `json:"data"`
}
// GetCollatedExchangeAccountInfoByCoin collates individual exchange account
// information and turns into into a map string of
// exchange.AccountCurrencyInfo
func GetCollatedExchangeAccountInfoByCoin(accounts []exchange.AccountInfo) map[string]exchange.AccountCurrencyInfo {
result := make(map[string]exchange.AccountCurrencyInfo)
for i := 0; i < len(accounts); i++ {
for j := 0; j < len(accounts[i].Currencies); j++ {
currencyName := accounts[i].Currencies[j].CurrencyName
avail := accounts[i].Currencies[j].TotalValue
onHold := accounts[i].Currencies[j].Hold
info, ok := result[currencyName]
if !ok {
accountInfo := exchange.AccountCurrencyInfo{CurrencyName: currencyName, Hold: onHold, TotalValue: avail}
result[currencyName] = accountInfo
} else {
info.Hold += onHold
info.TotalValue += avail
result[currencyName] = info
}
}
}
return result
}
// GetAccountCurrencyInfoByExchangeName returns info for an exchange
func GetAccountCurrencyInfoByExchangeName(accounts []exchange.AccountInfo, exchangeName string) (exchange.AccountInfo, error) {
for i := 0; i < len(accounts); i++ {
if accounts[i].ExchangeName == exchangeName {
return accounts[i], nil
}
}
return exchange.AccountInfo{}, errors.New(exchange.ErrExchangeNotFound)
}
// GetAllEnabledExchangeAccountInfo returns all the current enabled exchanges
func GetAllEnabledExchangeAccountInfo() AllEnabledExchangeAccounts {
var response AllEnabledExchangeAccounts
for _, individualBot := range bot.exchanges {
if individualBot != nil && individualBot.IsEnabled() {
if !individualBot.GetAuthenticatedAPISupport() {
log.Printf("GetAllEnabledExchangeAccountInfo: Skippping %s due to disabled authenticated API support.", individualBot.GetName())
continue
}
individualExchange, err := individualBot.GetExchangeAccountInfo()
if err != nil {
log.Printf("Error encountered retrieving exchange account info for %s. Error %s",
individualBot.GetName(), err)
continue
}
response.Data = append(response.Data, individualExchange)
}
}
return response
}
// SendAllEnabledAccountInfo via get request returns JSON response of account
// info
func SendAllEnabledAccountInfo(w http.ResponseWriter, r *http.Request) {
response := GetAllEnabledExchangeAccountInfo()
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
panic(err)
}
}
// WalletRoutes are current routes specified for queries.
var WalletRoutes = Routes{
Route{
"AllEnabledAccountInfo",
"GET",
"/exchanges/enabled/accounts/all",
SendAllEnabledAccountInfo,
},
}

View File

@@ -1,28 +0,0 @@
package main
import (
"testing"
)
func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) {
GetCollatedExchangeAccountInfoByCoin(GetAllEnabledExchangeAccountInfo().Data)
}
func TestGetAccountCurrencyInfoByExchangeName(t *testing.T) {
_, err := GetAccountCurrencyInfoByExchangeName(
GetAllEnabledExchangeAccountInfo().Data, "ANX",
)
if err == nil {
t.Error(
"Test Failed - Wallet_Routes_Test.go - GetAccountCurrencyInfoByExchangeName",
)
}
}
func TestGetAllEnabledExchangeAccountInfo(t *testing.T) {
if value := GetAllEnabledExchangeAccountInfo(); len(value.Data) != 0 {
t.Error(
"Test Failed - Wallet_Routes_Test.go - GetAllEnabledExchangeAccountInfo",
)
}
}

View File

@@ -9,12 +9,15 @@ import (
"github.com/gorilla/websocket"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency"
)
// Const vars for websocket
const (
WebsocketResponseSuccess = "OK"
)
// WebsocketRoutes adds ws route to the HTTP server
var WebsocketRoutes = Routes{
Route{
"ws",
@@ -24,6 +27,7 @@ var WebsocketRoutes = Routes{
},
}
// WebsocketClient stores information related to the websocket client
type WebsocketClient struct {
ID int
Conn *websocket.Conn
@@ -31,6 +35,7 @@ type WebsocketClient struct {
Authenticated bool
}
// WebsocketEvent is the struct used for websocket events
type WebsocketEvent struct {
Exchange string `json:"exchange,omitempty"`
AssetType string `json:"assetType,omitempty"`
@@ -38,20 +43,26 @@ type WebsocketEvent struct {
Data interface{}
}
// WebsocketEventResponse is the struct used for websocket event responses
type WebsocketEventResponse struct {
Event string `json:"event"`
Data interface{} `json:"data"`
Error string `json:"error"`
}
type WebsocketTickerRequest struct {
// WebsocketOrderbookTickerRequest is a struct used for ticker and orderbook
// requests
type WebsocketOrderbookTickerRequest struct {
Exchange string `json:"exchangeName"`
Currency string `json:"currency"`
AssetType string `json:"assetType"`
}
// WebsocketClientHub stores an array of websocket clients
var WebsocketClientHub []WebsocketClient
// WebsocketClientHandler upgrades the HTTP connection to a websocket
// compatible one
func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) {
upgrader := websocket.Upgrader{
WriteBufferSize: 1024,
@@ -73,6 +84,7 @@ func WebsocketClientHandler(w http.ResponseWriter, r *http.Request) {
log.Println("New websocket client connected.")
}
// DisconnectWebsocketClient disconnects a websocket client
func DisconnectWebsocketClient(id int, err error) {
for i := range WebsocketClientHub {
if WebsocketClientHub[i].ID == id {
@@ -84,6 +96,7 @@ func DisconnectWebsocketClient(id int, err error) {
}
}
// SendWebsocketMessage sends a websocket message to a specific client
func SendWebsocketMessage(id int, data interface{}) error {
for _, x := range WebsocketClientHub {
if x.ID == id {
@@ -93,6 +106,8 @@ func SendWebsocketMessage(id int, data interface{}) error {
return nil
}
// BroadcastWebsocketMessage broadcasts a websocket event message to all
// websocket clients
func BroadcastWebsocketMessage(evt WebsocketEvent) error {
for _, x := range WebsocketClientHub {
x.Conn.WriteJSON(evt)
@@ -100,29 +115,163 @@ func BroadcastWebsocketMessage(evt WebsocketEvent) error {
return nil
}
// WebsocketAuth is a struct used for
type WebsocketAuth struct {
Username string `json:"username"`
Password string `json:"password"`
}
type wsCommandHandler func(wsClient *websocket.Conn, data interface{}) error
var wsHandlers = map[string]wsCommandHandler{
"getconfig": wsGetConfig,
"saveconfig": wsSaveConfig,
"getaccountinfo": wsGetAccountInfo,
"gettickers": wsGetTickers,
"getticker": wsGetTicker,
"getorderbooks": wsGetOrderbooks,
"getorderbook": wsGetOrderbook,
"getexchangerates": wsGetExchangeRates,
"getportfolio": wsGetPortfolio,
}
func wsGetConfig(wsClient *websocket.Conn, data interface{}) error {
wsResp := WebsocketEventResponse{
Event: "GetConfig",
Data: bot.config,
}
return wsClient.WriteJSON(wsResp)
}
func wsSaveConfig(wsClient *websocket.Conn, data interface{}) error {
wsResp := WebsocketEventResponse{
Event: "SaveConfig",
}
var cfg config.Config
err := common.JSONDecode(data.([]byte), &cfg)
if err != nil {
wsResp.Error = err.Error()
err = wsClient.WriteJSON(wsResp)
if err != nil {
return err
}
}
err = bot.config.UpdateConfig(bot.configFile, cfg)
if err != nil {
wsResp.Error = err.Error()
err = wsClient.WriteJSON(wsResp)
if err != nil {
return err
}
}
setupBotExchanges()
wsResp.Data = WebsocketResponseSuccess
return wsClient.WriteJSON(wsResp)
}
func wsGetAccountInfo(wsClient *websocket.Conn, data interface{}) error {
accountInfo := GetAllEnabledExchangeAccountInfo()
wsResp := WebsocketEventResponse{
Event: "GetAccountInfo",
Data: accountInfo,
}
return wsClient.WriteJSON(wsResp)
}
func wsGetTickers(wsClient *websocket.Conn, data interface{}) error {
wsResp := WebsocketEventResponse{
Event: "GetTickers",
}
wsResp.Data = GetAllActiveTickers()
return wsClient.WriteJSON(wsResp)
}
func wsGetTicker(wsClient *websocket.Conn, data interface{}) error {
wsResp := WebsocketEventResponse{
Event: "GetTicker",
}
var tickerReq WebsocketOrderbookTickerRequest
err := common.JSONDecode(data.([]byte), &tickerReq)
if err != nil {
wsResp.Error = err.Error()
wsClient.WriteJSON(wsResp)
return err
}
result, err := GetSpecificTicker(tickerReq.Currency,
tickerReq.Exchange, tickerReq.AssetType)
if err != nil {
wsResp.Error = err.Error()
wsClient.WriteJSON(wsResp)
return err
}
wsResp.Data = result
return wsClient.WriteJSON(wsResp)
}
func wsGetOrderbooks(wsClient *websocket.Conn, data interface{}) error {
wsResp := WebsocketEventResponse{
Event: "GetOrderbooks",
}
wsResp.Data = GetAllActiveOrderbooks()
return wsClient.WriteJSON(wsResp)
}
func wsGetOrderbook(wsClient *websocket.Conn, data interface{}) error {
wsResp := WebsocketEventResponse{
Event: "GetOrderbook",
}
var orderbookReq WebsocketOrderbookTickerRequest
err := common.JSONDecode(data.([]byte), &orderbookReq)
if err != nil {
wsResp.Error = err.Error()
wsClient.WriteJSON(wsResp)
return err
}
result, err := GetSpecificOrderbook(orderbookReq.Currency,
orderbookReq.Exchange, orderbookReq.AssetType)
if err != nil {
wsResp.Error = err.Error()
wsClient.WriteJSON(wsResp)
return err
}
wsResp.Data = result
return wsClient.WriteJSON(wsResp)
}
func wsGetExchangeRates(wsClient *websocket.Conn, data interface{}) error {
wsResp := WebsocketEventResponse{
Event: "GetExchangeRates",
}
if currency.YahooEnabled {
wsResp.Data = currency.CurrencyStore
} else {
wsResp.Data = currency.CurrencyStoreFixer
}
return wsClient.WriteJSON(wsResp)
}
func wsGetPortfolio(wsClient *websocket.Conn, data interface{}) error {
wsResp := WebsocketEventResponse{
Event: "GetPortfolio",
}
wsResp.Data = bot.portfolio.GetPortfolioSummary()
return wsClient.WriteJSON(wsResp)
}
// WebsocketHandler Handles websocket client requests
func WebsocketHandler() {
for {
for x := range WebsocketClientHub {
msgType, msg, err := WebsocketClientHub[x].Conn.ReadMessage()
if err != nil {
DisconnectWebsocketClient(x, err)
continue
}
if msgType != websocket.TextMessage {
DisconnectWebsocketClient(x, err)
continue
}
var evt WebsocketEvent
err = common.JSONDecode(msg, &evt)
err := WebsocketClientHub[x].Conn.ReadJSON(&evt)
if err != nil {
log.Println(err)
DisconnectWebsocketClient(x, err)
continue
}
@@ -137,6 +286,9 @@ func WebsocketHandler() {
continue
}
req := common.StringToLower(evt.Event)
log.Printf("Websocket req: %s", req)
if !WebsocketClientHub[x].Authenticated && evt.Event != "auth" {
wsResp := WebsocketEventResponse{
Event: "auth",
@@ -152,8 +304,8 @@ func WebsocketHandler() {
log.Println(err)
continue
}
hashPW := common.HexEncodeToString(common.GetSHA256([]byte("password")))
if auth.Username == "username" && auth.Password == hashPW {
hashPW := common.HexEncodeToString(common.GetSHA256([]byte(bot.config.Webserver.AdminUsername)))
if auth.Username == bot.config.Webserver.AdminUsername && auth.Password == hashPW {
WebsocketClientHub[x].Authenticated = true
wsResp := WebsocketEventResponse{
Event: "auth",
@@ -172,97 +324,15 @@ func WebsocketHandler() {
continue
}
}
switch evt.Event {
case "GetConfig":
wsResp := WebsocketEventResponse{
Event: "GetConfig",
Data: bot.config,
}
SendWebsocketMessage(x, wsResp)
result, ok := wsHandlers[req]
if !ok {
log.Printf("Websocket unsupported event")
continue
case "SaveConfig":
wsResp := WebsocketEventResponse{
Event: "SaveConfig",
}
var cfg config.Config
err := common.JSONDecode(dataJSON, &cfg)
if err != nil {
wsResp.Error = err.Error()
SendWebsocketMessage(x, wsResp)
log.Println(err)
continue
}
}
//Save change the settings
for x := range bot.config.Exchanges {
for i := 0; i < len(cfg.Exchanges); i++ {
if cfg.Exchanges[i].Name == bot.config.Exchanges[x].Name {
bot.config.Exchanges[x].Enabled = cfg.Exchanges[i].Enabled
bot.config.Exchanges[x].APIKey = cfg.Exchanges[i].APIKey
bot.config.Exchanges[x].APISecret = cfg.Exchanges[i].APISecret
bot.config.Exchanges[x].EnabledPairs = cfg.Exchanges[i].EnabledPairs
}
}
}
//Reload the configuration
err = bot.config.SaveConfig(bot.configFile)
if err != nil {
wsResp.Error = err.Error()
SendWebsocketMessage(x, wsResp)
continue
}
err = bot.config.LoadConfig(bot.configFile)
if err != nil {
wsResp.Error = err.Error()
SendWebsocketMessage(x, wsResp)
continue
}
setupBotExchanges()
wsResp.Data = WebsocketResponseSuccess
SendWebsocketMessage(x, wsResp)
continue
case "GetAccountInfo":
accountInfo := GetAllEnabledExchangeAccountInfo()
wsResp := WebsocketEventResponse{
Event: "GetAccountInfo",
Data: accountInfo,
}
SendWebsocketMessage(x, wsResp)
continue
case "GetTicker":
wsResp := WebsocketEventResponse{
Event: "GetTicker",
}
var tickerReq WebsocketTickerRequest
err := common.JSONDecode(dataJSON, &tickerReq)
if err != nil {
wsResp.Error = err.Error()
SendWebsocketMessage(x, wsResp)
log.Println(err)
continue
}
data, err := GetSpecificTicker(tickerReq.Currency,
tickerReq.Exchange, tickerReq.AssetType)
if err != nil {
wsResp.Error = err.Error()
SendWebsocketMessage(x, wsResp)
log.Println(err)
continue
}
wsResp.Data = data
SendWebsocketMessage(x, wsResp)
continue
case "GetTickers":
wsResp := WebsocketEventResponse{
Event: "GetTickers",
}
tickers := GetAllActiveTickers()
wsResp.Data = tickers
SendWebsocketMessage(x, wsResp)
err = result(WebsocketClientHub[x].Conn, dataJSON)
if err != nil {
log.Printf("Websocket request %s failed. Error %s", evt.Event, err)
continue
}
}