Files
gocryptotrader/events/events.go
Ryan O'Hara-Reid 9d0616d8cf New communications package
Support for Slack, SMSGlobal, SMTP and Telegram

Supersedes: https://github.com/thrasher-/gocryptotrader/pull/126
2018-06-08 14:09:36 +10:00

294 lines
6.5 KiB
Go

package events
import (
"errors"
"fmt"
"log"
"strconv"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/communications"
"github.com/thrasher-/gocryptotrader/communications/base"
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
)
const (
itemPrice = "PRICE"
greaterThan = ">"
greaterThanOrEqual = ">="
lessThan = "<"
lessThanOrEqual = "<="
isEqual = "=="
actionSMSNotify = "SMS"
actionConsolePrint = "CONSOLE_PRINT"
actionTest = "ACTION_TEST"
)
var (
errInvalidItem = errors.New("invalid item")
errInvalidCondition = errors.New("invalid conditional option")
errInvalidAction = errors.New("invalid action")
errExchangeDisabled = errors.New("desired exchange is disabled")
// NOTE comms is an interim implementation
comms *communications.Communications
)
// Event struct holds the event variables
type Event struct {
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
// appended
var Events []*Event
// SetComms is an interim function that will support a median integration. This
// sets the current comms package.
func SetComms(commsP *communications.Communications) {
comms = commsP
}
// AddEvent adds an event to the Events chain and returns an index/eventID
// and an 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
}
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.Pair = CurrencyPair
Event.Asset = Asset
Event.Action = Action
Event.Executed = false
Events = append(Events, Event)
return Event.ID, nil
}
// RemoveEvent deletes and event by its ID
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
}
// GetEventCounter displays the emount of total events on the chain and the
// events that have been executed.
func GetEventCounter() (int, int) {
total := len(Events)
executed := 0
for _, x := range Events {
if x.Executed {
executed++
}
}
return total, executed
}
// ExecuteAction will execute the action pending on the chain
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.String())
if action[1] == "ALL" {
comms.PushEvent(base.Event{TradeDetails: message})
}
}
} else {
log.Printf("Event triggered: %s", e.String())
}
return true
}
// EventToString turns the structure event into a 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.Pair.FirstCurrency.String(),
e.Pair.SecondCurrency.String(), e.Asset, e.Item, e.Exchange, condition[0]+" "+condition[1], e.Action,
)
}
// CheckCondition will check the event structure to see if there is a condition
// met
func (e *Event) CheckCondition() bool {
condition := common.SplitStrings(e.Condition, ",")
targetPrice, _ := strconv.ParseFloat(condition[1], 64)
t, err := ticker.GetTicker(e.Exchange, e.Pair, e.Asset)
if err != nil {
return false
}
lastPrice := t.Last
if lastPrice == 0 {
return false
}
switch condition[0] {
case greaterThan:
{
if lastPrice > targetPrice {
return e.ExecuteAction()
}
}
case greaterThanOrEqual:
{
if lastPrice >= targetPrice {
return e.ExecuteAction()
}
}
case lessThan:
{
if lastPrice < targetPrice {
return e.ExecuteAction()
}
}
case lessThanOrEqual:
{
if lastPrice <= targetPrice {
return e.ExecuteAction()
}
}
case isEqual:
{
if lastPrice == targetPrice {
return e.ExecuteAction()
}
}
}
return false
}
// IsValidEvent checks the actions to be taken and returns an error if incorrect
func IsValidEvent(Exchange, Item, Condition, Action string) error {
Exchange = common.StringToUpper(Exchange)
Item = common.StringToUpper(Item)
Action = common.StringToUpper(Action)
if !IsValidExchange(Exchange) {
return errExchangeDisabled
}
if !IsValidItem(Item) {
return errInvalidItem
}
if !common.StringContains(Condition, ",") {
return errInvalidCondition
}
condition := common.SplitStrings(Condition, ",")
if !IsValidCondition(condition[0]) || len(condition[1]) == 0 {
return errInvalidCondition
}
if common.StringContains(Action, ",") {
action := common.SplitStrings(Action, ",")
if action[0] != actionSMSNotify {
return errInvalidAction
}
if action[1] != "ALL" {
comms.PushEvent(base.Event{Type: action[1]})
}
} else {
if Action != actionConsolePrint && Action != actionTest {
return errInvalidAction
}
}
return nil
}
// CheckEvents is the overarching routine that will iterate through the Events
// chain
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
}
}
}
}
}
}
// IsValidExchange validates the exchange
func IsValidExchange(Exchange string) bool {
Exchange = common.StringToUpper(Exchange)
cfg := config.GetConfig()
for _, x := range cfg.Exchanges {
if x.Name == Exchange && x.Enabled {
return true
}
}
return false
}
// IsValidCondition validates passed in condition
func IsValidCondition(Condition string) bool {
switch Condition {
case greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, isEqual:
return true
}
return false
}
// IsValidAction validates passed in action
func IsValidAction(Action string) bool {
Action = common.StringToUpper(Action)
switch Action {
case actionSMSNotify, actionConsolePrint, actionTest:
return true
}
return false
}
// IsValidItem validates passed in Item
func IsValidItem(Item string) bool {
Item = common.StringToUpper(Item)
switch Item {
case itemPrice:
return true
}
return false
}