예제 #1
0
class Notifier():
    """Handles sending notifications via the configured notifiers
    """
    def __init__(self, notifier_config):
        """Initializes Notifier class

        Args:
            notifier_config (dict): A dictionary containing configuration for the notifications.
        """

        self.logger = structlog.get_logger()
        self.notifier_config = notifier_config
        self.last_analysis = dict()

        enabled_notifiers = list()
        self.logger = structlog.get_logger()
        self.twilio_configured = self._validate_required_config(
            'twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']
                ['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']
                ['receiver_number'])
            enabled_notifiers.append('twilio')

        self.discord_configured = self._validate_required_config(
            'discord', notifier_config)
        if self.discord_configured:
            self.discord_client = DiscordNotifier(
                webhook=notifier_config['discord']['required']['webhook'],
                username=notifier_config['discord']['required']['username'],
                avatar=notifier_config['discord']['optional']['avatar'])
            enabled_notifiers.append('discord')

        self.slack_configured = self._validate_required_config(
            'slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_webhook=notifier_config['slack']['required']['webhook'])
            enabled_notifiers.append('slack')

        self.gmail_configured = self._validate_required_config(
            'gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']
                ['destination_emails'])
            enabled_notifiers.append('gmail')

        self.telegram_configured = self._validate_required_config(
            'telegram', notifier_config)
        if self.telegram_configured:
            self.telegram_client = TelegramNotifier(
                token=notifier_config['telegram']['required']['token'],
                chat_id=notifier_config['telegram']['required']['chat_id'],
                parse_mode=notifier_config['telegram']['optional']
                ['parse_mode'])
            enabled_notifiers.append('telegram')

        self.webhook_configured = self._validate_required_config(
            'webhook', notifier_config)
        if self.webhook_configured:
            self.webhook_client = WebhookNotifier(
                url=notifier_config['webhook']['required']['url'],
                username=notifier_config['webhook']['optional']['username'],
                password=notifier_config['webhook']['optional']['password'])
            enabled_notifiers.append('webhook')

        self.stdout_configured = self._validate_required_config(
            'stdout', notifier_config)
        if self.stdout_configured:
            self.stdout_client = StdoutNotifier()
            enabled_notifiers.append('stdout')

        self.logger.info('enabled notifers: %s', enabled_notifiers)

    def notify_all(self, new_analysis):
        """Trigger a notification for all notification options.

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        self.notify_slack(new_analysis)
        self.notify_discord(new_analysis)
        self.notify_twilio(new_analysis)
        self.notify_gmail(new_analysis)
        self.notify_telegram(new_analysis)
        self.notify_webhook(new_analysis)
        self.notify_stdout(new_analysis)

    def notify_discord(self, new_analysis):
        """Send a notification via the discord notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.discord_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['discord']['optional']['template'])
            if message.strip():
                self.discord_client.notify(message)

    def notify_slack(self, new_analysis):
        """Send a notification via the slack notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.slack_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['slack']['optional']['template'])
            if message.strip():
                self.slack_client.notify(message)

    def notify_twilio(self, new_analysis):
        """Send a notification via the twilio notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.twilio_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['twilio']['optional']['template'])
            if message.strip():
                self.twilio_client.notify(message)

    def notify_gmail(self, new_analysis):
        """Send a notification via the gmail notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.gmail_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['gmail']['optional']['template'])
            if message.strip():
                self.gmail_client.notify(message)

    def notify_telegram(self, new_analysis):
        """Send a notification via the telegram notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.telegram_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['telegram']['optional']['template'])
            if message.strip():
                self.telegram_client.notify(message)

    def notify_webhook(self, new_analysis):
        """Send a notification via the webhook notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.webhook_configured:
            for exchange in new_analysis:
                for market in new_analysis[exchange]:
                    for indicator_type in new_analysis[exchange][market]:
                        for indicator in new_analysis[exchange][market][
                                indicator_type]:
                            for index, analysis in enumerate(
                                    new_analysis[exchange][market]
                                [indicator_type][indicator]):
                                analysis_dict = analysis['result'].to_dict(
                                    orient='records')
                                if analysis_dict:
                                    new_analysis[exchange][market][
                                        indicator_type][indicator][
                                            index] = analysis_dict[-1]
                                else:
                                    new_analysis[exchange][market][
                                        indicator_type][indicator][index] = ''

            self.webhook_client.notify(new_analysis)

    def notify_stdout(self, new_analysis):
        """Send a notification via the stdout notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.stdout_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['stdout']['optional']['template'])
            if message.strip():
                self.stdout_client.notify(message)

    def _validate_required_config(self, notifier, notifier_config):
        """Validate the required configuration items are present for a notifier.

        Args:
            notifier (str): The name of the notifier key in default-config.json
            notifier_config (dict): A dictionary containing configuration for the notifications.

        Returns:
            bool: Is the notifier configured?
        """

        notifier_configured = True
        for _, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured

    def _indicator_message_templater(self, new_analysis, template):
        """Creates a message from a user defined template

        Args:
            new_analysis (dict): A dictionary of data related to the analysis to send a message about.
            template (str): A Jinja formatted message template.

        Returns:
            str: The templated messages for the notifier.
        """

        if not self.last_analysis:
            self.last_analysis = new_analysis

        message_template = Template(template)
        new_message = str()
        for exchange in new_analysis:
            for market in new_analysis[exchange]:
                for indicator_type in new_analysis[exchange][market]:
                    if indicator_type == 'informants':
                        continue
                    for indicator in new_analysis[exchange][market][
                            indicator_type]:
                        for index, analysis in enumerate(
                                new_analysis[exchange][market][indicator_type]
                            [indicator]):
                            if analysis['result'].shape[0] == 0:
                                continue

                            values = dict()

                            if indicator_type == 'indicators':
                                for signal in analysis['config']['signal']:
                                    latest_result = analysis['result'].iloc[-1]

                                    values[signal] = analysis['result'].iloc[
                                        -1][signal]
                                    if isinstance(values[signal], float):
                                        values[signal] = format(
                                            values[signal], '.8f')
                            elif indicator_type == 'crossovers':
                                latest_result = analysis['result'].iloc[-1]

                                key_signal = '{}_{}'.format(
                                    analysis['config']['key_signal'],
                                    analysis['config']['key_indicator_index'])

                                crossed_signal = '{}_{}'.format(
                                    analysis['config']['crossed_signal'],
                                    analysis['config']
                                    ['crossed_indicator_index'])

                                values[key_signal] = analysis['result'].iloc[
                                    -1][key_signal]
                                if isinstance(values[key_signal], float):
                                    values[key_signal] = format(
                                        values[key_signal], '.8f')

                                values[crossed_signal] = analysis[
                                    'result'].iloc[-1][crossed_signal]
                                if isinstance(values[crossed_signal], float):
                                    values[crossed_signal] = format(
                                        values[crossed_signal], '.8f')

                            status = 'neutral'
                            if latest_result['is_hot']:
                                status = 'hot'
                            elif latest_result['is_cold']:
                                status = 'cold'

                            # Save status of indicator's new analysis
                            new_analysis[exchange][market][indicator_type][
                                indicator][index]['status'] = status

                            if latest_result['is_hot'] or latest_result[
                                    'is_cold']:
                                try:
                                    last_status = self.last_analysis[exchange][
                                        market][indicator_type][indicator][
                                            index]['status']
                                except:
                                    last_status = str()

                                should_alert = True
                                if analysis['config'][
                                        'alert_frequency'] == 'once':
                                    if last_status == status:
                                        should_alert = False

                                if not analysis['config']['alert_enabled']:
                                    should_alert = False

                                if should_alert:
                                    base_currency, quote_currency = market.split(
                                        '/')
                                    new_message += message_template.render(
                                        values=values,
                                        exchange=exchange,
                                        market=market,
                                        base_currency=base_currency,
                                        quote_currency=quote_currency,
                                        indicator=indicator,
                                        indicator_number=index,
                                        analysis=analysis,
                                        status=status,
                                        last_status=last_status)

        # Merge changes from new analysis into last analysis
        self.last_analysis = {**self.last_analysis, **new_analysis}
        return new_message
