package main import ( "errors" "fmt" "log" "strconv" ) const ( ITEM_PRICE = "PRICE" GREATER_THAN = ">" GREATER_THAN_OR_EQUAL = ">=" LESS_THAN = "<" LESS_THAN_OR_EQUAL = "<=" IS_EQUAL = "==" ACTION_SMS_NOTIFY = "SMS" ACTION_CONSOLE_PRINT = "CONSOLE_PRINT" ) var ( ErrInvalidItem = errors.New("Invalid item.") ErrInvalidCondition = errors.New("Invalid conditional option.") ErrInvalidAction = errors.New("Invalid action.") ErrExchangeDisabled = errors.New("Desired exchange is disabled.") ErrFiatCurrencyInvalid = errors.New("Invalid fiat currency.") ) type Event struct { ID int Exchange string Item string Condition string CryptoCurrency string FiatCurrency string Action string Executed bool } var Events []*Event func AddEvent(Exchange, Item, Condition, CryptoCurrency, FiatCurrency, Action string) (int, error) { err := IsValidEvent(Exchange, Item, Condition, CryptoCurrency, FiatCurrency, Action) if err != nil { return 0, err } Event := &Event{} if len(Events) == 0 { Event.ID = 0 } else { Event.ID = len(Events) + 1 } Event.Exchange = Exchange Event.Item = Item Event.Condition = Condition Event.CryptoCurrency = CryptoCurrency Event.FiatCurrency = FiatCurrency Event.Action = Action Event.Executed = false Events = append(Events, Event) return Event.ID, nil } func RemoveEvent(EventID int) bool { for i, x := range Events { if x.ID == EventID { Events = append(Events[:i], Events[i+1:]...) return true } } return false } func GetEventCounter() (int, int) { total := len(Events) executed := 0 for _, x := range Events { if x.Executed { executed++ } } return total, executed } func (e *Event) ExecuteAction() bool { if StringContains(e.Action, ",") { action := SplitStrings(e.Action, ",") if action[0] == ACTION_SMS_NOTIFY { message := fmt.Sprintf("Event triggered: %s", e.EventToString()) if action[1] == "ALL" { SMSSendToAll(message) } else { SMSNotify(SMSGetNumberByName(action[1]), message) } } } else { log.Printf("Event triggered: %s", e.EventToString()) } return true } func (e *Event) EventToString() string { condition := SplitStrings(e.Condition, ",") return fmt.Sprintf("If the %s%s %s on %s is %s then %s.", e.CryptoCurrency, e.FiatCurrency, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action) } func (e *Event) CheckCondition() bool { lastPrice := 0.00 condition := SplitStrings(e.Condition, ",") targetPrice, _ := strconv.ParseFloat(condition[1], 64) /* to-do: add event handling for all currencies and fiat currencies */ if bot.exchange.bitfinex.GetName() == e.Exchange { result, err := bot.exchange.bitfinex.GetTicker("btcusd", nil) if err != nil { lastPrice = 0 } else { lastPrice = result.Last } } else if bot.exchange.bitstamp.GetName() == e.Exchange { lastPrice = bot.exchange.bitstamp.GetTicker().Last } else if bot.exchange.coinbase.GetName() == e.Exchange { lastPrice = bot.exchange.coinbase.GetTicker("BTC-USD").Price } else if bot.exchange.cryptsy.GetName() == e.Exchange { lastPrice = bot.exchange.cryptsy.Market["BTCUSD"].LastTrade.Price } else if bot.exchange.dwvx.GetName() == e.Exchange { result, err := bot.exchange.dwvx.GetTicker("BTCAUD") if err != nil { lastPrice = 0 } else { lastPrice = result.Last } } else if bot.exchange.lakebtc.GetName() == e.Exchange { lastPrice = bot.exchange.lakebtc.GetTicker().CNY.Last } else if bot.exchange.btcc.GetName() == e.Exchange { lastPrice = bot.exchange.btcc.GetTicker("btccny").Last } else if bot.exchange.huobi.GetName() == e.Exchange { lastPrice = bot.exchange.huobi.GetTicker("btc").Last } else if bot.exchange.itbit.GetName() == e.Exchange { lastPrice = bot.exchange.itbit.GetTicker("XBTUSD").LastPrice } else if bot.exchange.btce.GetName() == e.Exchange { lastPrice = bot.exchange.btce.Ticker["btc_usd"].Last } else if bot.exchange.btcmarkets.GetName() == e.Exchange { lastPrice = bot.exchange.btcmarkets.Ticker["BTC"].LastPrice } else if bot.exchange.okcoinChina.GetName() == e.Exchange { lastPrice = bot.exchange.okcoinChina.GetTicker("btc_cny").Last } else if bot.exchange.okcoinIntl.GetName() == e.Exchange { lastPrice = bot.exchange.okcoinIntl.GetTicker("btc_usd").Last } else if bot.exchange.anx.GetName() == e.Exchange { lastPrice = bot.exchange.anx.GetTicker("BTCUSD").Data.Last.Value } else if bot.exchange.kraken.GetName() == e.Exchange { lastPrice = bot.exchange.kraken.Ticker["XBTUSD"].Last } if lastPrice == 0 { return false } switch condition[0] { case GREATER_THAN: { if lastPrice > targetPrice { return e.ExecuteAction() } } case GREATER_THAN_OR_EQUAL: { if lastPrice >= targetPrice { return e.ExecuteAction() } } case LESS_THAN: { if lastPrice < targetPrice { return e.ExecuteAction() } } case LESS_THAN_OR_EQUAL: { if lastPrice <= targetPrice { return e.ExecuteAction() } } case IS_EQUAL: { if lastPrice == targetPrice { return e.ExecuteAction() } } } return false } func IsValidEvent(Exchange, Item, Condition, CryptoCurrency, FiatCurrency, Action string) error { if !IsValidExchange(Exchange) { return ErrExchangeDisabled } if !IsValidItem(Item) { return ErrInvalidItem } if !IsFiatCurrency(FiatCurrency) { return ErrFiatCurrencyInvalid } if !StringContains(Condition, ",") { return ErrInvalidCondition } condition := SplitStrings(Condition, ",") if !IsValidCondition(condition[0]) || len(condition[1]) == 0 { return ErrInvalidCondition } if StringContains(Action, ",") { action := SplitStrings(Action, ",") if action[0] != ACTION_SMS_NOTIFY { return ErrInvalidAction } if action[1] != "ALL" && SMSGetNumberByName(action[1]) == ErrSMSContactNotFound { return ErrInvalidAction } } else { if Action != ACTION_CONSOLE_PRINT { return ErrInvalidAction } } return nil } func CheckEvents() { for { total, executed := GetEventCounter() if total > 0 && executed != total { for _, event := range Events { if !event.Executed { success := event.CheckCondition() if success { log.Printf("Event %d triggered on %s successfully.\n", event.ID, event.Exchange) event.Executed = true } } } } } } func IsValidExchange(Exchange string) bool { if bot.exchange.bitfinex.GetName() == Exchange && bot.exchange.bitfinex.IsEnabled() || bot.exchange.bitstamp.GetName() == Exchange && bot.exchange.bitstamp.IsEnabled() || bot.exchange.btcc.GetName() == Exchange && bot.exchange.btcc.IsEnabled() || bot.exchange.btce.GetName() == Exchange && bot.exchange.btce.IsEnabled() || bot.exchange.btcmarkets.GetName() == Exchange && bot.exchange.btcmarkets.IsEnabled() || bot.exchange.coinbase.GetName() == Exchange && bot.exchange.coinbase.IsEnabled() || bot.exchange.cryptsy.GetName() == Exchange && bot.exchange.cryptsy.IsEnabled() || bot.exchange.dwvx.GetName() == Exchange && bot.exchange.dwvx.IsEnabled() || bot.exchange.huobi.GetName() == Exchange && bot.exchange.huobi.IsEnabled() || 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.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() { return true } return false } func IsValidCondition(Condition string) bool { switch Condition { case GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, IS_EQUAL: return true } return false } func IsValidAction(Action string) bool { switch Action { case ACTION_SMS_NOTIFY, ACTION_CONSOLE_PRINT: return true } return false } func IsValidItem(Item string) bool { switch Item { case ITEM_PRICE: return true } return false }