예제 #2
0
class Notifier(IndicatorUtils):
    """Handles sending notifications via the configured notifiers
    """

    def __init__(self, notifier_config, market_data):
        """Initializes Notifier class

        Args:
            notifier_config (dict): A dictionary containing configuration for the notifications.
        """

        self.logger = structlog.get_logger()
        self.notifier_config = notifier_config
        self.market_data = market_data
        self.last_analysis = dict()
        self.enable_charts = False
        self.all_historical_data = False
        self.timezone = None
        self.first_run = False

        enabled_notifiers = list()
        self.logger = structlog.get_logger()
        self.twilio_configured = self._validate_required_config('twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']['receiver_number']
            )
            enabled_notifiers.append('twilio')

        self.discord_configured = self._validate_required_config('discord', notifier_config)
        if self.discord_configured:
            self.discord_client = DiscordNotifier(
                webhook=notifier_config['discord']['required']['webhook'],
                username=notifier_config['discord']['required']['username'],
                avatar=notifier_config['discord']['optional']['avatar']
            )
            enabled_notifiers.append('discord')

        self.slack_configured = self._validate_required_config('slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_webhook=notifier_config['slack']['required']['webhook']
            )
            enabled_notifiers.append('slack')

        self.gmail_configured = self._validate_required_config('gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']['destination_emails']
            )
            enabled_notifiers.append('gmail')

        self.telegram_configured = self._validate_required_config('telegram', notifier_config)
        if self.telegram_configured:
            self.telegram_client = TelegramNotifier(
                token=notifier_config['telegram']['required']['token'],
                chat_id=notifier_config['telegram']['required']['chat_id'],
                parse_mode=notifier_config['telegram']['optional']['parse_mode']
            )
            enabled_notifiers.append('telegram')

        self.webhook_configured = self._validate_required_config('webhook', notifier_config)
        if self.webhook_configured:
            self.webhook_client = WebhookNotifier(
                url=notifier_config['webhook']['required']['url'],
                username=notifier_config['webhook']['optional']['username'],
                password=notifier_config['webhook']['optional']['password']
            )
            enabled_notifiers.append('webhook')

        self.stdout_configured = self._validate_required_config('stdout', notifier_config)
        if self.stdout_configured:
            self.stdout_client = StdoutNotifier()
            enabled_notifiers.append('stdout')

        self.logger.info('enabled notifers: %s', enabled_notifiers)


    def notify_all(self, new_analysis):
        """Trigger a notification for all notification options.

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        charts_dir = './charts'

        messages = self.get_indicator_messages(new_analysis)

        if self.enable_charts == True:
            if not os.path.exists(charts_dir):
                os.mkdir(charts_dir)

            self.create_charts(messages)

        self.notify_slack(new_analysis)
        self.notify_discord(new_analysis)
        self.notify_twilio(new_analysis)
        self.notify_gmail(new_analysis)
        self.notify_telegram(messages)
        self.notify_webhook(messages)
        self.notify_stdout(new_analysis)

    def notify_discord(self, new_analysis):
        """Send a notification via the discord notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.discord_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['discord']['optional']['template']
            )
            if message.strip():
                self.discord_client.notify(message)


    def notify_slack(self, new_analysis):
        """Send a notification via the slack notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.slack_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['slack']['optional']['template']
            )
            if message.strip():
                self.slack_client.notify(message)


    def notify_twilio(self, new_analysis):
        """Send a notification via the twilio notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.twilio_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['twilio']['optional']['template']
            )
            if message.strip():
                self.twilio_client.notify(message)


    def notify_gmail(self, new_analysis):
        """Send a notification via the gmail notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.gmail_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['gmail']['optional']['template']
            )
            if message.strip():
                self.gmail_client.notify(message)


    def notify_telegram(self, messages):
        """Send notifications via the telegram notifier

        Args:
            messages (dict): The notification messages to send.
        """

        if not self.telegram_configured:
            return

        message_template = Template(self.notifier_config['telegram']['optional']['template'])

        for exchange in messages:
            for market_pair in messages[exchange]:
                _messages = messages[exchange][market_pair]
                                    
                for candle_period in _messages:
                    if not isinstance(_messages[candle_period], list) or len (_messages[candle_period]) == 0:
                        continue

                    if self.enable_charts == True:
                        self.notify_telegram_chart(exchange, market_pair, candle_period, _messages[candle_period], message_template)
                    else:
                        self.notify_telegram_message(_messages[candle_period], message_template)


    def notify_telegram_chart(self, exchange, market_pair, candle_period, messages, message_template):
        market_pair = market_pair.replace('/', '_').lower()
        chart_file = '{}/{}_{}_{}.png'.format('./charts', exchange, market_pair, candle_period)

        if os.path.exists(chart_file):
            try:
                self.telegram_client.send_chart(open(chart_file, 'rb'))
                self.notify_telegram_message(messages, message_template)
            except (IOError, SyntaxError) :
                self.notify_telegram_message(messages, message_template)
        else:
            self.logger.info('Chart file %s doesnt exist, sending text message.', chart_file)
            self.notify_telegram_message(messages, message_template)

    def notify_telegram_message(self, messages, message_template):
        try:
            for message in messages:
                formatted_message = message_template.render(message)
                self.telegram_client.notify(formatted_message.strip())
        except (TelegramTimedOut) as e:
            self.logger.info('Error TimeOut!')
            self.logger.info(e)

    def notify_webhook(self, messages):
        """Send notifications via a new webhook notifier

        Args:
            messages (dict): The notification messages to send.
        """

        if not self.webhook_configured:
            return

        #message_template = Template(self.notifier_config['telegram']['optional']['template'])

        for exchange in messages:
            for market_pair in messages[exchange]:
                _messages = messages[exchange][market_pair]
                                    
                for candle_period in _messages:
                    if not isinstance(_messages[candle_period], list) or len (_messages[candle_period]) == 0:
                        continue

                    self.webhook_client.notify(exchange, market_pair, candle_period, _messages[candle_period], self.enable_charts)          

    def notify_stdout(self, new_analysis):
        """Send a notification via the stdout notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.stdout_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['stdout']['optional']['template']
            )
            if message.strip():
                self.stdout_client.notify(message)

    def _validate_required_config(self, notifier, notifier_config):
        """Validate the required configuration items are present for a notifier.

        Args:
            notifier (str): The name of the notifier key in default-config.json
            notifier_config (dict): A dictionary containing configuration for the notifications.

        Returns:
            bool: Is the notifier configured?
        """

        notifier_configured = True
        for _, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured


    def _indicator_message_templater(self, new_analysis, template):
        """Creates a message from a user defined template

        Args:
            new_analysis (dict): A dictionary of data related to the analysis to send a message about.
            template (str): A Jinja formatted message template.

        Returns:
            str: The templated messages for the notifier.
        """

        if not self.last_analysis:
            self.last_analysis = new_analysis

        message_template = Template(template)
        new_message = str()
        for exchange in new_analysis:
            for market in new_analysis[exchange]:
                for indicator_type in new_analysis[exchange][market]:
                    if indicator_type == 'informants':
                        continue
                    for indicator in new_analysis[exchange][market][indicator_type]:
                        for index, analysis in enumerate(new_analysis[exchange][market][indicator_type][indicator]):
                            if analysis['result'].shape[0] == 0:
                                continue

                            values = dict()

                            if indicator_type == 'indicators':
                                for signal in analysis['config']['signal']:
                                    latest_result = analysis['result'].iloc[-1]

                                    values[signal] = analysis['result'].iloc[-1][signal]
                                    if isinstance(values[signal], float):
                                        values[signal] = format(values[signal], '.8f')
                            elif indicator_type == 'crossovers':
                                latest_result = analysis['result'].iloc[-1]

                                key_signal = '{}_{}'.format(
                                    analysis['config']['key_signal'],
                                    analysis['config']['key_indicator_index']
                                )

                                crossed_signal = '{}_{}'.format(
                                    analysis['config']['crossed_signal'],
                                    analysis['config']['crossed_indicator_index']
                                )

                                values[key_signal] = analysis['result'].iloc[-1][key_signal]
                                if isinstance(values[key_signal], float):
                                        values[key_signal] = format(values[key_signal], '.8f')

                                values[crossed_signal] = analysis['result'].iloc[-1][crossed_signal]
                                if isinstance(values[crossed_signal], float):
                                        values[crossed_signal] = format(values[crossed_signal], '.8f')

                            status = 'neutral'
                            if latest_result['is_hot']:
                                status = 'hot'
                            elif latest_result['is_cold']:
                                status = 'cold'

                            if 'indicator_label' in analysis['config']:
                                indicator_label = analysis['config']['indicator_label']
                            else:
                                indicator_label = '{} {}'.format(indicator, analysis['config']['candle_period'])

                            # Save status of indicator's new analysis
                            new_analysis[exchange][market][indicator_type][indicator][index]['status'] = status

                            if latest_result['is_hot'] or latest_result['is_cold']:
                                try:
                                    last_status = self.last_analysis[exchange][market][indicator_type][indicator][index]['status']
                                except:
                                    last_status = str()

                                should_alert = True
                                if analysis['config']['alert_frequency'] == 'once':
                                    if last_status == status:
                                        should_alert = False

                                if not analysis['config']['alert_enabled']:
                                    should_alert = False

                                if should_alert:
                                    base_currency, quote_currency = market.split('/')
                                    new_message += message_template.render(
                                        values=values,
                                        exchange=exchange,
                                        market=market,
                                        base_currency=base_currency,
                                        quote_currency=quote_currency,
                                        indicator=indicator,
                                        indicator_number=index,
                                        analysis=analysis,
                                        status=status,
                                        last_status=last_status,
                                        indicator_label=indicator_label
                                    )

        # Merge changes from new analysis into last analysis
        self.last_analysis = {**self.last_analysis, **new_analysis}
        return new_message

    def get_indicator_messages(self, new_analysis):
        """Creates a message list from a user defined template

        Args:
            new_analysis (dict): A dictionary of data related to the analysis to send a message about.

        Returns:
            list: A list with the plain message data for the notifier.
        """

        if not self.last_analysis: #is the first run
            self.last_analysis = new_analysis
            self.first_run = True

        self.logger.info('Is first run: {}'.format(self.first_run))

        now = datetime.now(timezone(self.timezone))
        creation_date = now.strftime("%Y-%m-%d %H:%M:%S")

        new_messages = dict()
        ohlcv_values = dict()
        lrsi_values = dict()

        for exchange in new_analysis:
            new_messages[exchange] = dict()
            ohlcv_values[exchange] = dict()
            lrsi_values[exchange] = dict()

            for market_pair in new_analysis[exchange]:

                new_messages[exchange][market_pair] = dict()
                ohlcv_values[exchange][market_pair] = dict()
                lrsi_values[exchange][market_pair] = dict()

                #Getting price values for each market pair and candle period
                for indicator_type in new_analysis[exchange][market_pair]:
                    if indicator_type == 'informants':
                        #Getting OHLC prices
                        for index, analysis in enumerate(new_analysis[exchange][market_pair]['informants']['ohlcv']):
                            values = dict()
                            for signal in analysis['config']['signal']:
                                values[signal] = analysis['result'].iloc[-1][signal]
                                ohlcv_values[exchange][market_pair][analysis['config']['candle_period']] = values

                        #Getting LRSI values
                        if 'lrsi' in new_analysis[exchange][market_pair]['informants']:
                            for index, analysis in enumerate(new_analysis[exchange][market_pair]['informants']['lrsi']):
                                values = dict()
                                for signal in analysis['config']['signal']:
                                    values[signal] = analysis['result'].iloc[-1][signal]
                            
                                lrsi_values[exchange][market_pair][analysis['config']['candle_period']] = values                                 

                for indicator_type in new_analysis[exchange][market_pair]:
                    if indicator_type == 'informants':
                        continue

                    for indicator in new_analysis[exchange][market_pair][indicator_type]:
                        for index, analysis in enumerate(new_analysis[exchange][market_pair][indicator_type][indicator]):
                            if analysis['result'].shape[0] == 0:
                                continue

                            values = dict()
                            if 'candle_period' in analysis['config']:
                                candle_period = analysis['config']['candle_period']

                                if not candle_period in new_messages[exchange][market_pair]:
                                    new_messages[exchange][market_pair][candle_period] = list()

                            if indicator_type == 'indicators':
                                for signal in analysis['config']['signal']:
                                    latest_result = analysis['result'].iloc[-1]

                                    values[signal] = analysis['result'].iloc[-1][signal]
                                    if isinstance(values[signal], float):
                                        values[signal] = format(values[signal], '.2f')
                            elif indicator_type == 'crossovers':
                                latest_result = analysis['result'].iloc[-1]

                                key_signal = '{}_{}'.format(
                                    analysis['config']['key_signal'],
                                    analysis['config']['key_indicator_index']
                                )

                                crossed_signal = '{}_{}'.format(
                                    analysis['config']['crossed_signal'],
                                    analysis['config']['crossed_indicator_index']
                                )

                                values[key_signal] = analysis['result'].iloc[-1][key_signal]
                                if isinstance(values[key_signal], float):
                                        values[key_signal] = format(values[key_signal], '.2f')

                                values[crossed_signal] = analysis['result'].iloc[-1][crossed_signal]
                                if isinstance(values[crossed_signal], float):
                                        values[crossed_signal] = format(values[crossed_signal], '.2f')

                            status = 'neutral'
                            if latest_result['is_hot']:
                                status = 'hot'
                            elif latest_result['is_cold']:
                                status = 'cold'

                            if 'indicator_label' in analysis['config']:
                                indicator_label = analysis['config']['indicator_label']
                            else:
                                indicator_label = '{} {}'.format(indicator, analysis['config']['candle_period'])                                

                            # Save status of indicator's new analysis
                            new_analysis[exchange][market_pair][indicator_type][indicator][index]['status'] = status

                            if latest_result['is_hot'] or latest_result['is_cold']:
                                try:
                                    last_status = self.last_analysis[exchange][market_pair][indicator_type][indicator][index]['status']
                                except:
                                    last_status = str()

                                should_alert = True

                                if self.first_run:
                                    self.logger.info('Alert once for %s %s %s', market_pair, indicator, candle_period)
                                else:
                                    if analysis['config']['alert_frequency'] == 'once':
                                        if last_status == status:
                                            self.logger.info('Alert frecuency once. Dont alert. %s %s %s', 
                                            market_pair, indicator, candle_period)
                                            should_alert = False

                                if not analysis['config']['alert_enabled']:
                                    should_alert = False

                                if should_alert:
                                    base_currency, quote_currency = market_pair.split('/')
                                    precision = self.market_data[exchange][market_pair]['precision']
                                    decimal_format = '.{}f'.format(precision['price'])

                                    prices = ''
                                    candle_period = analysis['config']['candle_period']
                                    candle_values = ohlcv_values[exchange][market_pair]

                                    if candle_period in candle_values :
                                        for key, value in candle_values[candle_period].items():
                                            value = format(value, decimal_format)
                                            prices = '{} {}: {}' . format(prices, key.title(), value)                                   

                                    lrsi = ''
                                    if candle_period in lrsi_values[exchange][market_pair]:
                                        lrsi = lrsi_values[exchange][market_pair][candle_period]['lrsi']
                                        lrsi = format(lrsi, '.2f')

                                    """
                                    new_message = message_template.render(
                                        values=values, exchange=exchange, market=market_pair, base_currency=base_currency,
                                        quote_currency=quote_currency, indicator=indicator, indicator_number=index,
                                        analysis=analysis, status=status, last_status=last_status, 
                                        prices=prices, lrsi=lrsi, creation_date=creation_date, indicator_label=indicator_label)
                                    """

                                    #save some memory removing unused data
                                    if 'result' in analysis:
                                        del analysis['result']

                                    new_message = dict(
                                        values=values, exchange=exchange, market=market_pair, base_currency=base_currency,
                                        quote_currency=quote_currency, indicator=indicator, indicator_number=index,
                                        analysis=analysis, status=status, last_status=last_status, 
                                        prices=prices, lrsi=lrsi, creation_date=creation_date, indicator_label=indicator_label)                                    

                                    new_messages[exchange][market_pair][candle_period].append(new_message)

        # Merge changes from new analysis into last analysis
        self.last_analysis = {**self.last_analysis, **new_analysis}

        if self.first_run == True:
            self.first_run = False

        return new_messages
    
    def set_timezone(self, timezone):
        self.timezone = timezone

    def set_enable_charts(self, enable_charts):
        self.enable_charts = enable_charts
    
    def set_all_historical_data(self, all_historical_data):
        self.all_historical_data = all_historical_data

    def create_charts(self, messages):
        """Create charts to be available for all notifiers

        Args:
            messages (dict): All notification messages grouped by exchange, market and candle period
        """

        for exchange in messages:
            for market_pair in messages[exchange]:
                _messages = messages[exchange][market_pair]
                                    
                for candle_period in _messages:
                    candles_data = self.all_historical_data[exchange][market_pair][candle_period]
                    self.create_chart(exchange, market_pair, candle_period, candles_data)

    def create_chart(self, exchange, market_pair, candle_period, candles_data):
        now = datetime.now(timezone(self.timezone))
        creation_date = now.strftime("%Y-%m-%d %H:%M:%S")
        
        df = self.convert_to_dataframe(candles_data)

        plt.rc('axes', grid=True)
        plt.rc('grid', color='0.75', linestyle='-', linewidth=0.5)

        left, width = 0.1, 0.8
        rect1 = [left, 0.6, width, 0.3]
        rect2 = [left, 0.4, width, 0.2]
        rect3 = [left, 0.1, width, 0.3]

        fig = plt.figure(facecolor='white')
        fig.set_size_inches(8, 12, forward=True)
        axescolor = '#f6f6f6'  # the axes background color

        ax1 = fig.add_axes(rect1, facecolor=axescolor)  # left, bottom, width, height
        ax2 = fig.add_axes(rect2, facecolor=axescolor, sharex=ax1)
        ax3 = fig.add_axes(rect3, facecolor=axescolor, sharex=ax1)

        #Plot Candles chart
        self.plot_candlestick(ax1, df, candle_period)

        #Plot RSI (14)
        self.plot_rsi(ax2, df)

        # Calculate and plot MACD       
        self.plot_macd(ax3, df, candle_period)

        for ax in ax1, ax2, ax3:
            if ax != ax3:
                for label in ax.get_xticklabels():
                    label.set_visible(False)
            else:
                for label in ax.get_xticklabels():
                    label.set_rotation(30)
                    label.set_horizontalalignment('right')

            ax.xaxis.set_major_locator(mticker.MaxNLocator(10))
            ax.xaxis.set_major_formatter(DateFormatter('%d/%b'))
            ax.xaxis.set_tick_params(which='major', pad=15) 

        fig.autofmt_xdate()

        title = '{} {} {} - {}'.format(exchange, market_pair, candle_period, creation_date).upper()
        fig.suptitle(title, fontsize=14)

        market_pair = market_pair.replace('/', '_').lower()
        chart_file = '{}/{}_{}_{}.png'.format('./charts', exchange, market_pair, candle_period)

        plt.savefig(chart_file)
        plt.close(fig)

        return chart_file

    def candlestick_ohlc(self, ax, quotes, width=0.2, colorup='k', colordown='r',
                    alpha=1.0, ochl=False):
        """
        Plot the time, open, high, low, close as a vertical line ranging
        from low to high.  Use a rectangular bar to represent the
        open-close span.  If close >= open, use colorup to color the bar,
        otherwise use colordown

        Parameters
        ----------
        ax : `Axes`
            an Axes instance to plot to
        quotes : sequence of quote sequences
            data to plot.  time must be in float date format - see date2num
            (time, open, high, low, close, ...) vs
            (time, open, close, high, low, ...)
            set by `ochl`
        width : float
            fraction of a day for the rectangle width
        colorup : color
            the color of the rectangle where close >= open
        colordown : color
            the color of the rectangle where close <  open
        alpha : float
            the rectangle alpha level
        ochl: bool
            argument to select between ochl and ohlc ordering of quotes

        Returns
        -------
        ret : tuple
            returns (lines, patches) where lines is a list of lines
            added and patches is a list of the rectangle patches added

        """

        OFFSET = width / 2.0

        lines = []
        patches = []
        for q in quotes:
            if ochl:
                t, open, close, high, low = q[:5]
            else:
                t, open, high, low, close = q[:5]

            if close >= open:
                color = colorup
                lower = open
                height = close - open
            else:
                color = colordown
                lower = close
                height = open - close

            vline = Line2D(
                xdata=(t, t), ydata=(low, high),
                color=color,
                linewidth=0.5,
                antialiased=False,
            )

            rect = Rectangle(
                xy=(t - OFFSET, lower),
                width=width,
                height=height,
                facecolor=color,
                edgecolor=None,
                antialiased=False,
                alpha=1.0
            )

            lines.append(vline)
            patches.append(rect)
            ax.add_line(vline)
            ax.add_patch(rect)

        ax.autoscale_view()

        return lines, patches

    def plot_candlestick(self, ax, df, candle_period):
        textsize = 11

        _time = mdates.date2num(df.index.to_pydatetime())
        min_x = np.nanmin(_time)
        max_x = np.nanmax(_time)

        stick_width = ((max_x - min_x) / _time.size ) 

        prices = df["close"]

        ax.set_ymargin(0.2)
        ax.ticklabel_format(axis='y', style='plain')

        self.candlestick_ohlc(ax, zip(_time, df['open'], df['high'], df['low'], df['close']),
                    width=stick_width, colorup='olivedrab', colordown='crimson')
                    
        ma25 = self.moving_average(prices, 25, type='simple')
        ma7 = self.moving_average(prices, 7, type='simple')

        ax.plot(df.index, ma25, color='indigo', lw=0.6, label='MA (25)')
        ax.plot(df.index, ma7, color='orange', lw=0.6, label='MA (7)')
    
        ax.text(0.04, 0.94, 'MA (7, close, 0)', color='orange', transform=ax.transAxes, fontsize=textsize, va='top')
        ax.text(0.24, 0.94, 'MA (25, close, 0)', color='indigo', transform=ax.transAxes,  fontsize=textsize, va='top')

    def plot_rsi(self, ax, df):
        textsize = 11
        fillcolor = 'darkmagenta'

        rsi = self.relative_strength(df["close"])

        ax.plot(df.index, rsi, color=fillcolor, linewidth=0.5)
        ax.axhline(70, color='darkmagenta', linestyle='dashed', alpha=0.6)
        ax.axhline(30, color='darkmagenta', linestyle='dashed', alpha=0.6)
        ax.fill_between(df.index, rsi, 70, where=(rsi >= 70),
                        facecolor=fillcolor, edgecolor=fillcolor)
        ax.fill_between(df.index, rsi, 30, where=(rsi <= 30),
                        facecolor=fillcolor, edgecolor=fillcolor)
        ax.set_ylim(0, 100)
        ax.set_yticks([30, 70])
        ax.text(0.024, 0.94, 'RSI (14)', va='top',transform=ax.transAxes, fontsize=textsize)

    def plot_macd(self, ax, df, candle_period):
        textsize = 11

        df = StockDataFrame.retype(df)
        df['macd'] = df.get('macd')

        min_y = df.macd.min()
        max_y = df.macd.max()

        #Reduce Macd Histogram values to have a better visualization
        macd_h = df.macdh * 0.5

        if (macd_h.min() < min_y):
            min_y = macd_h.min()

        if (macd_h.max() > max_y):
            max_y = macd_h.max() 

        #Adding some extra space at bottom/top
        min_y = min_y * 1.2
        max_y = max_y * 1.2

        #Define candle bar width
        _time = mdates.date2num(df.index.to_pydatetime())
        min_x = np.nanmin(_time)
        max_x = np.nanmax(_time)

        bar_width = ((max_x - min_x) / _time.size ) * 0.8            

        ax.bar(x=_time, bottom=[0 for _ in macd_h.index], height=macd_h, width=bar_width, color="red", alpha = 0.4)
        ax.plot(_time, df.macd, color='blue', lw=0.6)
        ax.plot(_time, df.macds, color='red', lw=0.6)
        ax.set_ylim((min_y, max_y))
    
        ax.yaxis.set_major_locator(mticker.MaxNLocator(nbins=5, prune='upper'))
        ax.text(0.024, 0.94, 'MACD (12, 26, close, 9)', va='top', transform=ax.transAxes, fontsize=textsize) 

    def plot_ppsr(self, ax, df, candle_period):
        """Calculate Pivot Points, Supports and Resistances for given data
        
        :param df: pandas.DataFrame
        :return: pandas.DataFrame
        """
        PP = pd.Series((df['high'] + df['how'] + df['close']) / 3)
        R1 = pd.Series(2 * PP - df['low'])
        S1 = pd.Series(2 * PP - df['high'])
        R2 = pd.Series(PP + df['high'] - df['low'])
        S2 = pd.Series(PP - df['high'] + df['low'])
        R3 = pd.Series(df['high'] + 2 * (PP - df['low']))
        S3 = pd.Series(df['low'] - 2 * (df['high'] - PP))
        psr = {'PP': PP, 'R1': R1, 'S1': S1, 'R2': R2, 'S2': S2, 'R3': R3, 'S3': S3}
        PSR = pd.DataFrame(psr)
        df = df.join(PSR)
        return df

    def relative_strength(self, prices, n=14):
        """
        compute the n period relative strength indicator
        http://stockcharts.com/school/doku.php?id=chart_school:glossary_r#relativestrengthindex
        http://www.investopedia.com/terms/r/rsi.asp
        """

        deltas = np.diff(prices)
        seed = deltas[:n + 1]
        up = seed[seed >= 0].sum() / n
        down = -seed[seed < 0].sum() / n
        rs = up / down
        rsi = np.zeros_like(prices)
        rsi[:n] = 100. - 100. / (1. + rs)

        for i in range(n, len(prices)):
            delta = deltas[i - 1]  # cause the diff is 1 shorter

            if delta > 0:
                upval = delta
                downval = 0.
            else:
                upval = 0.
                downval = -delta

            up = (up * (n - 1) + upval) / n
            down = (down * (n - 1) + downval) / n

            rs = up / down
            rsi[i] = 100. - 100. / (1. + rs)

        return rsi


    def moving_average(self, x, n, type='simple'):
        """
        compute an n period moving average.

        type is 'simple' | 'exponential'

        """
        x = np.asarray(x)
        if type == 'simple':
            weights = np.ones(n)
        else:
            weights = np.exp(np.linspace(-1., 0., n))

        weights /= weights.sum()

        a = np.convolve(x, weights, mode='full')[:len(x)]
        a[:n] = a[n]
        return a
예제 #3
0
class Notifier():
    """Handles sending notifications via the configured notifiers
    """

    exchangeMap = {"binance": "币安",
                   "bitfinex": "bitfinex交易所",
                   "huobi": "火币全球",
                   "bittrex": "bittrex",
                   "gateio": "gateio",
                   "okex": "ok交易所",
                   "zb": "zb交易所",
                   "kucoin": "kucoin交易所",
                   "coinex":"coinex交易所",
                   "hitbtc":"hitbtc交易所",
                   "poloniex":"poloniex交易所"
                  }
                  
    periodMap = {"15min":"15分钟","30min":"30分钟","1h":"1小时", "1d":"日线", "d":"日线", "3d":"3日", "4h":"4小时", "6h":"6小时", "12h":"12小时", "w":"周线"}
    cst_tz = timezone('Asia/Shanghai')  
    utc_tz = timezone('UTC')
    webhook = 'https://oapi.dingtalk.com/robot/send?access_token=86546fe72b329fa2fac6f133dd6d6db318c4a12b13dd03b2d5df8c2375f73124'
    
    def __init__(self, notifier_config):
        """Initializes Notifier class

        Args:
            notifier_config (dict): A dictionary containing configuration for the notifications.
        """
        self.logger = structlog.get_logger()
        self.notifier_config = notifier_config
        self.last_analysis = dict()

        enabled_notifiers = list()
        self.logger = structlog.get_logger()
        self.twilio_configured = self._validate_required_config('twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']['receiver_number']
            )
            enabled_notifiers.append('twilio')

        self.discord_configured = self._validate_required_config('discord', notifier_config)
        if self.discord_configured:
            self.discord_client = DiscordNotifier(
                webhook=notifier_config['discord']['required']['webhook'],
                username=notifier_config['discord']['required']['username'],
                avatar=notifier_config['discord']['optional']['avatar']
            )
            enabled_notifiers.append('discord')

        self.slack_configured = self._validate_required_config('slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_webhook=notifier_config['slack']['required']['webhook']
            )
            enabled_notifiers.append('slack')

        self.gmail_configured = self._validate_required_config('gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']['destination_emails']
            )
            enabled_notifiers.append('gmail')

        self.telegram_configured = self._validate_required_config('telegram', notifier_config)
#        if self.telegram_configured:
#            self.telegram_client = TelegramNotifier(
#                token=notifier_config['telegram']['required']['token'],
#                chat_id=notifier_config['telegram']['required']['chat_id'],
#                parse_mode=notifier_config['telegram']['optional']['parse_mode']
#            )
#           enabled_notifiers.append('telegram')
#
        self.webhook_configured = self._validate_required_config('webhook', notifier_config)
        if self.webhook_configured:
            self.webhook_client = WebhookNotifier(
                url=notifier_config['webhook']['required']['url'],
                username=notifier_config['webhook']['optional']['username'],
                password=notifier_config['webhook']['optional']['password']
            )
            enabled_notifiers.append('webhook')

        self.stdout_configured = self._validate_required_config('stdout', notifier_config)
        if self.stdout_configured:
            self.stdout_client = StdoutNotifier()
            enabled_notifiers.append('stdout')

        self.logger.info('enabled notifiers: %s', enabled_notifiers)


    def notify_all(self, new_analysis):
        """Trigger a notification for all notification options.

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        self.notify_slack(new_analysis)
        self.notify_discord(new_analysis)
        self.notify_twilio(new_analysis)
        self.notify_gmail(new_analysis)
        self.notify_telegram(new_analysis)
        self.notify_webhook(new_analysis)
        self.notify_stdout(new_analysis)

    def notify_discord(self, new_analysis):
        """Send a notification via the discord notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.discord_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['discord']['optional']['template']
            )
            if message.strip():
                self.discord_client.notify(message)


    def notify_slack(self, new_analysis):
        """Send a notification via the slack notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.slack_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['slack']['optional']['template']
            )
            if message.strip():
                self.slack_client.notify(message)


    def notify_twilio(self, new_analysis):
        """Send a notification via the twilio notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.twilio_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['twilio']['optional']['template']
            )
            if message.strip():
                self.twilio_client.notify(message)


    def notify_gmail(self, new_analysis):
        """Send a notification via the gmail notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """
        if self.gmail_configured:
            (message, title) = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['gmail']['optional']['template']
            )
            message = message.strip()

            indicatorModes = sys.argv[3]
            if message != "":
                if(indicatorModes == 'easy'):
                    self.dingtalk(message, self.webhook)
                self.gmail_client.notify(message, title)

    def dingtalk(self, msg, webhook):
        headers = {'Content-Type': 'application/json; charset=utf-8'}
        data = {'msgtype': 'text', 'text': {'content': msg}, 'at': {'atMobiles': [], 'isAtAll': False}}
        post_data = json.dumps(data)
        response = requests.post(webhook, headers=headers, data=post_data)
        return response.text

    def notify_telegram(self, new_analysis):
        """Send a notification via the telegram notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.telegram_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['telegram']['optional']['template']
            )
            if message.strip():
                self.telegram_client.notify(message)


    def notify_webhook(self, new_analysis):
        """Send a notification via the webhook notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.webhook_configured:
            for exchange in new_analysis:
                for market in new_analysis[exchange]:
                    for indicator_type in new_analysis[exchange][market]:
                        for indicator in new_analysis[exchange][market][indicator_type]:
                            for index, analysis in enumerate(new_analysis[exchange][market][indicator_type][indicator]):
                                analysis_dict = analysis['result'].to_dict(orient='records')
                                if analysis_dict:
                                    new_analysis[exchange][market][indicator_type][indicator][index] = analysis_dict[-1]
                                else:
                                    new_analysis[exchange][market][indicator_type][indicator][index] = ''

            self.webhook_client.notify(new_analysis)

    def notify_stdout(self, new_analysis):
        """Send a notification via the stdout notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.stdout_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['stdout']['optional']['template']
            )
            if message.strip():
                self.stdout_client.notify(message)

    def _validate_required_config(self, notifier, notifier_config):
        """Validate the required configuration items are present for a notifier.

        Args:
            notifier (str): The name of the notifier key in default-config.json
            notifier_config (dict): A dictionary containing configuration for the notifications.

        Returns:
            bool: Is the notifier configured?
        """

        notifier_configured = True
        for _, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured


    def convertTitle(self):
        temp = sys.argv[2].split("_")
        items = temp[0].split("/")
        exchange = items[len(items)-1];
        period = temp[1].split(".")[0];
        type = ""
        if temp[len(temp)-1].split(".")[0] == "contract" :
            type = "合约"
            # [BTC - USD - 190927, ETC - USD - 190927, ETH - USD - 190927, LTC - USD - 190927, BSV - USD - 190927,
            #  BSV - USD - 190927, XRP - USD - 190927, TRX - USD - 190927,
            #  EOS - USD - 190927]
        return self.exchangeMap[exchange], self.periodMap[period], type
    
    def getLocalizeTime(self):
        utcnow = datetime.utcnow()  
        utcnow = utcnow.replace(tzinfo=self.utc_tz)  
        china = utcnow.astimezone(self.cst_tz)  
        return "   发送时间: 时区: 亚洲/上海(GMT+08:00)  %s"%china.strftime('%Y-%m-%d %H:%M:%S')
        
    def _indicator_message_templater(self, new_analysis, template):
        """Creates a message from a user defined template

        Args:
            new_analysis (dict): A dictionary of data related to the analysis to send a message about.
            template (str): A Jinja formatted message template.

        Returns:
            str: The templated messages for the notifier.
        """

        if not self.last_analysis:
            self.last_analysis = new_analysis

        message_template = Template(template)
        new_message = str()
        
        # extractCoins.matchCoinPairsToUsdt(sys.argv[2]);
        file = open(sys.argv[2], mode='r')
        text = file.read()
        if text == "":
            return "", ""

        (exchange, period, type) = self.convertTitle();
        title = '信号: '+ exchange + "  " + period + "    " + type + '\n\n'
        if len(sys.argv) > 5 and (sys.argv[5] == '-prefix'):
            prefix = self.notifier_config['gmail']['prefix']
            title = prefix + title
        new_message = new_message + "<html><head></head><body>"
        # new_message = new_message + self.getLocalizeTime() + "\n"
        new_message = new_message + text
        new_message = new_message + "</body> </html>"

        file.close()

        for exchange in new_analysis:
            for market in new_analysis[exchange]:
                for indicator_type in new_analysis[exchange][market]:
                    if indicator_type == 'informants':
                        continue
                    for indicator in new_analysis[exchange][market][indicator_type]:
                        for index, analysis in enumerate(new_analysis[exchange][market][indicator_type][indicator]):
                            if analysis['result'].shape[0] == 0:
                                continue

                            values = dict()

                            if indicator_type == 'indicators':
                                for signal in analysis['config']['signal']:
                                    latest_result = analysis['result'].iloc[-1]
                                    if signal == 'kdj':
                                        values['k'] = analysis['result'].iloc[-1]['k']
                                        values['d'] = analysis['result'].iloc[-1]['d']
                                        values['j'] = analysis['result'].iloc[-1]['j']
                                        if isinstance(values['k'], float):
                                            values['k'] = format(values['k'], '.8f')
                                        if isinstance(values['d'], float):
                                            values['d'] = format(values['d'], '.8f')
                                        if isinstance(values['j'], float):
                                            values['j'] = format(values['j'], '.8f')
                                    else:
                                        values[signal] = analysis['result'].iloc[-1][signal]
                                        if isinstance(values[signal], float):
                                            values[signal] = format(values[signal], '.8f')

                            elif indicator_type == 'crossovers':
                                latest_result = analysis['result'].iloc[-1]

                                key_signal = '{}_{}'.format(
                                    analysis['config']['key_signal'],
                                    analysis['config']['key_indicator_index']
                                )

                                crossed_signal = '{}_{}'.format(
                                    analysis['config']['crossed_signal'],
                                    analysis['config']['crossed_indicator_index']
                                )

                                values[key_signal] = analysis['result'].iloc[-1][key_signal]
                                if isinstance(values[key_signal], float):
                                        values[key_signal] = format(values[key_signal], '.8f')

                                values[crossed_signal] = analysis['result'].iloc[-1][crossed_signal]
                                if isinstance(values[crossed_signal], float):
                                        values[crossed_signal] = format(values[crossed_signal], '.8f')

                            status = 'neutral'
                            if latest_result['is_hot']:
                                status = 'hot'
                            elif latest_result['is_cold']:
                                status = 'cold'

                            # Save status of indicator's new analysis
                            new_analysis[exchange][market][indicator_type][indicator][index]['status'] = status

                            if latest_result['is_hot'] or latest_result['is_cold']:
                                try:
                                    last_status = self.last_analysis[exchange][market][indicator_type][indicator][index]['status']
                                except:
                                    last_status = str()

                                should_alert = True
                                if analysis['config']['alert_frequency'] == 'once':
                                    if last_status == status:
                                        should_alert = False

                                if not analysis['config']['alert_enabled']:
                                    should_alert = False

                                if False and should_alert:
                                    base_currency, quote_currency = market.split('/')
                                    new_message += message_template.render(
                                        values=values,
                                        exchange=exchange,
                                        market=market,
                                        base_currency=base_currency,
                                        quote_currency=quote_currency,
                                        indicator=indicator,
                                        indicator_number=index,
                                        analysis=analysis,
                                        status=status,
                                        last_status=last_status
                                    )
        # Merge changes from new analysis into last analysis
        self.last_analysis = {**self.last_analysis, **new_analysis}
        return new_message, title
예제 #4
0
class Notifier():
    """Handles sending notifications via the configured notifiers
    """

    def __init__(self, notifier_config):
        """Initializes Notifier class

        Args:
            notifier_config (dict): A dictionary containing configuration for the notifications.
        """

        self.logger = structlog.get_logger()
        self.twilio_configured = self.__validate_required_config('twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']['receiver_number']
            )

        self.slack_configured = self.__validate_required_config('slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_webhook=notifier_config['slack']['required']['webhook']
            )

        self.gmail_configured = self.__validate_required_config('gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']['destination_emails']
            )

        self.integram_configured = self.__validate_required_config('integram', notifier_config)
        if self.integram_configured:
            self.integram_client = IntegramNotifier(
                url=notifier_config['integram']['required']['url']
            )


    def __validate_required_config(self, notifier, notifier_config):
        """Validate the required configuration items are present for a notifier.

        Args:
            notifier (str): The name of the notifier key in default-config.json
            notifier_config (dict): A dictionary containing configuration for the notifications.

        Returns:
            bool: Is the notifier configured?
        """

        notifier_configured = True
        for _, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured


    def notify_all(self, message):
        """Trigger a notification for all notification options.

        Args:
            message (str): The message to send.
        """

        self.notify_slack(message)
        self.notify_twilio(message)
        self.notify_gmail(message)
        self.notify_integram(message)


    def notify_slack(self, message):
        """Send a notification via the slack notifier

        Args:
            message (str): The message to send.
        """

        if self.slack_configured:
            self.slack_client.notify(message)


    def notify_twilio(self, message):
        """Send a notification via the twilio notifier

        Args:
            message (str): The message to send.
        """

        if self.twilio_configured:
            self.twilio_client.notify(message)


    def notify_gmail(self, message):
        """Send a notification via the gmail notifier

        Args:
            message (str): The message to send.
        """

        if self.gmail_configured:
            self.gmail_client.notify(message)

    def notify_integram(self, message):
        """Send a notification via the integram notifier

        Args:
            message (str): The message to send.
        """

        if self.integram_configured:
            self.integram_client.notify(message)
예제 #5
0
class Notifier():
    """Handles sending notifications via the configured notifiers
    """
    def __init__(self, notifier_config):
        """Initializes Notifier class

        Args:
            notifier_config (dict): A dictionary containing configuration for the notifications.
        """

        self.logger = structlog.get_logger()
        self.notifier_config = notifier_config
        self.last_analysis = dict()

        enabled_notifiers = list()
        self.logger = structlog.get_logger()
        self.twilio_configured = self._validate_required_config(
            'twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']
                ['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']
                ['receiver_number'])
            enabled_notifiers.append('twilio')

        self.discord_configured = self._validate_required_config(
            'discord', notifier_config)
        if self.discord_configured:
            self.discord_client = DiscordNotifier(
                webhook=notifier_config['discord']['required']['webhook'],
                username=notifier_config['discord']['required']['username'],
                avatar=notifier_config['discord']['optional']['avatar'])

        self.slack_configured = self._validate_required_config(
            'slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_webhook=notifier_config['slack']['required']['webhook'])
            enabled_notifiers.append('slack')

        self.gmail_configured = self._validate_required_config(
            'gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']
                ['destination_emails'])
            enabled_notifiers.append('gmail')

        self.telegram_configured = self._validate_required_config(
            'telegram', notifier_config)
        if self.telegram_configured:
            self.telegram_client = TelegramNotifier(
                token=notifier_config['telegram']['required']['token'],
                chat_id=notifier_config['telegram']['required']['chat_id'])
            enabled_notifiers.append('telegram')

        self.logger.info('enabled notifers: %s', enabled_notifiers)

    def notify_all(self, new_analysis):
        """Trigger a notification for all notification options.

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        self.notify_slack(new_analysis)
        self.notify_discord(new_analysis)
        self.notify_twilio(new_analysis)
        self.notify_gmail(new_analysis)
        self.notify_telegram(new_analysis)

    def notify_discord(self, new_analysis):
        """Send a notification via the discord notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.discord_configured:
            message = self._message_templater(
                new_analysis,
                self.notifier_config['discord']['optional']['template'])
            if message.strip():
                self.discord_client.notify(message)

    def notify_slack(self, new_analysis):
        """Send a notification via the slack notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.slack_configured:
            message = self._message_templater(
                new_analysis,
                self.notifier_config['slack']['optional']['template'])
            if message.strip():
                self.slack_client.notify(message)

    def notify_twilio(self, new_analysis):
        """Send a notification via the twilio notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.twilio_configured:
            message = self._message_templater(
                new_analysis,
                self.notifier_config['twilio']['optional']['template'])
            if message.strip():
                self.twilio_client.notify(message)

    def notify_gmail(self, new_analysis):
        """Send a notification via the gmail notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.gmail_configured:
            message = self._message_templater(
                new_analysis,
                self.notifier_config['gmail']['optional']['template'])
            if message.strip():
                self.gmail_client.notify(message)

    def notify_telegram(self, new_analysis):
        """Send a notification via the telegram notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.telegram_configured:
            message = self._message_templater(
                new_analysis,
                self.notifier_config['telegram']['optional']['template'])
            if message.strip():
                self.telegram_client.notify(message)

    def _validate_required_config(self, notifier, notifier_config):
        """Validate the required configuration items are present for a notifier.

        Args:
            notifier (str): The name of the notifier key in default-config.json
            notifier_config (dict): A dictionary containing configuration for the notifications.

        Returns:
            bool: Is the notifier configured?
        """

        notifier_configured = True
        for _, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured

    def _message_templater(self, new_analysis, template):
        """Creates a message from a user defined template

        Args:
            new_analysis (dict): A dictionary of data related to the analysis to send a message about.
            template (str): A Jinja formatted message template.

        Returns:
            str: The templated messages for the notifier.
        """

        if not self.last_analysis:
            self.last_analysis = new_analysis

        message_template = Template(template)
        new_message = str()
        for exchange in new_analysis:
            for market in new_analysis[exchange]:
                for analyzer in new_analysis[exchange][market]:
                    for index, indicator in enumerate(
                            new_analysis[exchange][market][analyzer]):
                        formatted_values = list()
                        for value in indicator['result']['values']:
                            if isinstance(value, float):
                                formatted_values.append(format(value, '.8f'))
                            else:
                                formatted_values.append(value)
                        formatted_string = '/'.join(formatted_values)

                        status = 'neutral'
                        if indicator['result']['is_hot']:
                            status = 'hot'
                        elif indicator['result']['is_cold']:
                            status = 'cold'

                        if indicator['result']['is_hot'] or indicator[
                                'result']['is_cold']:
                            try:
                                last_status = self.last_analysis[exchange][
                                    market][analyzer][index]['status']
                            except:
                                last_status = str()

                            should_alert = True
                            if indicator['config'][
                                    'alert_frequency'] == 'once':
                                if last_status == status:
                                    should_alert = False

                            if not indicator['config']['alert_enabled']:
                                should_alert = False

                            if should_alert:
                                new_analysis[exchange][market][analyzer][
                                    index]['status'] = status
                                new_message += message_template.render(
                                    exchange=exchange,
                                    market=market,
                                    analyzer=analyzer,
                                    analyzer_number=index,
                                    status=status,
                                    string_values=formatted_string,
                                    raw_indicator=indicator)

        # Merge changes from new analysis into last analysis
        self.last_analysis = {**self.last_analysis, **new_analysis}
        return new_message
class Notifier():
    """
    Handles sending notifications via the configured notifiers
    """
    def __init__(self, notifier_config):
        self.logger = structlog.get_logger()
        self.twilio_configured = self.__validate_required_config('twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']['receiver_number']
            )

        self.slack_configured = self.__validate_required_config('slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_key=notifier_config['slack']['required']['key'],
                slack_channel=notifier_config['slack']['required']['channel']
            )

        self.gmail_configured = self.__validate_required_config('gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']['destination_emails']
            )

        self.integram_configured = self.__validate_required_config('integram', notifier_config)
        if self.integram_configured:
            self.integram_client = IntegramNotifier(
                url=notifier_config['integram']['required']['url']
            )

    def __validate_required_config(self, notifier, notifier_config):
        notifier_configured = True
        for opt, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured

    def notify_all(self, message):
        """
        Triggers the notification with all configured notifiers
        """
        self.notify_slack(message)
        self.notify_twilio(message)
        self.notify_gmail(message)
        self.notify_integram(message)

    def notify_slack(self, message):
        """
        Triggers the notification to slack
        """
        if self.slack_configured:
            self.slack_client.notify(message)


    def notify_twilio(self, message):
        """
        Triggers the notification to slack
        """
        if self.twilio_configured:
            self.twilio_client.notify(message)

    def notify_gmail(self, message):
        """
        Triggers the notification via email using gmail
        """
        if self.gmail_configured:
            self.gmail_client.notify(message)

    def notify_integram(self, message):
        """
        Triggers the notification via email using gmail
        """
        if self.integram_configured:
            self.integram_client.notify(message)
예제 #7
0
class Notifier():
    """
    Handles sending notifications via the configured notifiers
    """
    def __init__(self, notifier_config):
        self.logger = structlog.get_logger()
        self.twilio_configured = self.__validate_required_config(
            'twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']
                ['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']
                ['receiver_number'])

        self.slack_configured = self.__validate_required_config(
            'slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_key=notifier_config['slack']['required']['key'],
                slack_channel=notifier_config['slack']['required']['channel'])

        self.gmail_configured = self.__validate_required_config(
            'gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']
                ['destination_emails'])

        self.integram_configured = self.__validate_required_config(
            'integram', notifier_config)
        if self.integram_configured:
            self.integram_client = IntegramNotifier(
                url=notifier_config['integram']['required']['url'])

    def __validate_required_config(self, notifier, notifier_config):
        notifier_configured = True
        for opt, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured

    def notify_all(self, message):
        """
        Triggers the notification with all configured notifiers
        """
        self.notify_slack(message)
        self.notify_twilio(message)
        self.notify_gmail(message)
        self.notify_integram(message)

    def notify_slack(self, message):
        """
        Triggers the notification to slack
        """
        if self.slack_configured:
            self.slack_client.notify(message)

    def notify_twilio(self, message):
        """
        Triggers the notification to slack
        """
        if self.twilio_configured:
            self.twilio_client.notify(message)

    def notify_gmail(self, message):
        """
        Triggers the notification via email using gmail
        """
        if self.gmail_configured:
            self.gmail_client.notify(message)

    def notify_integram(self, message):
        """
        Triggers the notification via email using gmail
        """
        if self.integram_configured:
            self.integram_client.notify(message)
예제 #8
0
class Notifier():
    """Handles sending notifications via the configured notifiers
    """
    def __init__(self, notifier_config, config):
        """Initializes Notifier class

        Args:
            notifier_config (dict): A dictionary containing configuration for the notifications.
        """

        self.config = config
        self.logger = structlog.get_logger()
        self.notifier_config = notifier_config
        self.last_analysis = dict()

        self.hotImoji = emojize(":hotsprings: ", use_aliases=True)
        self.coldImoji = emojize(":snowman: ", use_aliases=True)
        self.primaryImoji = emojize(":ok_hand: ", use_aliases=True)

        enabled_notifiers = list()
        self.logger = structlog.get_logger()
        self.twilio_configured = self._validate_required_config(
            'twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']
                ['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']
                ['receiver_number'])
            enabled_notifiers.append('twilio')

        self.discord_configured = self._validate_required_config(
            'discord', notifier_config)
        if self.discord_configured:
            self.discord_client = DiscordNotifier(
                webhook=notifier_config['discord']['required']['webhook'],
                username=notifier_config['discord']['required']['username'],
                avatar=notifier_config['discord']['optional']['avatar'])
            enabled_notifiers.append('discord')

        self.slack_configured = self._validate_required_config(
            'slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_webhook=notifier_config['slack']['required']['webhook'])
            enabled_notifiers.append('slack')

        self.gmail_configured = self._validate_required_config(
            'gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']
                ['destination_emails'])
            enabled_notifiers.append('gmail')

        self.telegram_configured = self._validate_required_config(
            'telegram', notifier_config)
        if self.telegram_configured:
            self.telegram_client = TelegramNotifier(
                token=notifier_config['telegram']['required']['token'],
                chat_id=notifier_config['telegram']['required']['chat_id'],
                parse_mode=notifier_config['telegram']['optional']
                ['parse_mode'])
            enabled_notifiers.append('telegram')

        self.webhook_configured = self._validate_required_config(
            'webhook', notifier_config)
        if self.webhook_configured:
            self.webhook_client = WebhookNotifier(
                url=notifier_config['webhook']['required']['url'],
                username=notifier_config['webhook']['optional']['username'],
                password=notifier_config['webhook']['optional']['password'])
            enabled_notifiers.append('webhook')

        self.stdout_configured = self._validate_required_config(
            'stdout', notifier_config)
        if self.stdout_configured:
            self.stdout_client = StdoutNotifier()
            enabled_notifiers.append('stdout')

        self.logger.info('enabled notifers: %s', enabled_notifiers)

    def notify_all(self, new_analysis):
        """Trigger a notification for all notification options.

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        self.notify_slack(new_analysis)
        self.notify_discord(new_analysis)
        self.notify_twilio(new_analysis)
        self.notify_gmail(new_analysis)
        self.notify_telegram(new_analysis)
        self.notify_webhook(new_analysis)
        self.notify_stdout(new_analysis)

    def notify_all_new(self, new_analysis):
        """Trigger a notification for all notification options.

        Args:
            new_analysis (dict): The new_analysis to send.
            :param config:
        """

        self._indicator_message_templater(
            new_analysis,
            self.notifier_config['slack']['optional']['template'])
        print()

    def notify_discord(self, new_analysis):
        """Send a notification via the discord notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.discord_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['discord']['optional']['template'])
            if message.strip():
                self.discord_client.notify(message)

    def notify_slack(self, new_analysis):
        """Send a notification via the slack notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.slack_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['slack']['optional']['template'])
            if message.strip():
                self.slack_client.notify(message)

    def notify_twilio(self, new_analysis):
        """Send a notification via the twilio notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.twilio_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['twilio']['optional']['template'])
            if message.strip():
                self.twilio_client.notify(message)

    def notify_gmail(self, new_analysis):
        """Send a notification via the gmail notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.gmail_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['gmail']['optional']['template'])
            if message.strip():
                self.gmail_client.notify(message)

    def notify_telegram(self, new_analysis):
        """Send a notification via the telegram notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.telegram_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['telegram']['optional']['template'])
            if message.strip():
                self.telegram_client.notify(message)

    def notify_webhook(self, new_analysis):
        """Send a notification via the webhook notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.webhook_configured:
            for exchange in new_analysis:
                for market in new_analysis[exchange]:
                    for indicator_type in new_analysis[exchange][market]:
                        for indicator in new_analysis[exchange][market][
                                indicator_type]:
                            for index, analysis in enumerate(
                                    new_analysis[exchange][market]
                                [indicator_type][indicator]):
                                analysis_dict = analysis['result'].to_dict(
                                    orient='records')
                                if analysis_dict:
                                    new_analysis[exchange][market][
                                        indicator_type][indicator][
                                            index] = analysis_dict[-1]
                                else:
                                    new_analysis[exchange][market][
                                        indicator_type][indicator][index] = ''

            self.webhook_client.notify(new_analysis)

    def notify_stdout(self, new_analysis):
        """Send a notification via the stdout notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.stdout_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['stdout']['optional']['template'])
            if message.strip():
                self.stdout_client.notify(message)

    def _validate_required_config(self, notifier, notifier_config):
        """Validate the required configuration items are present for a notifier.

        Args:
            notifier (str): The name of the notifier key in default-config.json
            notifier_config (dict): A dictionary containing configuration for the notifications.

        Returns:
            bool: Is the notifier configured?
        """

        notifier_configured = True
        for _, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured

    def _indicator_message_templater(self, new_analysis, template):
        """Creates a message from a user defined template

        Args:
            new_analysis (dict): A dictionary of data related to the analysis to send a message about.
            template (str): A Jinja formatted message template.

        Returns:
            str: The templated messages for the notifier.
        """

        if not self.last_analysis:
            self.last_analysis = new_analysis

        customCode = True

        message_template = Template(template)
        new_message = str()
        for exchange in new_analysis:
            for market in new_analysis[exchange]:

                base_currency, quote_currency = market.split('/')

                sendNotification = False
                allIndicatorData = {}
                primaryIndicators = []
                crossedData = {}
                informatntData = {}
                couldCount = 0
                hotCount = 0

                allIndicatorData["name"] = base_currency + quote_currency
                allIndicatorData["exchange"] = exchange
                allIndicatorData["crossed"] = {}
                allIndicatorData["informants"] = {}

                for indicator_type in new_analysis[exchange][market]:

                    print(indicator_type)

                    for indicator in new_analysis[exchange][market][
                            indicator_type]:

                        for index, analysis in enumerate(
                                new_analysis[exchange][market][indicator_type]
                            [indicator]):
                            if analysis['result'].shape[0] == 0:
                                continue

                            values = dict()
                            jsonIndicator = {}

                            if indicator_type == 'informants':
                                for signal in analysis['config']['signal']:
                                    latest_result = analysis['result'].iloc[-1]

                                    values[signal] = analysis['result'].iloc[
                                        -1][signal]
                                    if isinstance(values[signal], float):
                                        values[signal] = format(
                                            values[signal], '.8f')

                                informent_result = {
                                    "result": values,
                                    "config": analysis['config']
                                }
                                informatntData[indicator] = informent_result
                                continue

                            elif indicator_type == 'indicators':
                                for signal in analysis['config']['signal']:
                                    latest_result = analysis['result'].iloc[-1]

                                    values[signal] = analysis['result'].iloc[
                                        -1][signal]
                                    if isinstance(values[signal], float):
                                        values[signal] = format(
                                            values[signal], '.8f')

                            elif indicator_type == 'crossovers':
                                latest_result = analysis['result'].iloc[-1]
                                crosedIndicator = True
                                allIndicatorData["crosed"] = crosedIndicator

                                key_signal = '{}_{}'.format(
                                    analysis['config']['key_signal'],
                                    analysis['config']['key_indicator_index'])

                                crossed_signal = '{}_{}'.format(
                                    analysis['config']['crossed_signal'],
                                    analysis['config']
                                    ['crossed_indicator_index'])

                                values[key_signal] = analysis['result'].iloc[
                                    -1][key_signal]
                                if isinstance(values[key_signal], float):
                                    values[key_signal] = format(
                                        values[key_signal], '.8f')

                                values[crossed_signal] = analysis[
                                    'result'].iloc[-1][crossed_signal]
                                if isinstance(values[crossed_signal], float):
                                    values[crossed_signal] = format(
                                        values[crossed_signal], '.8f')

                                dataCros = {
                                    "name":
                                    key_signal[0:-2],
                                    "key_value":
                                    values[key_signal],
                                    "crossed_value":
                                    values[crossed_signal],
                                    "is_hot":
                                    True if latest_result['is_hot'] else False,
                                    "is_cold":
                                    True if latest_result['is_cold'] else False
                                }
                                dataCros["key_config"] = \
                                    new_analysis[exchange][market]['informants'][dataCros["name"]][0]["config"]
                                dataCros["crossed_config"] = \
                                    new_analysis[exchange][market]['informants'][dataCros["name"]][1]["config"]

                                crossedData[dataCros["name"]] = dataCros
                                continue

                            status = 'neutral'
                            if latest_result['is_hot']:
                                status = 'hot'
                                if indicator != "ichimoku" and indicator_type != "informants" and indicator_type != "crossovers":
                                    hotCount += 1
                            elif latest_result['is_cold']:
                                status = 'cold'
                                if indicator != "ichimoku" and indicator_type != "informants" and indicator_type != "crossovers":
                                    couldCount += 1

                            # Save status of indicator's new analysis
                            new_analysis[exchange][market][indicator_type][
                                indicator][index]['status'] = status

                            if latest_result['is_hot'] or latest_result[
                                    'is_cold'] or customCode:
                                try:
                                    last_status = \
                                        self.last_analysis[exchange][market][indicator_type][indicator][index]['status']
                                except:
                                    last_status = str()

                                should_alert = True
                                try:
                                    if analysis['config'][
                                            'alert_frequency'] == 'once':
                                        if last_status == status:
                                            should_alert = False

                                    if not analysis['config']['alert_enabled']:
                                        should_alert = False
                                except Exception as ex:
                                    print(ex)

                                jsonIndicator["values"] = values
                                jsonIndicator["exchange"] = exchange
                                jsonIndicator["market"] = market
                                jsonIndicator["base_currency"] = base_currency
                                jsonIndicator[
                                    "quote_currency"] = quote_currency
                                jsonIndicator["indicator"] = indicator
                                jsonIndicator["indicator_number"] = index

                                jsonIndicator["config"] = analysis['config']

                                jsonIndicator["status"] = status
                                jsonIndicator["last_status"] = last_status

                                if (latest_result['is_hot']
                                        or latest_result['is_cold']
                                    ) and should_alert:
                                    sendNotification = True

                                    primaryIndicators.append(indicator)

                                    new_message += message_template.render(
                                        values=values,
                                        exchange=exchange,
                                        market=market,
                                        base_currency=base_currency,
                                        quote_currency=quote_currency,
                                        indicator=indicator,
                                        indicator_number=index,
                                        analysis=analysis,
                                        status=status,
                                        last_status=last_status)

                                    if len(primaryIndicators) == 0:
                                        jsonIndicator["primary"] = True
                                    else:
                                        jsonIndicator["primary"] = False

                                else:
                                    jsonIndicator["primary"] = False

                                allIndicatorData[indicator] = jsonIndicator
                myset = self.config.settings
                if sendNotification == True and len(primaryIndicators) >= 2 and \
                        (hotCount >= myset["max_hot_notification"] or couldCount >= myset["max_cold_notification"]):
                    allIndicatorData["crossed"] = crossedData
                    allIndicatorData["informants"] = informatntData
                    print("Hot : " + str(hotCount) + "  cold : " +
                          str(couldCount))
                    self.notify_custom_telegram(allIndicatorData)
                    # exit()

        # Merge changes from new analysis into last analysis
        self.last_analysis = {**self.last_analysis, **new_analysis}
        return new_message

    def bollinger_bands_state_calculation(self, upperband, middleband,
                                          lowerband, v_close):

        isInTop = v_close > middleband
        isDown = v_close < middleband

        isCold = isHot = False

        if isInTop:
            diff_close = upperband - v_close
            diff_med = v_close - middleband

            isCold = diff_close < diff_med

        elif isDown:
            diff_close = v_close - lowerband
            diff_med = middleband - v_close

            isHot = diff_close < diff_med

        if isCold:
            return "cold"
        elif isHot:
            return "hot"
        else:
            return "neutral"

    def moving_avg_status(self, key_value, crossed_value, is_hot, key_config,
                          crossed_config):

        if is_hot:
            message = str(key_config["period_count"]) + " up " + str(
                crossed_config["period_count"])
        else:
            message = str(key_config["period_count"]) + " down " + str(
                crossed_config["period_count"])

        message += self.status_generator(False, "hot" if is_hot else "cold")
        # message += "\n \t" + key_config["candle_period"] + "/" + str(key_config["period_count"]) + " ====> " + str(
        # key_value) message += "\n \t" + crossed_config["candle_period"] + "/" + str(crossed_config["period_count"])
        #  + " ====> " + str(crossed_value)

        return message

    def status_generator(self, primary, state):
        message = str()

        if primary and (state == "cold" or state == "hot"):
            message += self.primaryImoji
        elif state == "cold":
            message += self.coldImoji
        elif state == "hot":
            message += self.hotImoji

        message += "\n"
        return " " + message

    def notify_custom_telegram(self, objectsData):
        """Send a notification via the telegram notifier

        Args:
            objectsData (dict): The new_analysis to send.
        """

        # print(json.dumps(objectsData))

        try:
            message = str()

            # Crypto Name
            message = emojize(
                ":gem:", use_aliases=True) + " #" + objectsData["name"] + "\n"

            # Market Name
            message += self.config.settings[
                "period_data"] + " / " + objectsData["exchange"] + "\n"
            message += "#Price -- " + objectsData["informants"]["ohlcv"][
                "result"]["close"] + " BTC \n\n"

            # MFI
            mfi = objectsData["mfi"]
            message += "MFI - is " + mfi["status"] + " (" + mfi["values"][
                "mfi"] + ") "
            message += self.status_generator(mfi["primary"], mfi["status"])

            # RSI
            rsi = objectsData["rsi"]
            message += "RSI - is " + rsi["status"] + " (" + rsi["values"][
                "rsi"] + ")"
            message += self.status_generator(rsi["primary"], rsi["status"])

            # STOCH_RSI
            stock_rsi = objectsData["stoch_rsi"]
            message += "STOCH_RSI - is " + stock_rsi[
                "status"] + " (" + stock_rsi["values"]["stoch_rsi"] + ")"
            message += self.status_generator(stock_rsi["primary"],
                                             stock_rsi["status"])

            # MACD
            macd = objectsData["macd"]
            message += "MACD - is " + macd["status"] + " (" + macd["values"][
                "macd"] + ")"
            message += self.status_generator(macd["primary"], macd["status"])

            # Bollinder bands
            bb_result = objectsData["informants"]["bollinger_bands"]["result"]
            bollinger_bands_state = self.bollinger_bands_state_calculation(
                float(bb_result["upperband"]), float(bb_result["middleband"]),
                float(bb_result["lowerband"]),
                float(objectsData["informants"]["ohlcv"]["result"]["close"]))
            message += "Bollinger Bands - is " + bollinger_bands_state
            message += self.status_generator(False, bollinger_bands_state)
            # message += " \t(upperband ===> " + bb_result["upperband"] + ") \n"
            # message += " \t(middleband ===> " + bb_result["middleband"] + ") \n"
            # message += " \t(lowerband ===> " + bb_result["lowerband"] + ")"

            # EMA Crossed
            ema = objectsData["crossed"]["ema"]
            ema_key_config = ema["key_config"]
            ema_crossed_config = ema["crossed_config"]
            mva_stat = self.moving_avg_status(ema["key_value"],
                                              ema["crossed_value"],
                                              bool(ema["is_hot"]),
                                              ema_key_config,
                                              ema_crossed_config)
            message += "Moving Average (ema) " + mva_stat

            # ICHIMOKU
            ichimoku = objectsData["ichimoku"]
            message += "Ichimoku Cloud - is " + ichimoku["status"]
            message += self.status_generator(ichimoku["primary"],
                                             ichimoku["status"])

            if self.telegram_configured:
                self.telegram_client.notify(message)

            print(message)

        except Exception as ex:
            print(ex)
예제 #9
0
class Notifier():
    """Handles sending notifications via the configured notifiers
    """

    def __init__(self, notifier_config):
        """Initializes Notifier class

        Args:
            notifier_config (dict): A dictionary containing configuration for the notifications.
        """

        self.logger = structlog.get_logger()
        self.notifier_config = notifier_config
        self.last_analysis = dict()

        enabled_notifiers = list()
        self.logger = structlog.get_logger()
        self.twilio_configured = self._validate_required_config('twilio', notifier_config)
        if self.twilio_configured:
            self.twilio_client = TwilioNotifier(
                twilio_key=notifier_config['twilio']['required']['key'],
                twilio_secret=notifier_config['twilio']['required']['secret'],
                twilio_sender_number=notifier_config['twilio']['required']['sender_number'],
                twilio_receiver_number=notifier_config['twilio']['required']['receiver_number']
            )
            enabled_notifiers.append('twilio')

        self.discord_configured = self._validate_required_config('discord', notifier_config)
        if self.discord_configured:
            self.discord_client = DiscordNotifier(
                webhook=notifier_config['discord']['required']['webhook'],
                username=notifier_config['discord']['required']['username'],
                avatar=notifier_config['discord']['optional']['avatar']
            )
            enabled_notifiers.append('discord')

        self.slack_configured = self._validate_required_config('slack', notifier_config)
        if self.slack_configured:
            self.slack_client = SlackNotifier(
                slack_webhook=notifier_config['slack']['required']['webhook']
            )
            enabled_notifiers.append('slack')

        self.gmail_configured = self._validate_required_config('gmail', notifier_config)
        if self.gmail_configured:
            self.gmail_client = GmailNotifier(
                username=notifier_config['gmail']['required']['username'],
                password=notifier_config['gmail']['required']['password'],
                destination_addresses=notifier_config['gmail']['required']['destination_emails']
            )
            enabled_notifiers.append('gmail')

        self.telegram_configured = self._validate_required_config('telegram', notifier_config)
        if self.telegram_configured:
            self.telegram_client = TelegramNotifier(
                token=notifier_config['telegram']['required']['token'],
                chat_id=notifier_config['telegram']['required']['chat_id'],
                parse_mode=notifier_config['telegram']['optional']['parse_mode']
            )
            enabled_notifiers.append('telegram')

        self.webhook_configured = self._validate_required_config('webhook', notifier_config)
        if self.webhook_configured:
            self.webhook_client = WebhookNotifier(
                url=notifier_config['webhook']['required']['url'],
                username=notifier_config['webhook']['optional']['username'],
                password=notifier_config['webhook']['optional']['password']
            )
            enabled_notifiers.append('webhook')

        self.stdout_configured = self._validate_required_config('stdout', notifier_config)
        if self.stdout_configured:
            self.stdout_client = StdoutNotifier()
            enabled_notifiers.append('stdout')

        self.logger.info('enabled notifers: %s', enabled_notifiers)


    def notify_all(self, new_analysis):
        """Trigger a notification for all notification options.

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        self.notify_slack(new_analysis)
        self.notify_discord(new_analysis)
        self.notify_twilio(new_analysis)
        self.notify_gmail(new_analysis)
        self.notify_telegram(new_analysis)
        self.notify_webhook(new_analysis)
        self.notify_stdout(new_analysis)

    def notify_discord(self, new_analysis):
        """Send a notification via the discord notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.discord_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['discord']['optional']['template']
            )
            if message.strip():
                self.discord_client.notify(message)


    def notify_slack(self, new_analysis):
        """Send a notification via the slack notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.slack_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['slack']['optional']['template']
            )
            if message.strip():
                self.slack_client.notify(message)


    def notify_twilio(self, new_analysis):
        """Send a notification via the twilio notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.twilio_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['twilio']['optional']['template']
            )
            if message.strip():
                self.twilio_client.notify(message)


    def notify_gmail(self, new_analysis):
        """Send a notification via the gmail notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.gmail_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['gmail']['optional']['template']
            )
            if message.strip():
                self.gmail_client.notify(message)


    def notify_telegram(self, new_analysis):
        """Send a notification via the telegram notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.telegram_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['telegram']['optional']['template']
            )
            if message.strip():
                self.telegram_client.notify(message)


    def notify_webhook(self, new_analysis):
        """Send a notification via the webhook notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.webhook_configured:
            for exchange in new_analysis:
                for market in new_analysis[exchange]:
                    for indicator_type in new_analysis[exchange][market]:
                        for indicator in new_analysis[exchange][market][indicator_type]:
                            for index, analysis in enumerate(new_analysis[exchange][market][indicator_type][indicator]):
                                analysis_dict = analysis['result'].to_dict(orient='records')
                                if analysis_dict:
                                    new_analysis[exchange][market][indicator_type][indicator][index] = analysis_dict[-1]
                                else:
                                    new_analysis[exchange][market][indicator_type][indicator][index] = ''

            self.webhook_client.notify(new_analysis)

    def notify_stdout(self, new_analysis):
        """Send a notification via the stdout notifier

        Args:
            new_analysis (dict): The new_analysis to send.
        """

        if self.stdout_configured:
            message = self._indicator_message_templater(
                new_analysis,
                self.notifier_config['stdout']['optional']['template']
            )
            if message.strip():
                self.stdout_client.notify(message)

    def _validate_required_config(self, notifier, notifier_config):
        """Validate the required configuration items are present for a notifier.

        Args:
            notifier (str): The name of the notifier key in default-config.json
            notifier_config (dict): A dictionary containing configuration for the notifications.

        Returns:
            bool: Is the notifier configured?
        """

        notifier_configured = True
        for _, val in notifier_config[notifier]['required'].items():
            if not val:
                notifier_configured = False
        return notifier_configured


    def _indicator_message_templater(self, new_analysis, template):
        """Creates a message from a user defined template

        Args:
            new_analysis (dict): A dictionary of data related to the analysis to send a message about.
            template (str): A Jinja formatted message template.

        Returns:
            str: The templated messages for the notifier.
        """

        if not self.last_analysis:
            self.last_analysis = new_analysis

        message_template = Template(template)
        new_message = str()
        for exchange in new_analysis:
            for market in new_analysis[exchange]:
                for indicator_type in new_analysis[exchange][market]:
                    if indicator_type == 'informants':
                        continue
                    for indicator in new_analysis[exchange][market][indicator_type]:
                        for index, analysis in enumerate(new_analysis[exchange][market][indicator_type][indicator]):
                            if analysis['result'].shape[0] == 0:
                                continue

                            values = dict()

                            if indicator_type == 'indicators':
                                for signal in analysis['config']['signal']:
                                    latest_result = analysis['result'].iloc[-1]

                                    values[signal] = analysis['result'].iloc[-1][signal]
                                    if isinstance(values[signal], float):
                                        values[signal] = format(values[signal], '.8f')
                            elif indicator_type == 'crossovers':
                                latest_result = analysis['result'].iloc[-1]

                                key_signal = '{}_{}'.format(
                                    analysis['config']['key_signal'],
                                    analysis['config']['key_indicator_index']
                                )

                                crossed_signal = '{}_{}'.format(
                                    analysis['config']['crossed_signal'],
                                    analysis['config']['crossed_indicator_index']
                                )

                                values[key_signal] = analysis['result'].iloc[-1][key_signal]
                                if isinstance(values[key_signal], float):
                                        values[key_signal] = format(values[key_signal], '.8f')

                                values[crossed_signal] = analysis['result'].iloc[-1][crossed_signal]
                                if isinstance(values[crossed_signal], float):
                                        values[crossed_signal] = format(values[crossed_signal], '.8f')

                            status = 'neutral'
                            if latest_result['is_hot']:
                                status = 'hot'
                            elif latest_result['is_cold']:
                                status = 'cold'

                            # Save status of indicator's new analysis
                            new_analysis[exchange][market][indicator_type][indicator][index]['status'] = status

                            if latest_result['is_hot'] or latest_result['is_cold']:
                                try:
                                    last_status = self.last_analysis[exchange][market][indicator_type][indicator][index]['status']
                                except:
                                    last_status = str()

                                should_alert = True
                                if analysis['config']['alert_frequency'] == 'once':
                                    if last_status == status:
                                        should_alert = False

                                if not analysis['config']['alert_enabled']:
                                    should_alert = False

                                if should_alert:
                                    base_currency, quote_currency = market.split('/')
                                    new_message += message_template.render(
                                        values=values,
                                        exchange=exchange,
                                        market=market,
                                        base_currency=base_currency,
                                        quote_currency=quote_currency,
                                        indicator=indicator,
                                        indicator_number=index,
                                        analysis=analysis,
                                        status=status,
                                        last_status=last_status
                                    )

        # Merge changes from new analysis into last analysis
        self.last_analysis = {**self.last_analysis, **new_analysis}
        return new_message