From 069cc314466bb6ddba78dc36c41aa1e7111f222c Mon Sep 17 00:00:00 2001 From: xinshen Date: Sat, 1 Aug 2020 23:46:53 +0200 Subject: [PATCH 01/46] Allow conditional notifications --- app/app.py | 2 +- app/conf.py | 5 ++ app/notification.py | 67 +++++++++++++++++++++---- docs/config.md | 119 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 181 insertions(+), 12 deletions(-) diff --git a/app/app.py b/app/app.py index 9b67c931..1dafc542 100644 --- a/app/app.py +++ b/app/app.py @@ -50,7 +50,7 @@ def main(): key: market_data[exchange][key] for key in chunk} notifier = Notifier( - config.notifiers, config.indicators, market_data_chunk) + config.notifiers, config.indicators, config.conditionals, market_data_chunk) behaviour = Behaviour(config, exchange_interface, notifier) workerName = "Worker-{}".format(num) diff --git a/app/conf.py b/app/conf.py index fcbd2403..788e3c1c 100644 --- a/app/conf.py +++ b/app/conf.py @@ -58,6 +58,11 @@ def __init__(self): self.exchanges = user_config['exchanges'] else: self.exchanges = dict() + + if 'conditionals' in user_config: + self.conditionals = user_config['conditionals'] + else: + self.conditionals = None for exchange in ccxt.exchanges: if exchange not in self.exchanges: diff --git a/app/notification.py b/app/notification.py index 91b9c8b3..59746e55 100644 --- a/app/notification.py +++ b/app/notification.py @@ -10,9 +10,8 @@ from time import sleep import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt import matplotlib.dates as mdates +import matplotlib.pyplot as plt import matplotlib.ticker as mticker import numpy as np import pandas as pd @@ -36,12 +35,14 @@ from notifiers.twilio_client import TwilioNotifier from notifiers.webhook_client import WebhookNotifier +matplotlib.use('Agg') + class Notifier(IndicatorUtils): """Handles sending notifications via the configured notifiers """ - def __init__(self, notifier_config, indicator_config, market_data): + def __init__(self, notifier_config, indicator_config, conditional_config, market_data): """Initializes Notifier class Args: @@ -51,6 +52,7 @@ def __init__(self, notifier_config, indicator_config, market_data): self.logger = structlog.get_logger() self.notifier_config = notifier_config self.indicator_config = indicator_config + self.conditional_config = conditional_config self.market_data = market_data self.last_analysis = dict() self.enable_charts = False @@ -147,13 +149,58 @@ def notify_all(self, new_analysis): 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.conditional_config: + self.notify_conditional(exchange, market_pair, _messages) + else: + for candle_period in _messages: + if not isinstance(_messages[candle_period], list) or len(_messages[candle_period]) == 0: + continue + + self.notify_all_messages( + exchange, market_pair, candle_period, _messages[candle_period]) + sleep(4) + + def notify_conditional(self, exchange, market_pair, messages): + status = ['hot', 'cold'] + + for condition in self.conditional_config: + x = 0 + nb_conditions = 0 + new_message = {} + new_message['values'] = [] + new_message['indicator'] = [] + for candle_period in messages: + if messages[candle_period]: + new_message['exchange'] = messages[candle_period][0]['exchange'] + new_message['market'] = messages[candle_period][0]['market'] + new_message['base_currency'] = messages[candle_period][0]['base_currency'] + new_message['quote_currency'] = messages[candle_period][0]['quote_currency'] + for msg in messages[candle_period]: + for stat in status: + if msg['status'] == stat: + try: + for indicator in condition[stat]: + if msg['indicator'] in indicator.keys(): + if indicator[msg['indicator']] == msg['indicator_number']: + new_message['values'].append( + msg['values']) + new_message['indicator'].append( + msg['indicator']) + x += 1 + except: + pass + for stat in status: + try: + nb_conditions += len(condition[stat]) + except: + pass - self.notify_all_messages( - exchange, market_pair, candle_period, _messages[candle_period]) - sleep(4) + if x == nb_conditions and x != 0: + new_message['status'] = condition['label'] + self.notify_discord([new_message]) + self.notify_webhook([new_message], None) + self.notify_telegram([new_message], None) + self.notify_stdout([new_message]) def notify_all_messages(self, exchange, market_pair, candle_period, messages): chart_file = None @@ -682,7 +729,7 @@ def create_charts(self, messages): except Exception as e: self.logger.info( 'Error creating chart for %s %s', market_pair, candle_period) - self.logger.exception(e) + self.logger.exception(e) def create_chart(self, exchange, market_pair, candle_period, candles_data): diff --git a/docs/config.md b/docs/config.md index 24a1facd..1f72c90a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -508,8 +508,125 @@ crossovers: crossed_signal: sma ``` +# 8) Conditionals -# 8) Examples +It's allowing you to receive notifications, only if one or more conditions are respected. + +Use case examples: +- Receive a buy notification if rsi is cold and bollinger is hot and aroon is cold. +- Receive a sell notification if 1d rsi is hot and 1h rsi is hot and bollinger is cold and aroon is hot. + +**You will not receive notifications if all conditions, of one conditionnal, are not met.** + +## Example + +```yml +settings: + log_level: INFO + update_interval: 120 + start_worker_interval: 2 + market_data_chunk_size: 1 + timezone: Europe/Paris + +exchanges: + kraken: + required: + enabled: true + all_pairs: + - USD + +indicators: + rsi: + - enabled: true + alert_enabled: true + alert_frequency: always + signal: + - rsi + hot: 30 + cold: 70 + candle_period: 1h + period_count: 14 + - enabled: true + alert_enabled: true + alert_frequency: always + signal: + - rsi + hot: 40 + cold: 60 + candle_period: 1d + period_count: 14 + bollinger: + - enabled: true + candle_period: 1h + alert_enabled: true + alert_frequency: always + period_count: 25 + std_dev: 2.5 + signal: + - low_band + - close + - up_band + mute_cold: false + - enabled: true + candle_period: 1d + alert_enabled: true + alert_frequency: always + period_count: 25 + std_dev: 2.5 + signal: + - low_band + - close + - up_band + mute_cold: false + aroon_oscillator: + - enabled: true + alert_enabled: true + alert_frequency: always + sma_vol_period: 50 + period_count: 14 + signal: + - aroon + candle_period: 1h + +conditionals: + - label: "Signal to buy" + hot: + - rsi: 0 + - rsi: 1 + cold: + - bollinger: 0 + - label: "Signal to buy" + hot: + - rsi: 1 + - label: "Signal to sell" + cold: + - rsi: 1 + - rsi: 0 + hot: + - aroon_oscillator: 0 + +notifiers: + telegram: + required: + token: X + chat_id: Y + optional: + parse_mode: html + template: "[{{market}}] {{indicator}} {{status}} {{values}} {{ '\n' -}}" +``` + +## Template value available + - values + - indicator + - exchange + - market + - base_currency + - quote_currency + - status + + The `status` will be the string set in `label`. + +# 9) Examples Putting it all together an example config.yml might look like the config below if you want to use the default settings with bittrex ```yml From 2f3a9e1b206adb90ccecbb839e1379f03281c5c0 Mon Sep 17 00:00:00 2001 From: xinshen Date: Sun, 2 Aug 2020 02:03:09 +0200 Subject: [PATCH 02/46] Correct DatetimeIndex error due to new version of pandas --- app/analyzers/indicators/ichimoku.py | 2 +- app/config_conditionnals.yml | 144 +++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 app/config_conditionnals.yml diff --git a/app/analyzers/indicators/ichimoku.py b/app/analyzers/indicators/ichimoku.py index 6ec5bd57..7fb5abf2 100644 --- a/app/analyzers/indicators/ichimoku.py +++ b/app/analyzers/indicators/ichimoku.py @@ -69,7 +69,7 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa cloud_displacement = 26 last_time = dataframe.index[-1] timedelta = dataframe.index[1] - dataframe.index[0] - newindex = pandas.DatetimeIndex(start=last_time + timedelta, + newindex = pandas.date_range(last_time + timedelta, freq=timedelta, periods=cloud_displacement) ichimoku_values = ichimoku_values.append( diff --git a/app/config_conditionnals.yml b/app/config_conditionnals.yml new file mode 100644 index 00000000..1b8e90d5 --- /dev/null +++ b/app/config_conditionnals.yml @@ -0,0 +1,144 @@ +settings: + log_level: INFO + update_interval: 120 + start_worker_interval: 2 + market_data_chunk_size: 1 + timezone: Europe/Paris + +exchanges: + kraken: + required: + enabled: true + all_pairs: + - EUR + exclude: + - XBTEUR.d + - ETHEUR.d + - USDC + - USDT + - MLN + +indicators: + rsi: + - enabled: true + alert_enabled: true + alert_frequency: multi + signal: + - rsi + hot: 30 + cold: 70 + candle_period: 1h + period_count: 14 + - enabled: true + alert_enabled: true + alert_frequency: multi + signal: + - rsi + hot: 40 + cold: 60 + candle_period: 1d + period_count: 14 + momentum: + - enabled: false + alert_enabled: false + mfi: + - enabled: false + alert_enabled: false + obv: + - enabled: false + alert_enabled: false + stoch_rsi: + - enabled: false + alert_enabled: false + macd: + - enabled: false + alert_enabled: false + ichimoku: + - enabled: false + alert_enabled: false + iiv: + - enabled: false + alert_enabled: false + ma_crossover: + - enabled: true + candle_period: 1d + alert_enabled: true + alert_frequency: always + exponential: true + ma_fast: 50 + ma_slow: 120 + signal: + - open + - close + bollinger: + - enabled: true + candle_period: 1h + alert_enabled: true + alert_frequency: always + period_count: 25 + std_dev: 2.5 + signal: + - low_band + - close + - up_band + mute_cold: false + - enabled: true + candle_period: 1d + alert_enabled: true + alert_frequency: always + period_count: 25 + std_dev: 2.5 + signal: + - low_band + - close + - up_band + mute_cold: false + aroon_oscillator: + - enabled: true + alert_enabled: true + alert_frequency: always + sma_vol_period: 50 + period_count: 14 + signal: + - aroon + candle_period: 1h + +informants: + lrsi: + - enabled: false + vwap: + - enabled: false + sma: + - enabled: false + ema: + - enabled: false + alert_enabled: false + bollinger_bands: + - enabled: false + ohlcv: + - enabled: false + +conditionals: + - label: "Buy" + hot: + - rsi: 0 + - rsi: 1 + - bollinger: 1 + cold: + - aroon_oscillator: 0 + - label: "Sell" + cold: + - rsi: 0 + - rsi: 1 + - bollinger: 1 + hot: + - aroon_oscillator: 0 + +notifiers: + telegram: + required: + token: 1384797982:AAHVoB6AoHA2ptoWPKH_qZBjH2JBfMqivYE + chat_id: -373824531 + optional: + parse_mode: html + template: "[{{market}}] {{indicator}} {{status}} {{values}} {{ '\n' -}}" From ca74b30c8858ed92f1b875823b2235ec2ef3b7ba Mon Sep 17 00:00:00 2001 From: x1n5h3n Date: Sun, 2 Aug 2020 00:15:58 +0000 Subject: [PATCH 03/46] delete --- app/config_conditionnals.yml | 144 ----------------------------------- 1 file changed, 144 deletions(-) delete mode 100644 app/config_conditionnals.yml diff --git a/app/config_conditionnals.yml b/app/config_conditionnals.yml deleted file mode 100644 index 1b8e90d5..00000000 --- a/app/config_conditionnals.yml +++ /dev/null @@ -1,144 +0,0 @@ -settings: - log_level: INFO - update_interval: 120 - start_worker_interval: 2 - market_data_chunk_size: 1 - timezone: Europe/Paris - -exchanges: - kraken: - required: - enabled: true - all_pairs: - - EUR - exclude: - - XBTEUR.d - - ETHEUR.d - - USDC - - USDT - - MLN - -indicators: - rsi: - - enabled: true - alert_enabled: true - alert_frequency: multi - signal: - - rsi - hot: 30 - cold: 70 - candle_period: 1h - period_count: 14 - - enabled: true - alert_enabled: true - alert_frequency: multi - signal: - - rsi - hot: 40 - cold: 60 - candle_period: 1d - period_count: 14 - momentum: - - enabled: false - alert_enabled: false - mfi: - - enabled: false - alert_enabled: false - obv: - - enabled: false - alert_enabled: false - stoch_rsi: - - enabled: false - alert_enabled: false - macd: - - enabled: false - alert_enabled: false - ichimoku: - - enabled: false - alert_enabled: false - iiv: - - enabled: false - alert_enabled: false - ma_crossover: - - enabled: true - candle_period: 1d - alert_enabled: true - alert_frequency: always - exponential: true - ma_fast: 50 - ma_slow: 120 - signal: - - open - - close - bollinger: - - enabled: true - candle_period: 1h - alert_enabled: true - alert_frequency: always - period_count: 25 - std_dev: 2.5 - signal: - - low_band - - close - - up_band - mute_cold: false - - enabled: true - candle_period: 1d - alert_enabled: true - alert_frequency: always - period_count: 25 - std_dev: 2.5 - signal: - - low_band - - close - - up_band - mute_cold: false - aroon_oscillator: - - enabled: true - alert_enabled: true - alert_frequency: always - sma_vol_period: 50 - period_count: 14 - signal: - - aroon - candle_period: 1h - -informants: - lrsi: - - enabled: false - vwap: - - enabled: false - sma: - - enabled: false - ema: - - enabled: false - alert_enabled: false - bollinger_bands: - - enabled: false - ohlcv: - - enabled: false - -conditionals: - - label: "Buy" - hot: - - rsi: 0 - - rsi: 1 - - bollinger: 1 - cold: - - aroon_oscillator: 0 - - label: "Sell" - cold: - - rsi: 0 - - rsi: 1 - - bollinger: 1 - hot: - - aroon_oscillator: 0 - -notifiers: - telegram: - required: - token: 1384797982:AAHVoB6AoHA2ptoWPKH_qZBjH2JBfMqivYE - chat_id: -373824531 - optional: - parse_mode: html - template: "[{{market}}] {{indicator}} {{status}} {{values}} {{ '\n' -}}" From ddc201954221ae6db55eeed097a0abd709c40e3c Mon Sep 17 00:00:00 2001 From: xinshen Date: Mon, 3 Aug 2020 00:09:08 +0200 Subject: [PATCH 04/46] Correct crossover --- app/analyzers/crossover.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/analyzers/crossover.py b/app/analyzers/crossover.py index 69188662..477282e6 100644 --- a/app/analyzers/crossover.py +++ b/app/analyzers/crossover.py @@ -33,9 +33,8 @@ def analyze(self, key_indicator, key_signal, key_indicator_index, column_indexed_name = '{}_{}'.format(column, key_indicator_index) new_key_indicator.rename(columns={column: column_indexed_name}, inplace=True) - length = new_key_indicator.shape() crossed_indicator_name = '{}_{}'.format(crossed_signal, crossed_indicator_index) - new_crossed_indicator = crossed_indicator[:length[1]].copy(deep=True) + new_crossed_indicator = crossed_indicator.copy(deep=True) for column in new_crossed_indicator: column_indexed_name = '{}_{}'.format(column, crossed_indicator_index) new_crossed_indicator.rename(columns={column: column_indexed_name}, inplace=True) From 8ffc451801ce1511e5504c186f3e7efb94892acc Mon Sep 17 00:00:00 2001 From: xinshen Date: Thu, 6 Aug 2020 00:08:13 +0200 Subject: [PATCH 05/46] Format document --- app/analyzers/indicators/ichimoku.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/analyzers/indicators/ichimoku.py b/app/analyzers/indicators/ichimoku.py index efe54642..9915d690 100644 --- a/app/analyzers/indicators/ichimoku.py +++ b/app/analyzers/indicators/ichimoku.py @@ -70,8 +70,8 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa last_time = dataframe.index[-1] timedelta = dataframe.index[1] - dataframe.index[0] newindex = pandas.date_range(last_time + timedelta, - freq=timedelta, - periods=cloud_displacement) + freq=timedelta, + periods=cloud_displacement) ichimoku_values = ichimoku_values.append( pandas.DataFrame(index=newindex)) # cloud offset @@ -80,7 +80,6 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa ichimoku_values['leading_span_b'] = ichimoku_values['leading_span_b'].shift( cloud_displacement) - for index in range(0, ichimoku_values.index.shape[0]): date = ichimoku_values.index[index] @@ -104,4 +103,4 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa if chart == None: ichimoku_values.dropna(how='any', inplace=True) - return ichimoku_values \ No newline at end of file + return ichimoku_values From 79ea219909e74f55443f7e5179d00223db8f2248 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sun, 22 Nov 2020 13:30:44 +0000 Subject: [PATCH 06/46] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f1455a52..18118f1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3 +FROM python:3.8-buster # TA-lib is required by the python TA-lib wrapper. This provides analysis. COPY lib/ta-lib-0.4.0-src.tar.gz /tmp/ta-lib-0.4.0-src.tar.gz From 2b13b19573e64d0e45bb65d1bc74ecb143a49d90 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Wed, 2 Dec 2020 13:17:05 +0100 Subject: [PATCH 07/46] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..5c1892cc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] ..." +labels: '' +assignees: w1ld3r + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. git clone '...' +2. docker build -t dev/crypto-signals:latest . +3. docker run '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop** + - OS Name and Version + - Kernel Version + - Docker version + +**Additional context** +Add any other context about the problem here. +For example if any modification of the code as been made From 8bebae2186387695cab725948ddf4698e636c868 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Wed, 2 Dec 2020 13:18:35 +0100 Subject: [PATCH 08/46] Update issue templates --- .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bccb3493 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE] ..." +labels: '' +assignees: w1ld3r + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From d187dff8003e0b9463c337ad38e3c67daa54e34d Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Wed, 2 Dec 2020 14:02:25 +0100 Subject: [PATCH 09/46] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5c1892cc..818b516b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -20,8 +20,12 @@ Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots** -If applicable, add screenshots to help explain your problem. +**config.yml** +``` +settings: +log_level: INFO +[...] +``` **Desktop** - OS Name and Version From 64956c7b1839a39c5817042cfefaf68f3913c12a Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Wed, 2 Dec 2020 14:23:49 +0100 Subject: [PATCH 10/46] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 818b516b..992ecc7b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -28,8 +28,8 @@ log_level: INFO ``` **Desktop** - - OS Name and Version - - Kernel Version + - OS name and version + - crypto-signal branch - Docker version **Additional context** From fcfd0228c280ce74354ce3a4b2caef911c5ced78 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Wed, 2 Dec 2020 20:27:57 +0000 Subject: [PATCH 11/46] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 992ecc7b..2d681a6c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug report about: Create a report to help us improve -title: "[BUG] ..." +title: "[BUG]" labels: '' assignees: w1ld3r @@ -21,7 +21,7 @@ Steps to reproduce the behavior: A clear and concise description of what you expected to happen. **config.yml** -``` +```yml settings: log_level: INFO [...] From 249e5fa65f1c056b155a6f923fb41711bde194cd Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Wed, 2 Dec 2020 21:27:27 +0000 Subject: [PATCH 12/46] Update README.md change git clone url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5633653a..672dbfdb 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Because this is a development branch you need to build your custom Docker image. Be sure you have git installed in your system. -1. Clone this repo `git clone https://github.com/CryptoSignal/crypto-signal.git` +1. Clone this repo `git clone https://github.com/w1ld3r/crypto-signal.git` 1. Enter to cripto-signal folder `cd crypto-signal` From e7a3a9c03506b56a5055e0d28d1b04011dbe06d4 Mon Sep 17 00:00:00 2001 From: Chamoda Pandithage Date: Sat, 1 May 2021 10:18:12 +0530 Subject: [PATCH 13/46] Custom smtp server gmail option removed. --- app/defaults.yml | 3 +- app/notification.py | 40 ++++++++++--------- .../{gmail_client.py => email_client.py} | 17 ++++---- docs/config.md | 24 ++++++----- 4 files changed, 47 insertions(+), 37 deletions(-) rename app/notifiers/{gmail_client.py => email_client.py} (73%) diff --git a/app/defaults.yml b/app/defaults.yml index 2eaa1cad..ac5fda9a 100644 --- a/app/defaults.yml +++ b/app/defaults.yml @@ -36,8 +36,9 @@ notifiers: webhook: null optional: template: "{{exchange}}-{{market}}-{{indicator}}-{{indicator_number}} is {{status}}!{{ '\n' -}}" - gmail: + email: required: + smtp_server: null username: null password: null destination_emails: null diff --git a/app/notification.py b/app/notification.py index 083222a5..51642e6d 100644 --- a/app/notification.py +++ b/app/notification.py @@ -28,7 +28,7 @@ from analyzers.indicators import candle_recognition, ichimoku from analyzers.utils import IndicatorUtils from notifiers.discord_client import DiscordNotifier -from notifiers.gmail_client import GmailNotifier +from notifiers.email_client import EmailNotifier from notifiers.slack_client import SlackNotifier from notifiers.stdout_client import StdoutNotifier from notifiers.telegram_client import TelegramNotifier @@ -91,15 +91,16 @@ def __init__(self, notifier_config, indicator_config, conditional_config, market ) 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'] + self.email_configured = self._validate_required_config( + 'email', notifier_config) + if self.email_configured: + self.email_client = EmailNotifier( + smtp_server=notifier_config['email']['required']['smtp_server'], + username=notifier_config['email']['required']['username'], + password=notifier_config['email']['required']['password'], + destination_addresses=notifier_config['email']['required']['destination_emails'] ) - enabled_notifiers.append('gmail') + enabled_notifiers.append('email') self.telegram_configured = self._validate_required_config( 'telegram', notifier_config) @@ -220,7 +221,7 @@ def notify_all_messages(self, exchange, market_pair, candle_period, messages): self.notify_discord(messages) self.notify_webhook(messages, chart_file) # self.notify_twilio(new_analysis) - # self.notify_gmail(new_analysis) + self.notify_email(messages) self.notify_telegram(messages, chart_file) self.notify_stdout(messages) @@ -272,20 +273,21 @@ def notify_twilio(self, new_analysis): if message.strip(): self.twilio_client.notify(message) - def notify_gmail(self, new_analysis): - """Send a notification via the gmail notifier + def notify_email(self, new_analysis): + """Send a notification via the email notifier Args: new_analysis (dict): The new_analysis to send. """ + if not self.email_configured: + return - 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) + message_template = Template( + self.notifier_config['email']['optional']['template']) + + for message in new_analysis: + formatted_message = message_template.render(message) + self.email_client.notify(formatted_message.strip()) def notify_telegram(self, messages, chart_file): """Send notifications via the telegram notifier diff --git a/app/notifiers/gmail_client.py b/app/notifiers/email_client.py similarity index 73% rename from app/notifiers/gmail_client.py rename to app/notifiers/email_client.py index bd7f6fda..99376eb5 100644 --- a/app/notifiers/gmail_client.py +++ b/app/notifiers/email_client.py @@ -1,4 +1,4 @@ -"""Notify a user via Gmail +"""Notify a user via Email """ import smtplib @@ -9,21 +9,22 @@ from notifiers.utils import NotifierUtils -class GmailNotifier(NotifierUtils): - """Class for handling gmail notifications +class EmailNotifier(NotifierUtils): + """Class for handling email notifications """ - def __init__(self, username, password, destination_addresses): - """Initialize GmailNotifier class + def __init__(self, smtp_server, username, password, destination_addresses): + """Initialize EmailNotifier class Args: - username (str): Username of the gmail account to use for sending message. - password (str): Password of the gmail account to use for sending message. + smtp_server (str): Smtp server address in form host:port + username (str): Username of the email account to use for sending message. + password (str): Password of the email account to use for sending message. destination_addresses (list): A list of email addresses to notify. """ self.logger = structlog.get_logger() - self.smtp_server = 'smtp.gmail.com:587' + self.smtp_server = smtp_server self.username = username self.password = password self.destination_addresses = ','.join(destination_addresses) diff --git a/docs/config.md b/docs/config.md index 1f72c90a..80dff053 100644 --- a/docs/config.md +++ b/docs/config.md @@ -145,20 +145,25 @@ notifiers: template: "{{exchange}}-{{market}}-{{indicator}}-{{indicator_number}} is {{status}}!{{ '\n' -}}" ``` -## Gmail +## Email +**smtp_server**\ +default: None\ +necessity: required for Email\ +description: Your smtp server hostname + **username**\ default: None\ -necessity: required for Gmail\ -description: Your gmail username which is required for sending emails. +necessity: required for Email\ +description: Your email username which is required for sending emails. **password**\ default: None\ -necessity: required for Gmail\ -description: Your gmail password which is required for sending emails. +necessity: required for Email\ +description: Your email password which is required for sending emails. **destination_emails**\ default: None\ -necessity: required for Gmail\ +necessity: required for Email\ description: The email addresses to receive the emails that are sent. **template**\ @@ -166,13 +171,14 @@ default: {{exchange}}-{{market}}-{{analyzer}}-{{analyzer_number}} is {{status}}! necessity: optional\ description: See the notifier templating section. -An example of notifier settings for gmail +An example of notifier settings for email ```yml notifiers: - gmail: + email: required: - username: my_user@gmail.com + smtp_server: smtp.gmail.com:587 + username: example@gmail.com password: abcd1234 destination_emails: - my_user@gmail.com From c7173794f455aa6d85d5bf05b01ce41d6c5639c4 Mon Sep 17 00:00:00 2001 From: xinshen Date: Sat, 1 May 2021 23:13:06 +0200 Subject: [PATCH 14/46] Add notification with conditional --- app/notification.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/notification.py b/app/notification.py index 083222a5..b63e8ea8 100644 --- a/app/notification.py +++ b/app/notification.py @@ -175,6 +175,7 @@ def notify_conditional(self, exchange, market_pair, messages): new_message['market'] = messages[candle_period][0]['market'] new_message['base_currency'] = messages[candle_period][0]['base_currency'] new_message['quote_currency'] = messages[candle_period][0]['quote_currency'] + new_message['prices'] = messages[candle_period][0]['prices'] for msg in messages[candle_period]: for stat in status: if msg['status'] == stat: From f650d83b4afeddc2d9bcc0c3f41ca53b40011804 Mon Sep 17 00:00:00 2001 From: xinshen Date: Mon, 24 May 2021 20:10:39 +0200 Subject: [PATCH 15/46] add price_value functionality to conditional --- README.md | 22 +++++-- app/config copy.yml | 151 ++++++++++++++++++++++++++++++++++++++++++++ app/notification.py | 17 +++-- 3 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 app/config copy.yml diff --git a/README.md b/README.md index 60887b78..74103718 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ exchanges: #### Show me the price! -If you want prices in your notification messages, you can use the "prices" variable. +If you want prices in your notification messages, you can use the "prices" variable or "price_value". ``` notifiers: @@ -136,6 +136,17 @@ notifiers: template: "[{{analysis.config.candle_period}}] {{market}} {{values}} Prices: [{{prices}}]" ``` +``` +notifiers: + telegram: + required: + token: 791615820:AAGFgGSumWUrb-CyXtGxzAuYaabababababababa + chat_id: 687950000 + optional: + parse_mode: html + template: "[{{analysis.config.candle_period}}] {{market}} {{values}} Price 15m low: [{{price_value['15m'].low}}]" +``` + By the way, to have this feature you need to configure "ohlcv" informant for each candle period of your indicators. ``` @@ -615,8 +626,7 @@ informants: - low - close candle_period: 4h - period_count: 14 - + period_count: 14 ``` Then you can use the "price_value" variable to have the values of prices and be able to do some operations on them. @@ -630,9 +640,9 @@ notifiers: optional: parse_mode: html template: "{{ market }} - BUY {{ price_value.close }} - SL: {{ decimal_format|format(price_value.low * 0.9) }} - TP: {{ decimal_format|format(price_value.close * 1.02) }} {{ decimal_format|format(price_value.close * 1.04) }} " + BUY {{ price_value['1h'].close }} + SL: {{ price_value['4h'].low * 0.9 }} + TP: {{ price_value['1h'].close * 1.02 }} {{ price_value['4h'].close * 1.04 }} " ``` The code for "decimal_format" and "format" is necessary to obtain the prices formatted with the corresponding zeros. diff --git a/app/config copy.yml b/app/config copy.yml new file mode 100644 index 00000000..044f698b --- /dev/null +++ b/app/config copy.yml @@ -0,0 +1,151 @@ +settings: + log_level: INFO + update_interval: 120 + start_worker_interval: 2 + market_data_chunk_size: 1 + timezone: Europe/Paris + +exchanges: + kraken: + required: + enabled: true + all_pairs: + - EUR + exclude: + - XBTEUR.d + - ETHEUR.d + - USDC + - USDT + - MLN + +indicators: + rsi: + - enabled: true + alert_enabled: true + alert_frequency: multi + signal: + - rsi + hot: 100 + cold: 0 + candle_period: 1h + period_count: 14 + - enabled: true + alert_enabled: true + alert_frequency: multi + signal: + - rsi + hot: 100 + cold: 0 + candle_period: 1d + period_count: 14 + momentum: + - enabled: false + alert_enabled: false + mfi: + - enabled: false + alert_enabled: false + obv: + - enabled: false + alert_enabled: false + stoch_rsi: + - enabled: false + alert_enabled: false + macd_cross: + - enabled: true + candle_period: 1d + alert_enabled: true + alert_frequency: always + signal: + - macd + - signal + mute_cold: false + ichimoku: + - enabled: false + alert_enabled: false + iiv: + - enabled: false + alert_enabled: false + ma_crossover: + - enabled: true + candle_period: 1d + alert_enabled: true + alert_frequency: once + exponential: true + ma_fast: 50 + ma_slow: 120 + signal: + - open + - close + bollinger: + - enabled: true + candle_period: 1h + alert_enabled: true + alert_frequency: always + period_count: 25 + std_dev: 2.5 + signal: + - low_band + - close + - up_band + mute_cold: false + - enabled: true + candle_period: 1d + alert_enabled: true + alert_frequency: always + period_count: 25 + std_dev: 2.5 + signal: + - low_band + - close + - up_band + mute_cold: false + aroon_oscillator: + - enabled: true + alert_enabled: true + alert_frequency: always + sma_vol_period: 50 + period_count: 14 + signal: + - aroon + candle_period: 1h + +informants: + lrsi: + - enabled: false + vwap: + - enabled: false + sma: + - enabled: false + ema: + - enabled: false + alert_enabled: false + bollinger_bands: + - enabled: false + ohlcv: + - enabled: true + signal: + - high + - low + - close + candle_period: 15m + period_count: 20 + +# conditionals: +# - label: "Buy" +# hot: +# - rsi: 0 +# - rsi: 1 +# - label: "Sell" +# cold: +# - rsi: 0 +# - rsi: 1 + + +notifiers: + telegram: + required: + token: 1384797982:AAHVoB6AoHA2ptoWPKH_qZBjH2JBfMqivYE + chat_id: -373824531 + optional: + parse_mode: html + template: "[{{market}}] {{indicator}} {{status}} {{values}} {{ '\n' -}}" \ No newline at end of file diff --git a/app/notification.py b/app/notification.py index b63e8ea8..9dbff28c 100644 --- a/app/notification.py +++ b/app/notification.py @@ -169,6 +169,11 @@ def notify_conditional(self, exchange, market_pair, messages): new_message = {} new_message['values'] = [] new_message['indicator'] = [] + new_message['price_value'] = {} + + for stat in list(set(status)&set(condition.keys())): + nb_conditions += len(condition[stat]) + for candle_period in messages: if messages[candle_period]: new_message['exchange'] = messages[candle_period][0]['exchange'] @@ -176,6 +181,7 @@ def notify_conditional(self, exchange, market_pair, messages): new_message['base_currency'] = messages[candle_period][0]['base_currency'] new_message['quote_currency'] = messages[candle_period][0]['quote_currency'] new_message['prices'] = messages[candle_period][0]['prices'] + new_message['price_value'][candle_period] = messages[candle_period][0]['price_value'] for msg in messages[candle_period]: for stat in status: if msg['status'] == stat: @@ -190,12 +196,7 @@ def notify_conditional(self, exchange, market_pair, messages): x += 1 except: pass - for stat in status: - try: - nb_conditions += len(condition[stat]) - except: - pass - + if x == nb_conditions and x != 0: new_message['status'] = condition['label'] self.notify_discord([new_message]) @@ -240,7 +241,6 @@ def notify_discord(self, messages): for message in messages: formatted_message = message_template.render(message) - self.discord_client.notify(formatted_message.strip()) def notify_slack(self, new_analysis): @@ -306,7 +306,6 @@ def notify_telegram(self, messages, chart_file): for message in messages: formatted_messages.append(message_template.render(message)) - if self.enable_charts: if chart_file and os.path.exists(chart_file): try: @@ -658,7 +657,7 @@ def get_indicator_messages(self, new_analysis): value = format( value, decimal_format) - prices = '{} {}: {}' . format( + prices = '{} {}: {}'.format( prices, key.title(), value) decimal_format = '%' + decimal_format From 9a7aafb9a08acee3d8eedd0d8b8bdd659e066885 Mon Sep 17 00:00:00 2001 From: xinshen Date: Wed, 26 May 2021 23:55:52 +0200 Subject: [PATCH 16/46] add decimal_format in conditional reformated message --- app/config copy.yml | 151 -------------------------------------------- app/notification.py | 2 + 2 files changed, 2 insertions(+), 151 deletions(-) delete mode 100644 app/config copy.yml diff --git a/app/config copy.yml b/app/config copy.yml deleted file mode 100644 index 044f698b..00000000 --- a/app/config copy.yml +++ /dev/null @@ -1,151 +0,0 @@ -settings: - log_level: INFO - update_interval: 120 - start_worker_interval: 2 - market_data_chunk_size: 1 - timezone: Europe/Paris - -exchanges: - kraken: - required: - enabled: true - all_pairs: - - EUR - exclude: - - XBTEUR.d - - ETHEUR.d - - USDC - - USDT - - MLN - -indicators: - rsi: - - enabled: true - alert_enabled: true - alert_frequency: multi - signal: - - rsi - hot: 100 - cold: 0 - candle_period: 1h - period_count: 14 - - enabled: true - alert_enabled: true - alert_frequency: multi - signal: - - rsi - hot: 100 - cold: 0 - candle_period: 1d - period_count: 14 - momentum: - - enabled: false - alert_enabled: false - mfi: - - enabled: false - alert_enabled: false - obv: - - enabled: false - alert_enabled: false - stoch_rsi: - - enabled: false - alert_enabled: false - macd_cross: - - enabled: true - candle_period: 1d - alert_enabled: true - alert_frequency: always - signal: - - macd - - signal - mute_cold: false - ichimoku: - - enabled: false - alert_enabled: false - iiv: - - enabled: false - alert_enabled: false - ma_crossover: - - enabled: true - candle_period: 1d - alert_enabled: true - alert_frequency: once - exponential: true - ma_fast: 50 - ma_slow: 120 - signal: - - open - - close - bollinger: - - enabled: true - candle_period: 1h - alert_enabled: true - alert_frequency: always - period_count: 25 - std_dev: 2.5 - signal: - - low_band - - close - - up_band - mute_cold: false - - enabled: true - candle_period: 1d - alert_enabled: true - alert_frequency: always - period_count: 25 - std_dev: 2.5 - signal: - - low_band - - close - - up_band - mute_cold: false - aroon_oscillator: - - enabled: true - alert_enabled: true - alert_frequency: always - sma_vol_period: 50 - period_count: 14 - signal: - - aroon - candle_period: 1h - -informants: - lrsi: - - enabled: false - vwap: - - enabled: false - sma: - - enabled: false - ema: - - enabled: false - alert_enabled: false - bollinger_bands: - - enabled: false - ohlcv: - - enabled: true - signal: - - high - - low - - close - candle_period: 15m - period_count: 20 - -# conditionals: -# - label: "Buy" -# hot: -# - rsi: 0 -# - rsi: 1 -# - label: "Sell" -# cold: -# - rsi: 0 -# - rsi: 1 - - -notifiers: - telegram: - required: - token: 1384797982:AAHVoB6AoHA2ptoWPKH_qZBjH2JBfMqivYE - chat_id: -373824531 - optional: - parse_mode: html - template: "[{{market}}] {{indicator}} {{status}} {{values}} {{ '\n' -}}" \ No newline at end of file diff --git a/app/notification.py b/app/notification.py index 9dbff28c..a075632a 100644 --- a/app/notification.py +++ b/app/notification.py @@ -182,6 +182,7 @@ def notify_conditional(self, exchange, market_pair, messages): new_message['quote_currency'] = messages[candle_period][0]['quote_currency'] new_message['prices'] = messages[candle_period][0]['prices'] new_message['price_value'][candle_period] = messages[candle_period][0]['price_value'] + new_message['decimal_format'] = messages[candle_period][0]['decimal_format'] for msg in messages[candle_period]: for stat in status: if msg['status'] == stat: @@ -306,6 +307,7 @@ def notify_telegram(self, messages, chart_file): for message in messages: formatted_messages.append(message_template.render(message)) + if self.enable_charts: if chart_file and os.path.exists(chart_file): try: From 699f3a430ff5406a10e08c3ca484e3ce72fb4a7c Mon Sep 17 00:00:00 2001 From: xinshen Date: Thu, 27 May 2021 00:04:46 +0200 Subject: [PATCH 17/46] adding doc for decimal_format casting in jinja template --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74103718..c0f51073 100644 --- a/README.md +++ b/README.md @@ -641,8 +641,8 @@ notifiers: parse_mode: html template: "{{ market }} BUY {{ price_value['1h'].close }} - SL: {{ price_value['4h'].low * 0.9 }} - TP: {{ price_value['1h'].close * 1.02 }} {{ price_value['4h'].close * 1.04 }} " + SL: {{ decimal_format|format(price_value['4h'].low * 0.9) }} + TP: {{ decimal_format|format(price_value['1h'].close * 1.02) }} {{ decimal_format|format(price_value['4h'].close * 1.04) }} " ``` The code for "decimal_format" and "format" is necessary to obtain the prices formatted with the corresponding zeros. From e532d6173514128bb3ade98fe15ad3244fd413ed Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 19 Jun 2021 16:31:52 +0000 Subject: [PATCH 18/46] Add Advanced Settings doc et update conditionals doc --- docs/config.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/config.md b/docs/config.md index 80dff053..2b7891d9 100644 --- a/docs/config.md +++ b/docs/config.md @@ -7,7 +7,9 @@ 5) Indicators 6) Informants 7) Crossovers -8) Examples +8) Conditionals +9) Advanced Settings +10) Examples # 1) Configuration file structure The configuration file is YAML formatted and consists of the following top level keys. @@ -624,15 +626,39 @@ notifiers: ## Template value available - values - indicator + - price_value - exchange - market - base_currency - quote_currency - status + - price_value + - decimal_format The `status` will be the string set in `label`. - -# 9) Examples + +# 9) Advanced Settings + ## `start_worker_interval` + The settings start_worker_interval allow to define the number of the seconds between each start of a worker (use of multi processing to manage chunk of pairs). + It's usefull to manage time between request to exchange servers. + ```yml + settings: + [...] + start_worker_interval: 2 + [...] + ``` + + ## `market_data_chunk_size` + The settings market_data_chunk_size allow to define the number of pairs a worker will work on. + Lower the chunk is, faster the worker end his work. But, lower the chunk is, more workers will be required. + ```yml + settings: + [...] + market_data_chunk_size: 1 + [...] + ``` + +# 10) Examples Putting it all together an example config.yml might look like the config below if you want to use the default settings with bittrex ```yml From f95af1b6406f97d7a688dc9210c8090cf3752f4a Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 19 Jun 2021 16:34:48 +0000 Subject: [PATCH 19/46] Update Advanced Settings doc --- docs/config.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/config.md b/docs/config.md index 2b7891d9..2586d304 100644 --- a/docs/config.md +++ b/docs/config.md @@ -639,8 +639,8 @@ notifiers: # 9) Advanced Settings ## `start_worker_interval` - The settings start_worker_interval allow to define the number of the seconds between each start of a worker (use of multi processing to manage chunk of pairs). - It's usefull to manage time between request to exchange servers. + `start_worker_interval` allows to define the number of the seconds between each start of a worker (use of multi processing to manage chunk of pairs). + It's usefull to manage time between requests to exchange servers. ```yml settings: [...] @@ -649,7 +649,7 @@ notifiers: ``` ## `market_data_chunk_size` - The settings market_data_chunk_size allow to define the number of pairs a worker will work on. + `market_data_chunk_size` allows to define the number of pairs a worker will work on. Lower the chunk is, faster the worker end his work. But, lower the chunk is, more workers will be required. ```yml settings: From 0f74f5ed878194326924b5c3da632a2747c8a9dd Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Thu, 1 Jul 2021 22:02:02 +0000 Subject: [PATCH 20/46] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2d681a6c..7bc3e188 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -15,7 +15,7 @@ Steps to reproduce the behavior: 1. git clone '...' 2. docker build -t dev/crypto-signals:latest . 3. docker run '....' -4. See error +4. Error **Expected behavior** A clear and concise description of what you expected to happen. @@ -27,7 +27,7 @@ log_level: INFO [...] ``` -**Desktop** +**Machine** - OS name and version - crypto-signal branch - Docker version @@ -35,3 +35,4 @@ log_level: INFO **Additional context** Add any other context about the problem here. For example if any modification of the code as been made + From 56851a7097caae081446b4ff873b3ae11c1d37a8 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 3 Jul 2021 19:39:53 +0200 Subject: [PATCH 21/46] [FEATURE] Multiple notifier from the same family --- app/notification.py | 237 ++++++++++++++++++++++++-------------------- 1 file changed, 130 insertions(+), 107 deletions(-) diff --git a/app/notification.py b/app/notification.py index e62bd4ed..1bf7bbf7 100644 --- a/app/notification.py +++ b/app/notification.py @@ -59,74 +59,89 @@ def __init__(self, notifier_config, indicator_config, conditional_config, market self.all_historical_data = False self.timezone = None self.first_run = False + self.twilio_clients = {} + self.discord_clients = {} + self.slack_clients = {} + self.email_clients = {} + self.telegram_clients = {} + self.webhook_clients = {} + self.stdout_clients = {} 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.email_configured = self._validate_required_config( - 'email', notifier_config) - if self.email_configured: - self.email_client = EmailNotifier( - smtp_server=notifier_config['email']['required']['smtp_server'], - username=notifier_config['email']['required']['username'], - password=notifier_config['email']['required']['password'], - destination_addresses=notifier_config['email']['required']['destination_emails'] - ) - enabled_notifiers.append('email') - - 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') + for notifier in notifier_config.keys(): + if notifier.startswith('twilio'): + self.twilio_configured = self._validate_required_config( + notifier, notifier_config) + if self.twilio_configured: + self.twilio_clients[notifier] = TwilioNotifier( + twilio_key=notifier_config[notifier]['required']['key'], + twilio_secret=notifier_config[notifier]['required']['secret'], + twilio_sender_number=notifier_config[notifier]['required']['sender_number'], + twilio_receiver_number=notifier_config[notifier]['required']['receiver_number'] + ) + enabled_notifiers.append(notifier) + + if notifier.startswith('discord'): + self.discord_configured = self._validate_required_config( + notifier, notifier_config) + if self.discord_configured: + self.discord_clients[notifier] = DiscordNotifier( + webhook=notifier_config[notifier]['required']['webhook'], + username=notifier_config[notifier]['required']['username'], + avatar=notifier_config[notifier]['optional']['avatar'] + ) + enabled_notifiers.append(notifier) + + if notifier.startswith('slack'): + self.slack_configured = self._validate_required_config( + notifier, notifier_config) + if self.slack_configured: + self.slack_clients[notifier] = SlackNotifier( + slack_webhook=notifier_config[notifier]['required']['webhook'] + ) + enabled_notifiers.append(notifier) + + if notifier.startswith('email'): + self.email_configured = self._validate_required_config( + notifier, notifier_config) + if self.email_configured: + self.email_clients[notifier] = EmailNotifier( + smtp_server=notifier_config[notifier]['required']['smtp_server'], + username=notifier_config[notifier]['required']['username'], + password=notifier_config[notifier]['required']['password'], + destination_addresses=notifier_config[notifier]['required']['destination_emails'] + ) + enabled_notifiers.append(notifier) + + if notifier.startswith('telegram'): + self.telegram_configured = self._validate_required_config( + notifier, notifier_config) + if self.telegram_configured: + self.telegram_clients[notifier] = TelegramNotifier( + token=notifier_config[notifier]['required']['token'], + chat_id=notifier_config[notifier]['required']['chat_id'], + parse_mode=notifier_config[notifier]['optional']['parse_mode'] + ) + enabled_notifiers.append(notifier) + + if notifier.startswith('webhook'): + self.webhook_configured = self._validate_required_config( + notifier, notifier_config) + if self.webhook_configured: + self.webhook_clients[notifier] = WebhookNotifier( + url=notifier_config[notifier]['required']['url'], + username=notifier_config[notifier]['optional']['username'], + password=notifier_config[notifier]['optional']['password'] + ) + enabled_notifiers.append(notifier) + + if notifier.startswith('stdout'): + self.stdout_configured = self._validate_required_config( + notifier, notifier_config) + if self.stdout_configured: + self.stdout_clients[notifier] = StdoutNotifier() + enabled_notifiers.append(notifier) self.logger.info('enabled notifers: %s', enabled_notifiers) @@ -172,9 +187,9 @@ def notify_conditional(self, exchange, market_pair, messages): new_message['indicator'] = [] new_message['price_value'] = {} - for stat in list(set(status)&set(condition.keys())): + for stat in list(set(status) & set(condition.keys())): nb_conditions += len(condition[stat]) - + for candle_period in messages: if messages[candle_period]: new_message['exchange'] = messages[candle_period][0]['exchange'] @@ -198,7 +213,7 @@ def notify_conditional(self, exchange, market_pair, messages): x += 1 except: pass - + if x == nb_conditions and x != 0: new_message['status'] = condition['label'] self.notify_discord([new_message]) @@ -214,7 +229,7 @@ def notify_all_messages(self, exchange, market_pair, candle_period, messages): candles_data = self.all_historical_data[exchange][market_pair][candle_period] chart_file = self.create_chart( exchange, market_pair, candle_period, candles_data) - #self.logger.info('Chart file %s', chart_file) + # self.logger.info('Chart file %s', chart_file) except Exception as e: self.logger.info('Error creating chart for %s %s', market_pair, candle_period) @@ -238,12 +253,13 @@ def notify_discord(self, messages): if not self.discord_configured: return - message_template = Template( - self.notifier_config['discord']['optional']['template']) + for notifier in self.discord_clients: + message_template = Template( + self.notifier_config[notifier]['optional']['template']) - for message in messages: - formatted_message = message_template.render(message) - self.discord_client.notify(formatted_message.strip()) + for message in messages: + formatted_message = message_template.render(message) + self.discord_clients[notifier].notify(formatted_message.strip()) def notify_slack(self, new_analysis): """Send a notification via the slack notifier @@ -284,12 +300,13 @@ def notify_email(self, new_analysis): if not self.email_configured: return - message_template = Template( - self.notifier_config['email']['optional']['template']) + for notifier in self.email_clients: + message_template = Template( + self.notifier_config[notifier]['optional']['template']) - for message in new_analysis: - formatted_message = message_template.render(message) - self.email_client.notify(formatted_message.strip()) + for message in new_analysis: + formatted_message = message_template.render(message) + self.email_clients[notifier].notify(formatted_message.strip()) def notify_telegram(self, messages, chart_file): """Send notifications via the telegram notifier @@ -302,27 +319,31 @@ def notify_telegram(self, messages, chart_file): if not self.telegram_configured: return - message_template = Template( - self.notifier_config['telegram']['optional']['template']) + for notifier in self.telegram_clients: + message_template = Template( + self.notifier_config[notifier]['optional']['template']) - formatted_messages = [] + formatted_messages = [] - for message in messages: - formatted_messages.append(message_template.render(message)) + for message in messages: + formatted_messages.append(message_template.render(message)) - if self.enable_charts: - if chart_file and os.path.exists(chart_file): - try: - self.telegram_client.send_chart_messages( - open(chart_file, 'rb'), formatted_messages) - except (IOError, SyntaxError): - self.telegram_client.send_messages(formatted_messages) + if self.enable_charts: + if chart_file and os.path.exists(chart_file): + try: + self.telegram_clients[notifier].send_chart_messages( + open(chart_file, 'rb'), formatted_messages) + except (IOError, SyntaxError): + self.telegram_clients[notifier].send_messages( + formatted_messages) + else: + self.logger.info( + 'Chart file %s doesnt exist, sending text message.', chart_file) + self.telegram_clients[notifier].send_messages( + formatted_messages) else: - self.logger.info( - 'Chart file %s doesnt exist, sending text message.', chart_file) - self.telegram_client.send_messages(formatted_messages) - else: - self.telegram_client.send_messages(formatted_messages) + self.telegram_clients[notifier].send_messages( + formatted_messages) def notify_webhook(self, messages, chart_file): """Send notifications via a new webhook notifier @@ -335,7 +356,8 @@ def notify_webhook(self, messages, chart_file): if not self.webhook_configured: return - self.webhook_client.notify(messages, chart_file) + for notifier in self.webhook_clients: + self.webhook_clients[notifier].notify(messages, chart_file) def notify_stdout(self, messages): """Send a notification via the stdout notifier @@ -346,14 +368,15 @@ def notify_stdout(self, messages): if not self.stdout_configured: return + + for notifier in self.stdout_clients: + message_template = Template( + self.notifier_config[notifier]['optional']['template']) - message_template = Template( - self.notifier_config['stdout']['optional']['template']) + for message in messages: + formatted_message = message_template.render(message) - for message in messages: - formatted_message = message_template.render(message) - - self.stdout_client.notify(formatted_message.strip()) + self.stdout_clients[notifier].notify(formatted_message.strip()) def _validate_required_config(self, notifier, notifier_config): """Validate the required configuration items are present for a notifier. @@ -505,7 +528,7 @@ def get_indicator_messages(self, new_analysis): self.last_analysis = new_analysis self.first_run = True - #self.logger.info('Is first run: {}'.format(self.first_run)) + # 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") @@ -626,7 +649,7 @@ def get_indicator_messages(self, new_analysis): should_alert = True # if self.first_run: - #self.logger.info('Alert once for %s %s %s', market_pair, indicator, candle_period) + # self.logger.info('Alert once for %s %s %s', market_pair, indicator, candle_period) if not self.first_run: if analysis['config']['alert_frequency'] == 'once': @@ -675,7 +698,7 @@ def get_indicator_messages(self, new_analysis): 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, + analysis=analysis, status=status, last_status=last_status, prices=prices, lrsi=lrsi, creation_date=creation_date, indicator_label=indicator_label) """ @@ -737,7 +760,7 @@ def create_charts(self, messages): def create_chart(self, exchange, market_pair, candle_period, candles_data): - #self.logger.info("Beginning creation of charts: {} - {} - {}".format(exchange, market_pair, candle_period)) + # self.logger.info("Beginning creation of charts: {} - {} - {}".format(exchange, market_pair, candle_period)) now = datetime.now(timezone(self.timezone)) creation_date = now.strftime("%Y-%m-%d %H:%M:%S") From a0a23cd2ad394a3d2f23a44ee38c9f6130edd2bc Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sun, 4 Jul 2021 15:57:14 +0200 Subject: [PATCH 22/46] [UPDATE] Update doc for multiple notifier functionality --- docs/config.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/config.md b/docs/config.md index 2586d304..19fbb742 100644 --- a/docs/config.md +++ b/docs/config.md @@ -307,6 +307,28 @@ notifiers: template: "{{exchange}}-{{market}}-{{indicator}}-{{indicator_number}} is {{status}}!{{ '\n' -}}" ``` +## Mutiple Notifiers +The same type of notifier can be used multiple times like bellow: +```yml +notifiers: + telegram_00: + required: + token: XXX + chat_id: YYY + optional: + parse_mode: html + template: "[{{market}}] {{indicator}} {{status}} {{values}} {{ '\n' -}}" + telegram_01: + required: + token: AAA + chat_id: BBB + optional: + parse_mode: html + template: "[{{market}}] {{prices}} {{ '\n' -}}" +``` + +Be careful of the request rate to external services. + ## Notifier Templating The notifier templates are built with a templating language called [Jinja2](http://jinja.pocoo.org/docs/2.10/templates/) and anything that is a valid Jinja message is valid for crypto-signal. The options available are as follows: From 44f27b91a7ce49783bd193cb46c7c65d09f672a9 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Mon, 5 Jul 2021 22:58:45 +0200 Subject: [PATCH 23/46] [FEATURE] Add ability to request market future api --- app/exchange.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/exchange.py b/app/exchange.py index c4b3aacb..7cf8cbef 100644 --- a/app/exchange.py +++ b/app/exchange.py @@ -32,9 +32,11 @@ def __init__(self, exchange_config): # Loads the exchanges using ccxt. for exchange in exchange_config: if exchange_config[exchange]['required']['enabled']: - new_exchange = getattr(ccxt, exchange)({ - "enableRateLimit": True - }) + parameters = {'enableRateLimit': True} + if 'future' in exchange_config[exchange].keys(): + if exchange_config[exchange]['future'] == True: + parameters['options'] = {'defaultType': 'future'} + new_exchange = getattr(ccxt, exchange)(parameters) # sets up api permissions for user if given if new_exchange: From f3689d8178f4c1b9a3d3c4b111ba792cc0f07de6 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Mon, 5 Jul 2021 23:16:30 +0200 Subject: [PATCH 24/46] [Merge] Merge New source changes --- .gitignore | 1 + README.md | 88 ++++++++++++++++++++++++ app/analyzers/indicators/ichimoku.py | 71 ++++++++++++++----- app/analyzers/indicators/ma_crossover.py | 8 +-- app/analyzers/indicators/macd_cross.py | 4 +- app/behaviour.py | 3 +- app/notification.py | 13 ++-- 7 files changed, 160 insertions(+), 28 deletions(-) mode change 100644 => 100755 app/notification.py diff --git a/.gitignore b/.gitignore index 4277bd1d..9b09338d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ config.yml .idea/ lib/ta-lib/ app/charts +app/user_data diff --git a/README.md b/README.md index c0f51073..8a641b60 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Development branch to testing new features. This develop version has a lot of im - New indicator Klinger Oscillator - New indicator MACD Cross - New indicator StochRSI Cross +- New option to customize ichimoku strategies and added chikou span ## Installing And Running @@ -576,6 +577,93 @@ If you have enable_charts: true, you will have a parameter "chart" in the same w fh.write(fileinfo['body']) ``` +### Ichimoku + +The default ichimoku strategy is replaced with TK cross and future cross. You can either select one of them or both. If you choose both, you will receive a signal when a TK cross or future cross happens. + +``` +indicators: + ichimoku: + - enabled: true + alert_enabled: true + alert_frequency: once + signal: + - leading_span_a + - leading_span_b + - tenkansen + - kijunsen + hot: true + cold: true + hot_label: 'Uptrend is coming' + cold_label: 'Downtred is coming' + candle_period: 4h + indicator_label: 'Ichimoku' +``` + +If you want to add your own strategy, you could add your strategy to app/user_data/strategies folder (if it doesn't exist, create one). The name of your strategy should be the same name as the class name. Your custom strategy should use te following template: + +``` +class : + def analyze(ichimoku_values, dataframe): + """Custom Ichimoku strategy + + Args: + ichimoku_values (list, optional): historical list containg 'leading_span_a', 'leading_span_b', 'tenkansen', 'kijunsen' and 'chikou_span' + dataframe (float, optional): historical pandas dataframe containing 'timestamp', 'open', 'high', 'low', 'close' and 'volume' + + Returns: + is_hot, is_cold: A dataframe containing the indicators and hot/cold values. + """ + + return is_hot, is_cold +``` + +Then in your config.yaml, add the custom_strategy property, this will override the default behavior. + +``` +indicators: + ichimoku: + - enabled: true + custom_strategy: + alert_enabled: true + alert_frequency: once + signal: + - leading_span_a + - leading_span_b + - tenkansen + - kijunsen + - chikou_span + hot: true + cold: true + hot_label: 'Uptrend is coming' + cold_label: 'Downtred is coming' + candle_period: 4h + indicator_label: 'Ichimoku' +``` + +Example of a custom strategy: + +``` +class CustomStrategy: + def analyze(ichimoku_values, dataframe): + date = dataframe.index[-1] + leading_span_date = ichimoku_values.index[-1] + + tk_cross_hot = ichimoku_values['tenkansen'][date] > ichimoku_values['kijunsen'][date] + cloud_breakout_hot = dataframe['close'][date] > ichimoku_values['leading_span_a'][date] + price_hot = dataframe['close'][date] > ichimoku_values['kijunsen'][date] + + is_hot = tk_cross_hot and price_hot and cloud_breakout_hot + + tk_cross_cold = ichimoku_values['tenkansen'][date] < ichimoku_values['kijunsen'][date] + cloud_breakout_cold = dataframe['close'][date] < ichimoku_values['leading_span_a'][date] + price_cold = dataframe['close'][date] < ichimoku_values['kijunsen'][date] + + is_cold = tk_cross_cold and price_cold and cloud_breakout_cold + + return is_hot, is_cold +``` + ### Custom Hot/Cold labels Setting a custom text for the "hot" or "cold" signals allows to have a really cool notification message. diff --git a/app/analyzers/indicators/ichimoku.py b/app/analyzers/indicators/ichimoku.py index 9915d690..9976a517 100644 --- a/app/analyzers/indicators/ichimoku.py +++ b/app/analyzers/indicators/ichimoku.py @@ -8,16 +8,17 @@ from talib import abstract from analyzers.utils import IndicatorUtils +from importlib import import_module class Ichimoku(IndicatorUtils): - def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_span_b_period, - signal=['leading_span_a', 'leading_span_b'], hot_thresh=None, cold_thresh=None, chart=None): + def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_span_b_period, custom_strategy=None, + signal=['tenkansen', 'kijunsen'], hot_thresh=None, cold_thresh=None, chart=None): """Performs an ichimoku cloud analysis on the historical data Args: historical_data (list): A matrix of historical OHCLV data. - signal (list, optional): Defaults to leading_span_a and leading_span_b. The indicator + signal (list, optional): Defaults to tenkansen and kijunsen. The indicator line to check hot/cold against. hot_thresh (float, optional): Defaults to None. The threshold at which this might be good to purchase. @@ -26,6 +27,8 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa tenkansen_period (int, optional) kijunsen_period (int, optional) senkou_span_b_period (int, optional) + custom_strategy (string, optional): Defaults to None. Name of the custom strategy. The file name and class name + should have the same name as the custom strategy. Returns: pandas.DataFrame: A dataframe containing the indicators and hot/cold values. @@ -37,7 +40,8 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa 'tenkansen': [numpy.nan] * dataframe.index.shape[0], 'kijunsen': [numpy.nan] * dataframe.index.shape[0], 'leading_span_a': [numpy.nan] * dataframe.index.shape[0], - 'leading_span_b': [numpy.nan] * dataframe.index.shape[0] + 'leading_span_b': [numpy.nan] * dataframe.index.shape[0], + 'chikou_span' : [numpy.nan] * dataframe.index.shape[0] } ichimoku_values = pandas.DataFrame(ichimoku_columns, @@ -54,6 +58,8 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa high_senkou = dataframe['high'].rolling( window=senkou_span_b_period).max() + chikou_span_delay = 26 + ichimoku_values['chikou_span'] = dataframe['close'].shift(-chikou_span_delay) ichimoku_values['tenkansen'] = (low_tenkansen + high_tenkansen) / 2 ichimoku_values['kijunsen'] = (low_kijunsen + high_kijunsen) / 2 ichimoku_values['leading_span_a'] = ( @@ -79,28 +85,59 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa cloud_displacement) ichimoku_values['leading_span_b'] = ichimoku_values['leading_span_b'].shift( cloud_displacement) + + if chart == None: + if custom_strategy == None: + leading_span_hot = False + leading_span_cold = False + tk_cross_hot = False + tk_cross_cold = False + tk_cross_enabled = (('tenkansen' and 'kijunsen') in signal) + leading_span_enabled = (('leading_span_a' and 'leading_span_b') in signal) + date = dataframe.index[-1] + leading_span_date = ichimoku_values.index[-1] + + if tk_cross_enabled: + tk_cross_hot = ichimoku_values['tenkansen'][date] > ichimoku_values['kijunsen'][date] + tk_cross_cold = ichimoku_values['tenkansen'][date] < ichimoku_values['kijunsen'][date] + + if leading_span_enabled: + leading_span_hot = ichimoku_values['leading_span_a'][leading_span_date] > ichimoku_values['leading_span_b'][leading_span_date] + leading_span_cold = ichimoku_values['leading_span_a'][leading_span_date] < ichimoku_values['leading_span_b'][leading_span_date] - for index in range(0, ichimoku_values.index.shape[0]): - date = ichimoku_values.index[index] - - if date <= dataframe.index[-1]: - span_hot = ichimoku_values['leading_span_a'][date] > ichimoku_values['leading_span_b'][date] - close_hot = dataframe['close'][date] > ichimoku_values['leading_span_a'][date] if hot_thresh: - ichimoku_values.at[date, - 'is_hot'] = span_hot and close_hot - span_cold = ichimoku_values['leading_span_a'][date] < ichimoku_values['leading_span_b'][date] - close_cold = dataframe['close'][date] < ichimoku_values['leading_span_a'][date] + ichimoku_values.at[date, 'is_hot'] = tk_cross_hot or leading_span_hot + if cold_thresh: - ichimoku_values.at[date, - 'is_cold'] = span_cold and close_cold + ichimoku_values.at[date, 'is_cold'] = tk_cross_cold or leading_span_cold else: - pass + module = import_module("user_data.strategies." + custom_strategy) + attr = getattr(module, custom_strategy) + + custom_hot, custom_cold = attr.analyze(ichimoku_values, dataframe) + date = dataframe.index[-1] + + if hot_thresh: + ichimoku_values.at[date, 'is_hot'] = custom_hot + + if cold_thresh: + ichimoku_values.at[date, 'is_cold'] = custom_cold + + # Undo shifting in order to have the values aligned for displaying + ichimoku_values['chikou_span'] = dataframe['close'] + ichimoku_values['leading_span_a'] = ichimoku_values['leading_span_a'].shift(-cloud_displacement) + ichimoku_values['leading_span_b'] = ichimoku_values['leading_span_b'].shift(-cloud_displacement) + + ichimoku_values.dropna(how='any', inplace=True) except Exception as e: print('Error running ichimoku analysis: {}'.format(e)) +<<<<<<< HEAD if chart == None: ichimoku_values.dropna(how='any', inplace=True) return ichimoku_values +======= + return ichimoku_values +>>>>>>> a67f02b06376d8a7dc116d605649a99add32a559 diff --git a/app/analyzers/indicators/ma_crossover.py b/app/analyzers/indicators/ma_crossover.py index dbf41ff7..81ed4013 100644 --- a/app/analyzers/indicators/ma_crossover.py +++ b/app/analyzers/indicators/ma_crossover.py @@ -1,4 +1,4 @@ -""" Custom Indicator Increase In Volume +""" Moving Average Crossover """ import math @@ -12,7 +12,7 @@ class MACrossover(IndicatorUtils): def analyze(self, historical_data, signal=['close'], hot_thresh=None, cold_thresh=None, exponential=True, ma_fast=10, ma_slow=50): - """Performs an analysis about the increase in volumen on the historical data + """Performs an analysis about a crossover in 2 moving averages Args: historical_data (list): A matrix of historical OHCLV data. @@ -45,7 +45,7 @@ def analyze(self, historical_data, signal=['close'], hot_thresh=None, cold_thres ma_crossover['is_hot'] = False ma_crossover['is_cold'] = False - ma_crossover['is_hot'].iloc[-1] = previous_fast < previous_slow and current_fast > current_slow - ma_crossover['is_cold'].iloc[-1] = previous_fast > previous_slow and current_fast < current_slow + ma_crossover.at[ma_crossover.index[-1], 'is_hot'] = previous_fast < previous_slow and current_fast > current_slow + ma_crossover.at[ma_crossover.index[-1], 'is_cold'] = previous_fast > previous_slow and current_fast < current_slow return ma_crossover diff --git a/app/analyzers/indicators/macd_cross.py b/app/analyzers/indicators/macd_cross.py index d8f26587..b4f40e35 100644 --- a/app/analyzers/indicators/macd_cross.py +++ b/app/analyzers/indicators/macd_cross.py @@ -41,7 +41,7 @@ def analyze(self, historical_data, signal=['macd'], hot_thresh=None, cold_thresh macd_cross['is_hot'] = False macd_cross['is_cold'] = False - macd_cross['is_cold'].iloc[-1] = previous_macd > previous_signal and current_macd < current_signal - macd_cross['is_hot'].iloc[-1] = previous_macd < previous_signal and current_macd > current_signal + macd_cross.at[macd_cross.index[-1], 'is_hot'] = previous_macd < previous_signal and current_macd > current_signal + macd_cross.at[macd_cross.index[-1], 'is_cold'] = previous_macd > previous_signal and current_macd < current_signal return macd_cross diff --git a/app/behaviour.py b/app/behaviour.py index a360bc08..5a05ac51 100644 --- a/app/behaviour.py +++ b/app/behaviour.py @@ -253,7 +253,8 @@ def _get_indicator_results(self, exchange, market_pair): analysis_args['kijunsen_period'] = indicator_conf['kijunsen_period'] if 'kijunsen_period' in indicator_conf else 60 analysis_args['senkou_span_b_period'] = indicator_conf[ 'senkou_span_b_period'] if 'senkou_span_b_period' in indicator_conf else 120 - + analysis_args['custom_strategy'] = indicator_conf['custom_strategy'] if 'custom_strategy' in indicator_conf else None + if indicator == 'candle_recognition': analysis_args['candle_check'] = indicator_conf['candle_check'] if 'candle_check' in indicator_conf else 1 analysis_args['notification'] = indicator_conf['notification'] if 'notification' in indicator_conf else 'hot' diff --git a/app/notification.py b/app/notification.py old mode 100644 new mode 100755 index 1bf7bbf7..3ebb5a07 --- a/app/notification.py +++ b/app/notification.py @@ -1197,23 +1197,28 @@ def plot_ichimoku(self, ax, df, historical_data, candle_period): tenkansen = ichimoku_data.tenkansen leading_span_a = ichimoku_data.leading_span_a leading_span_b = ichimoku_data.leading_span_b + chikou_span = ichimoku_data.chikou_span + ax.plot(_time2, kijunsen, color='red', lw=0.6) ax.plot(_time2, tenkansen, color='blue', lw=0.6) ax.plot(_time2, leading_span_a, color='darkgreen', lw=0.6, linestyle='dashed') ax.plot(_time2, leading_span_b, color='darkred', lw=0.6, linestyle='dashed') + ax.plot(_time2, chikou_span, color='purple', lw=0.6) ax.fill_between(_time2, leading_span_a, leading_span_b, where=leading_span_a > leading_span_b, facecolor='#008000', interpolate=True, alpha=0.25) ax.fill_between(_time2, leading_span_a, leading_span_b, where=leading_span_b > leading_span_a, facecolor='#ff0000', interpolate=True, alpha=0.25) - ax.text(0.04, 0.94, 'kijunsen', color='red', + ax.text(0.06, 0.94, 'kijunsen', color='red', transform=ax.transAxes, fontsize=textsize, va='top') - ax.text(0.20, 0.94, 'tenkansen', color='blue', + ax.text(0.19, 0.94, 'tenkansen', color='blue', transform=ax.transAxes, fontsize=textsize, va='top') - ax.text(0.44, 0.94, '-Senkou-Span-A-', color='darkgreen', + ax.text(0.35, 0.94, 'Senkou-Span-A', color='darkgreen', transform=ax.transAxes, fontsize=textsize, va='top', fontstyle='italic') - ax.text(0.66, 0.94, '-Senkou-Span-B-', color='darkred', + ax.text(0.56, 0.94, 'Senkou-Span-B', color='darkred', transform=ax.transAxes, fontsize=textsize, va='top', fontstyle='italic') + ax.text(0.78, 0.94, 'chikou_span', color='purple', + transform=ax.transAxes, fontsize=textsize, va='top') From bee5f83824b1c614d46b9c7b9c3867125b8bb847 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Wed, 7 Jul 2021 18:32:19 +0200 Subject: [PATCH 25/46] remove error --- app/analyzers/indicators/ichimoku.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/analyzers/indicators/ichimoku.py b/app/analyzers/indicators/ichimoku.py index 9976a517..5e8c4524 100644 --- a/app/analyzers/indicators/ichimoku.py +++ b/app/analyzers/indicators/ichimoku.py @@ -133,11 +133,4 @@ def analyze(self, historical_data, tenkansen_period, kijunsen_period, senkou_spa except Exception as e: print('Error running ichimoku analysis: {}'.format(e)) -<<<<<<< HEAD - if chart == None: - ichimoku_values.dropna(how='any', inplace=True) - - return ichimoku_values -======= return ichimoku_values ->>>>>>> a67f02b06376d8a7dc116d605649a99add32a559 From 9835f5ad13eb9dd2d83969dda2d29fa2fb63e7ac Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 24 Jul 2021 00:03:19 +0200 Subject: [PATCH 26/46] add check on split market pair --- app/notification.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/notification.py b/app/notification.py index 3ebb5a07..9e6b3142 100755 --- a/app/notification.py +++ b/app/notification.py @@ -667,8 +667,10 @@ def get_indicator_messages(self, new_analysis): should_alert = False if should_alert: - base_currency, quote_currency = market_pair.split( - '/') + base_currency = market_pair.split('/') + quote_currency = '' + if len(base_currency) == 2: + base_currency, quote_currency = base_currency precision = self.market_data[exchange][market_pair]['precision'] decimal_format = '.{}f'.format( precision['price']) From bf91f1829a0dd756c22de4c439001dfcd0dd6843 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 24 Jul 2021 21:07:36 +0200 Subject: [PATCH 27/46] correct alert once in conditionnal --- app/notification.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/app/notification.py b/app/notification.py index 9e6b3142..58d9a8ec 100755 --- a/app/notification.py +++ b/app/notification.py @@ -52,6 +52,8 @@ def __init__(self, notifier_config, indicator_config, conditional_config, market self.logger = structlog.get_logger() self.notifier_config = notifier_config self.indicator_config = indicator_config + if conditional_config: + self.conditional_mode = True self.conditional_config = conditional_config self.market_data = market_data self.last_analysis = dict() @@ -165,7 +167,7 @@ def notify_all(self, new_analysis): for market_pair in messages[exchange]: _messages = messages[exchange][market_pair] - if self.conditional_config: + if self.conditional_mode: self.notify_conditional(exchange, market_pair, _messages) else: for candle_period in _messages: @@ -175,12 +177,17 @@ def notify_all(self, new_analysis): self.notify_all_messages( exchange, market_pair, candle_period, _messages[candle_period]) sleep(4) + + if self.first_run: + self.first_run = False def notify_conditional(self, exchange, market_pair, messages): status = ['hot', 'cold'] for condition in self.conditional_config: - x = 0 + c_nb_conditions = 0 + c_nb_once_muted = 0 + c_nb_new_status = 0 nb_conditions = 0 new_message = {} new_message['values'] = [] @@ -210,16 +217,24 @@ def notify_conditional(self, exchange, market_pair, messages): msg['values']) new_message['indicator'].append( msg['indicator']) - x += 1 + c_nb_conditions += 1 + if msg['last_status'] == msg['last_status'] and msg['analysis']['config']['alert_frequency'] == 'once' and not self.first_run: + c_nb_once_muted += 1 + if msg['last_status'] != msg['last_status']: + c_nb_new_status += 1 except: pass - if x == nb_conditions and x != 0: - new_message['status'] = condition['label'] - self.notify_discord([new_message]) - self.notify_webhook([new_message], None) - self.notify_telegram([new_message], None) - self.notify_stdout([new_message]) + if c_nb_conditions == nb_conditions and c_nb_conditions != 0: + if c_nb_once_muted > 0 and c_nb_new_status == 0: + self.logger.info('Alert frecuency once. Dont alert. %s %s', + new_message['market'], new_message['indicator']) + else: + new_message['status'] = condition['label'] + self.notify_discord([new_message]) + self.notify_webhook([new_message], None) + self.notify_telegram([new_message], None) + self.notify_stdout([new_message]) def notify_all_messages(self, exchange, market_pair, candle_period, messages): chart_file = None @@ -651,9 +666,8 @@ def get_indicator_messages(self, new_analysis): # if self.first_run: # self.logger.info('Alert once for %s %s %s', market_pair, indicator, candle_period) - if not self.first_run: - if analysis['config']['alert_frequency'] == 'once': - if last_status == status: + if not self.first_run and not self.conditional_mode: + if analysis['config']['alert_frequency'] == 'once' and last_status == status: self.logger.info('Alert frecuency once. Dont alert. %s %s %s', market_pair, indicator, candle_period) should_alert = False @@ -721,9 +735,6 @@ def get_indicator_messages(self, new_analysis): # Merge changes from new analysis into last analysis self.last_analysis = {**self.last_analysis, **new_analysis} - if self.first_run: - self.first_run = False - return new_messages def set_timezone(self, timezone): From 8a27d380fe371fa65d5bdaf2c9ccf428f0d969de Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Tue, 3 Aug 2021 20:16:45 +0200 Subject: [PATCH 28/46] Fix uninitialized conditional_mode variable --- app/notification.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/notification.py b/app/notification.py index 58d9a8ec..1f16be32 100755 --- a/app/notification.py +++ b/app/notification.py @@ -52,6 +52,7 @@ def __init__(self, notifier_config, indicator_config, conditional_config, market self.logger = structlog.get_logger() self.notifier_config = notifier_config self.indicator_config = indicator_config + self.conditional_mode = False if conditional_config: self.conditional_mode = True self.conditional_config = conditional_config From 3d8c91cbd02cd79b7ae1c4ab77d095c10844dfe6 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 14 Aug 2021 00:53:16 +0200 Subject: [PATCH 29/46] Add candle_period in message for conditional --- app/notification.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/notification.py b/app/notification.py index 1f16be32..2cc90d9b 100755 --- a/app/notification.py +++ b/app/notification.py @@ -193,6 +193,7 @@ def notify_conditional(self, exchange, market_pair, messages): new_message = {} new_message['values'] = [] new_message['indicator'] = [] + new_message['candle_period'] = [] new_message['price_value'] = {} for stat in list(set(status) & set(condition.keys())): @@ -218,6 +219,7 @@ def notify_conditional(self, exchange, market_pair, messages): msg['values']) new_message['indicator'].append( msg['indicator']) + new_message['candle_period'].append(candle_period) c_nb_conditions += 1 if msg['last_status'] == msg['last_status'] and msg['analysis']['config']['alert_frequency'] == 'once' and not self.first_run: c_nb_once_muted += 1 From d01d94858cec9b6cfa1b9ec6540c8ea9fafd804a Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Wed, 1 Sep 2021 22:15:53 +0200 Subject: [PATCH 30/46] add try catch on telegram notify function --- app/notifiers/telegram_client.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/notifiers/telegram_client.py b/app/notifiers/telegram_client.py index b98448de..89c6ebeb 100644 --- a/app/notifiers/telegram_client.py +++ b/app/notifiers/telegram_client.py @@ -48,8 +48,14 @@ def notify(self, message): # print(message_chunks) # exit() for message_chunk in message_chunks: - self.bot.send_message( - chat_id=self.chat_id, text=message_chunk, parse_mode=self.parse_mode) + try: + self.bot.send_message( + chat_id=self.chat_id, text=message_chunk, parse_mode=self.parse_mode) + except Exception as e: + self.logger.info('Unable to send message using Telegram !') + self.logger.info('Check your configuration ...') + self.logger.debug(e) + exit(0) @retry( retry=retry_if_exception_type(telegram.error.TimedOut), From ceb8175e4b44c378814f5d07f7b320d8fd906563 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Fri, 3 Sep 2021 23:21:39 +0200 Subject: [PATCH 31/46] Add alert_frequency in timedelta format (day, hour, minutes, seconds). Ex: 15m or 1h20s1d --- app/notification.py | 80 +++++++++++++++++++++++++------------ app/notifiers/utils.py | 2 +- app/requirements-step-2.txt | 1 + 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/app/notification.py b/app/notification.py index 2cc90d9b..ebc35cc4 100755 --- a/app/notification.py +++ b/app/notification.py @@ -4,9 +4,10 @@ import copy import json import os +import re import sys import traceback -from datetime import datetime +import datetime from time import sleep import matplotlib @@ -52,11 +53,9 @@ def __init__(self, notifier_config, indicator_config, conditional_config, market self.logger = structlog.get_logger() self.notifier_config = notifier_config self.indicator_config = indicator_config - self.conditional_mode = False - if conditional_config: - self.conditional_mode = True self.conditional_config = conditional_config self.market_data = market_data + self.alert_frequencies = {} self.last_analysis = dict() self.enable_charts = False self.all_historical_data = False @@ -168,7 +167,7 @@ def notify_all(self, new_analysis): for market_pair in messages[exchange]: _messages = messages[exchange][market_pair] - if self.conditional_mode: + if self.conditional_config: self.notify_conditional(exchange, market_pair, _messages) else: for candle_period in _messages: @@ -178,7 +177,7 @@ def notify_all(self, new_analysis): self.notify_all_messages( exchange, market_pair, candle_period, _messages[candle_period]) sleep(4) - + if self.first_run: self.first_run = False @@ -193,7 +192,6 @@ def notify_conditional(self, exchange, market_pair, messages): new_message = {} new_message['values'] = [] new_message['indicator'] = [] - new_message['candle_period'] = [] new_message['price_value'] = {} for stat in list(set(status) & set(condition.keys())): @@ -219,7 +217,6 @@ def notify_conditional(self, exchange, market_pair, messages): msg['values']) new_message['indicator'].append( msg['indicator']) - new_message['candle_period'].append(candle_period) c_nb_conditions += 1 if msg['last_status'] == msg['last_status'] and msg['analysis']['config']['alert_frequency'] == 'once' and not self.first_run: c_nb_once_muted += 1 @@ -228,10 +225,10 @@ def notify_conditional(self, exchange, market_pair, messages): except: pass - if c_nb_conditions == nb_conditions and c_nb_conditions != 0: - if c_nb_once_muted > 0 and c_nb_new_status == 0: + if c_nb_conditions == nb_conditions and c_nb_conditions > 0: + if c_nb_once_muted and not c_nb_new_status: self.logger.info('Alert frecuency once. Dont alert. %s %s', - new_message['market'], new_message['indicator']) + new_message['market'], new_message['indicator']) else: new_message['status'] = condition['label'] self.notify_discord([new_message]) @@ -258,7 +255,7 @@ def notify_all_messages(self, exchange, market_pair, candle_period, messages): self.notify_webhook(messages, chart_file) # self.notify_twilio(new_analysis) self.notify_email(messages) - self.notify_telegram(messages, chart_file) + #self.notify_telegram(messages, chart_file) self.notify_stdout(messages) def notify_discord(self, messages): @@ -277,7 +274,8 @@ def notify_discord(self, messages): for message in messages: formatted_message = message_template.render(message) - self.discord_clients[notifier].notify(formatted_message.strip()) + self.discord_clients[notifier].notify( + formatted_message.strip()) def notify_slack(self, new_analysis): """Send a notification via the slack notifier @@ -386,7 +384,7 @@ def notify_stdout(self, messages): if not self.stdout_configured: return - + for notifier in self.stdout_clients: message_template = Template( self.notifier_config[notifier]['optional']['template']) @@ -532,6 +530,36 @@ def _indicator_message_templater(self, new_analysis, template): self.last_analysis = {**self.last_analysis, **new_analysis} return new_message + def parse_alert_fequency(self, alert_frequency): + now = datetime.datetime.now() + matches = re.findall(r'\d+[dhms]', alert_frequency) + if not matches: + return + + for match in matches: + try: + value = int(match[:-1]) + except Exception as e: + self.logger.info('Unable to parse alert_frequency "%s"', value) + self.logger.debug(e) + continue + if match.endswith('m'): + now += datetime.timedelta(minutes=value) + elif match.endswith('h'): + now += datetime.timedelta(hours=value) + elif match.endswith('s'): + now += datetime.timedelta(seconds=value) + elif match.endswith('d'): + now += datetime.timedelta(days=value) + return now + + def should_i_alert(self, alert_frequency_key, alert_frequency): + if alert_frequency_key in self.alert_frequencies: + if self.alert_frequencies[alert_frequency_key] > datetime.datetime.now(): + return False + self.alert_frequencies[alert_frequency_key] = self.parse_alert_fequency(alert_frequency) + return True + def get_indicator_messages(self, new_analysis): """Creates a message list from a user defined template @@ -548,7 +576,7 @@ def get_indicator_messages(self, new_analysis): # self.logger.info('Is first run: {}'.format(self.first_run)) - now = datetime.now(timezone(self.timezone)) + now = datetime.datetime.now(timezone(self.timezone)) creation_date = now.strftime("%Y-%m-%d %H:%M:%S") new_messages = dict() @@ -612,6 +640,7 @@ def get_indicator_messages(self, new_analysis): if isinstance(values[signal], float): values[signal] = format( values[signal], '.2f') + elif indicator_type == 'crossovers': latest_result = analysis['result'].iloc[-1] @@ -666,14 +695,14 @@ def get_indicator_messages(self, new_analysis): should_alert = True - # if self.first_run: - # self.logger.info('Alert once for %s %s %s', market_pair, indicator, candle_period) - - if not self.first_run and not self.conditional_mode: + if not self.first_run and not self.conditional_config: if analysis['config']['alert_frequency'] == 'once' and last_status == status: - self.logger.info('Alert frecuency once. Dont alert. %s %s %s', - market_pair, indicator, candle_period) - should_alert = False + self.logger.info('Alert frecuency once. Dont alert. %s %s %s', + market_pair, indicator, candle_period) + should_alert = False + else: + should_alert = self.should_i_alert(''.join( + [market_pair, indicator, candle_period]), analysis['config']['alert_frequency']) if not analysis['config']['alert_enabled']: should_alert = False @@ -769,16 +798,17 @@ def create_charts(self, messages): try: self.create_chart( exchange, market_pair, candle_period, candles_data) - except Exception as e: + except telegram.error.Unauthorized as e: self.logger.info( 'Error creating chart for %s %s', market_pair, candle_period) - self.logger.exception(e) + self.logger.exception(e.message) + raise def create_chart(self, exchange, market_pair, candle_period, candles_data): # self.logger.info("Beginning creation of charts: {} - {} - {}".format(exchange, market_pair, candle_period)) - now = datetime.now(timezone(self.timezone)) + now = datetime.datetime.now(timezone(self.timezone)) creation_date = now.strftime("%Y-%m-%d %H:%M:%S") df = self.convert_to_dataframe(candles_data) diff --git a/app/notifiers/utils.py b/app/notifiers/utils.py index 27167bea..c2f1dea3 100644 --- a/app/notifiers/utils.py +++ b/app/notifiers/utils.py @@ -38,4 +38,4 @@ def chunk_message(self, message, max_message_size): else: chunked_message.append(message) - return chunked_message + return chunked_message \ No newline at end of file diff --git a/app/requirements-step-2.txt b/app/requirements-step-2.txt index 42ef2b6c..bc2d11fc 100644 --- a/app/requirements-step-2.txt +++ b/app/requirements-step-2.txt @@ -16,3 +16,4 @@ PyYAML>=5.1 tulipy>=0.2.1 matplotlib>=3.0.1 scipy>=1.1.0 +dateparser>=1.0.0 From 2011750b3f40a3f98ac8da3b7023a73da0b8ba76 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Fri, 3 Sep 2021 23:34:48 +0200 Subject: [PATCH 32/46] correct errors --- app/notification.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/notification.py b/app/notification.py index ebc35cc4..e2044862 100755 --- a/app/notification.py +++ b/app/notification.py @@ -255,7 +255,7 @@ def notify_all_messages(self, exchange, market_pair, candle_period, messages): self.notify_webhook(messages, chart_file) # self.notify_twilio(new_analysis) self.notify_email(messages) - #self.notify_telegram(messages, chart_file) + self.notify_telegram(messages, chart_file) self.notify_stdout(messages) def notify_discord(self, messages): @@ -798,10 +798,10 @@ def create_charts(self, messages): try: self.create_chart( exchange, market_pair, candle_period, candles_data) - except telegram.error.Unauthorized as e: + except Exception as e: self.logger.info( 'Error creating chart for %s %s', market_pair, candle_period) - self.logger.exception(e.message) + self.logger.exception(e) raise def create_chart(self, exchange, market_pair, candle_period, candles_data): From 0095bc13c550cc3950bc7e98bdb83a5b84ee9cb1 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 4 Sep 2021 00:14:06 +0200 Subject: [PATCH 33/46] Add alert_frequency in timedelta format (day, hour, minutes, seconds). Ex: 15m or 1h20s1d --- app/notification.py | 9 +++++++-- app/requirements-step-2.txt | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/notification.py b/app/notification.py index e2044862..08204deb 100755 --- a/app/notification.py +++ b/app/notification.py @@ -211,6 +211,9 @@ def notify_conditional(self, exchange, market_pair, messages): if msg['status'] == stat: try: for indicator in condition[stat]: + if msg['alert_frequency'] != 'once': + should_alert += self.should_i_alert(''.join( + [msg['market'], msg['values'][0], candle_period]), msg['alert_frequency']) if msg['indicator'] in indicator.keys(): if indicator[msg['indicator']] == msg['indicator_number']: new_message['values'].append( @@ -225,7 +228,7 @@ def notify_conditional(self, exchange, market_pair, messages): except: pass - if c_nb_conditions == nb_conditions and c_nb_conditions > 0: + if c_nb_conditions == nb_conditions and c_nb_conditions and should_alert: if c_nb_once_muted and not c_nb_new_status: self.logger.info('Alert frecuency once. Dont alert. %s %s', new_message['market'], new_message['indicator']) @@ -557,7 +560,9 @@ def should_i_alert(self, alert_frequency_key, alert_frequency): if alert_frequency_key in self.alert_frequencies: if self.alert_frequencies[alert_frequency_key] > datetime.datetime.now(): return False - self.alert_frequencies[alert_frequency_key] = self.parse_alert_fequency(alert_frequency) + timedelta = self.parse_alert_fequency(alert_frequency) + if timedelta: + self.alert_frequencies[alert_frequency_key] = timedelta return True def get_indicator_messages(self, new_analysis): diff --git a/app/requirements-step-2.txt b/app/requirements-step-2.txt index bc2d11fc..42ef2b6c 100644 --- a/app/requirements-step-2.txt +++ b/app/requirements-step-2.txt @@ -16,4 +16,3 @@ PyYAML>=5.1 tulipy>=0.2.1 matplotlib>=3.0.1 scipy>=1.1.0 -dateparser>=1.0.0 From e17d0f5acf3841317e51899691cb53d91cb1ab7e Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Tue, 14 Sep 2021 23:53:28 +0200 Subject: [PATCH 34/46] ADD documentation for new alert_frequency functionnality --- docs/config.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/config.md b/docs/config.md index 19fbb742..29c45df5 100644 --- a/docs/config.md +++ b/docs/config.md @@ -346,7 +346,7 @@ The notifier templates are built with a templating language called [Jinja2](http - analysis.result.is_cold - The raw boolean value of if the indicator is cold. - analysis.config.enabled - The raw config item of if this indicator is enabled. If you receive a message with a value other than True something has gone horribly wrong. - analysis.config.alert_enabled - The raw config item of if this indicator alert is enabled. If you receive a message with a value other than True something has gone horribly wrong. -- analysis.config.alert_frequency - The raw config item of whether this alert is always sent or if it is only sent once per status change. +- analysis.config.alert_frequency - The raw config item of whether this alert is always sent or if it is only sent once per status change. Can also define the sleep time of an alert. - analysis.config.hot - The raw config item of what the configured hot threshold is. - analysis.config.cold - The raw config item of what the configured cold threshold is. - analysis.config.candle_period - The raw config item of what time period of candles to gather. @@ -373,6 +373,19 @@ default: True\ necessity: optional\ description: Valid values are true or false. Whether to send alerts for this particular indicator. +**alert_frequency**\ +default: always\ +necessity: optional\ +description: Valid values are always, once or time period in the format described below. Whether to send alerts or frequency of alerts for this particular indicator. +time period format: + - `d` for number of days + - `h` for number of hours + - `m` for number of minutes + - `s` for number of seconds + ex 1: `1d12h20m10s` + ex 2: `15m` +For conditional mode, if only one indicator of the condition has its alert frequency valid, the alert will be sent. (Should it stays like that ? Open an issue if you want to change that behaviour) + **signal**\ default: A string\ necessity: optional\ From d2385b1cf04aec4f528c13c7d778a7cf32cdd36f Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sun, 3 Oct 2021 23:45:44 +0200 Subject: [PATCH 35/46] resolve alert_fequency notification issue --- app/notification.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/app/notification.py b/app/notification.py index 08204deb..9ea3eefe 100755 --- a/app/notification.py +++ b/app/notification.py @@ -189,6 +189,7 @@ def notify_conditional(self, exchange, market_pair, messages): c_nb_once_muted = 0 c_nb_new_status = 0 nb_conditions = 0 + should_alert = False new_message = {} new_message['values'] = [] new_message['indicator'] = [] @@ -207,26 +208,22 @@ def notify_conditional(self, exchange, market_pair, messages): new_message['price_value'][candle_period] = messages[candle_period][0]['price_value'] new_message['decimal_format'] = messages[candle_period][0]['decimal_format'] for msg in messages[candle_period]: + alert_frequency = msg['analysis']['config']['alert_frequency'] for stat in status: - if msg['status'] == stat: - try: - for indicator in condition[stat]: - if msg['alert_frequency'] != 'once': - should_alert += self.should_i_alert(''.join( - [msg['market'], msg['values'][0], candle_period]), msg['alert_frequency']) - if msg['indicator'] in indicator.keys(): - if indicator[msg['indicator']] == msg['indicator_number']: - new_message['values'].append( - msg['values']) - new_message['indicator'].append( - msg['indicator']) - c_nb_conditions += 1 - if msg['last_status'] == msg['last_status'] and msg['analysis']['config']['alert_frequency'] == 'once' and not self.first_run: - c_nb_once_muted += 1 - if msg['last_status'] != msg['last_status']: - c_nb_new_status += 1 - except: - pass + if msg['status'] == stat and stat in condition.keys(): + for indicator in condition[stat]: + if msg['indicator'] in indicator.keys(): + if indicator[msg['indicator']] == msg['indicator_number']: + new_message['values'].append(msg['values']) + new_message['indicator'].append(msg['indicator']) + c_nb_conditions += 1 + if alert_frequency != 'once': + key = ''.join([msg['market'], list(msg['values'])[0], candle_period]) + should_alert += self.should_i_alert(key, alert_frequency) + if msg['status'] == msg['last_status'] and alert_frequency == 'once' and not self.first_run: + c_nb_once_muted += 1 + if msg['status'] != msg['last_status']: + c_nb_new_status += 1 if c_nb_conditions == nb_conditions and c_nb_conditions and should_alert: if c_nb_once_muted and not c_nb_new_status: From ec97d91ea0dac2f5bdea7f87e186200531833099 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Tue, 19 Oct 2021 22:20:59 +0200 Subject: [PATCH 36/46] Add try/except in _get_crossover_results because of user-input usage --- app/behaviour.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/app/behaviour.py b/app/behaviour.py index 5a05ac51..f0f64658 100644 --- a/app/behaviour.py +++ b/app/behaviour.py @@ -347,23 +347,25 @@ def _get_crossover_results(self, new_result): if not crossover_conf['enabled']: self.logger.debug("%s is disabled, skipping.", crossover) continue - - key_indicator = new_result[crossover_conf['key_indicator_type'] - ][crossover_conf['key_indicator']][crossover_conf['key_indicator_index']] - crossed_indicator = new_result[crossover_conf['crossed_indicator_type'] - ][crossover_conf['crossed_indicator']][crossover_conf['crossed_indicator_index']] - - crossover_conf['candle_period'] = crossover_conf['key_indicator'] + \ - str(crossover_conf['key_indicator_index']) - - dispatcher_args = { - 'key_indicator': key_indicator['result'], - 'key_signal': crossover_conf['key_signal'], - 'key_indicator_index': crossover_conf['key_indicator_index'], - 'crossed_indicator': crossed_indicator['result'], - 'crossed_signal': crossover_conf['crossed_signal'], - 'crossed_indicator_index': crossover_conf['crossed_indicator_index'] - } + try: + key_indicator = new_result[crossover_conf['key_indicator_type']][crossover_conf['key_indicator']][crossover_conf['key_indicator_index']] + crossed_indicator = new_result[crossover_conf['crossed_indicator_type']][crossover_conf['crossed_indicator']][crossover_conf['crossed_indicator_index']] + + crossover_conf['candle_period'] = crossover_conf['key_indicator'] + \ + str(crossover_conf['key_indicator_index']) + + dispatcher_args = { + 'key_indicator': key_indicator['result'], + 'key_signal': crossover_conf['key_signal'], + 'key_indicator_index': crossover_conf['key_indicator_index'], + 'crossed_indicator': crossed_indicator['result'], + 'crossed_signal': crossover_conf['crossed_signal'], + 'crossed_indicator_index': crossover_conf['crossed_indicator_index'] + } + except Exception as e: + self.logger.warning(e) + self.logger.warning(traceback.format_exc()) + continue results[crossover].append({ 'result': crossover_dispatcher[crossover](**dispatcher_args), From c5748aa049e7001b7f21f4bc7425df06073d2b3d Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 26 Feb 2022 17:24:12 +0100 Subject: [PATCH 37/46] fix docker and docker-compose --- Dockerfile | 10 ++++------ app/.requirements-step-2.txt.swp | Bin 0 -> 12288 bytes app/requirements-step-2.txt | 2 +- docker-compose.dev.yml | 8 -------- docker-compose.yml | 4 +--- 5 files changed, 6 insertions(+), 18 deletions(-) create mode 100644 app/.requirements-step-2.txt.swp delete mode 100644 docker-compose.dev.yml diff --git a/Dockerfile b/Dockerfile index 18118f1e..19cac2d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-buster +FROM python:latest # TA-lib is required by the python TA-lib wrapper. This provides analysis. COPY lib/ta-lib-0.4.0-src.tar.gz /tmp/ta-lib-0.4.0-src.tar.gz @@ -10,13 +10,11 @@ RUN cd /tmp && \ make && \ make install -ADD app/requirements-step-1.txt /app/requirements-step-1.txt -ADD app/requirements-step-2.txt /app/requirements-step-2.txt +COPY ./app /app + WORKDIR /app -# Pip doesn't install requirements sequentially. -# To ensure pre-reqs are installed in the correct -# order they have been split into two files +RUN pip install --upgrade pip RUN pip install -r requirements-step-1.txt RUN pip install -r requirements-step-2.txt diff --git a/app/.requirements-step-2.txt.swp b/app/.requirements-step-2.txt.swp new file mode 100644 index 0000000000000000000000000000000000000000..cd43bad4df46f39bdc1b142d37775610159104a9 GIT binary patch literal 12288 zcmeI&J#P~+7zc0%2{zu!#(>%~pYJ6}n}Iv25(|h*MFIq)&oRlRmpgNQPPs6^gv2-C zGw==g94ssySr8j53y;&I14E?_43++t{&IXiKX!gw6z9>4M~B-JnFwyTg!uXNZvIx> z8$AASO>{-&=BDy9=QgX)oj#j<*B1;Q*1BDqid?9hwWT_6#jH|gsH_be^QJ8tvzZPY z85<;$Iyx(Y4-kOB#RXb=9Zar^VG>7v@%`PM?K@k~FJ1>LAOHafKmY;|fB*y_0D(&@ zKz1PB^Xaej-(ain2mkc_5FG>{009U<00Izz00bZa0SG_<0+&!gO@-)g2yw#k|NsB} z{r~Hx5Z^gpIbS%RIiENmIUQ%tdC7Ue2|2r*+nlSM-(2fQU-O%=0.2 jinja2>=2.10 requests>=2.20.0 PyYAML>=5.1 -tulipy>=0.2.1 +newtulipy matplotlib>=3.0.1 scipy>=1.1.0 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml deleted file mode 100644 index 6d27ac8b..00000000 --- a/docker-compose.dev.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: '3' - -services: - app: - image: shadowreaver/crypto-signal:latest - volumes: - - ./app:/app - - ./config.yml:/app/config.yml diff --git a/docker-compose.yml b/docker-compose.yml index 54f30bb4..1502221a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,4 @@ version: '3' services: app: - image: shadowreaver/crypto-signal:master - volumes: - - ./config.yml:/app/config.yml + build: . From 753c2f81910ee851e46fe4bfaceca8017d37d070 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 26 Feb 2022 17:32:14 +0100 Subject: [PATCH 38/46] update readme with new docke commands --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8a641b60..843f3f44 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Development branch to testing new features. This develop version has a lot of im ## Installing And Running -Because this is a development branch you need to build your custom Docker image. The commands listed below are intended to be run in a terminal. +The commands listed below are intended to be run in a terminal. Be sure you have git installed in your system. @@ -30,11 +30,7 @@ Be sure you have git installed in your system. 1. Create a config.yml file and put it into "app" folder. -1. Build your own Docker image, for example, `docker build -t dev/crypto-signals:latest .` - -1. For testing and debugging run docker with "-t" option `docker run --rm -ti -v $PWD/app:/app dev/crypto-signals:latest` - -1. For production run in daemon mode using "-d" option `docker run --rm -di -v $PWD/app:/app dev/crypto-signals:latest` +1. Build and run the docker container: `docker-compose up --build` ### Configuring config.yml From edcd860ff4d7dcd9541bf8f9e00bfef160ff8557 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sat, 26 Feb 2022 16:59:04 +0000 Subject: [PATCH 39/46] Update feature_request.md --- .github/ISSUE_TEMPLATE/feature_request.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bccb3493..3f212ccc 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,7 +3,6 @@ name: Feature request about: Suggest an idea for this project title: "[FEATURE] ..." labels: '' -assignees: w1ld3r --- From 8084f6a29c8205d21c2012f843801a72f087558a Mon Sep 17 00:00:00 2001 From: incontestableness Date: Sat, 19 Mar 2022 14:55:51 -0500 Subject: [PATCH 40/46] Send charts with Discord webhooks --- app/.requirements-step-2.txt.swp | Bin 12288 -> 0 bytes app/notification.py | 29 +++++++++++++++++----- app/notifiers/discord_client.py | 41 ++++++++++++++++++++++++++++--- app/requirements-step-2.txt | 2 +- 4 files changed, 61 insertions(+), 11 deletions(-) delete mode 100644 app/.requirements-step-2.txt.swp diff --git a/app/.requirements-step-2.txt.swp b/app/.requirements-step-2.txt.swp deleted file mode 100644 index cd43bad4df46f39bdc1b142d37775610159104a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&J#P~+7zc0%2{zu!#(>%~pYJ6}n}Iv25(|h*MFIq)&oRlRmpgNQPPs6^gv2-C zGw==g94ssySr8j53y;&I14E?_43++t{&IXiKX!gw6z9>4M~B-JnFwyTg!uXNZvIx> z8$AASO>{-&=BDy9=QgX)oj#j<*B1;Q*1BDqid?9hwWT_6#jH|gsH_be^QJ8tvzZPY z85<;$Iyx(Y4-kOB#RXb=9Zar^VG>7v@%`PM?K@k~FJ1>LAOHafKmY;|fB*y_0D(&@ zKz1PB^Xaej-(ain2mkc_5FG>{009U<00Izz00bZa0SG_<0+&!gO@-)g2yw#k|NsB} z{r~Hx5Z^gpIbS%RIiENmIUQ%tdC7Ue2|2r*+nlSM-(2fQU-O% 0: + for message in messages: + self.notify(message) + + def send_messages(self, messages=[]): + if len(messages) > 0: + for message in messages: + self.notify(message) diff --git a/app/requirements-step-2.txt b/app/requirements-step-2.txt index 74523b68..54fdae55 100644 --- a/app/requirements-step-2.txt +++ b/app/requirements-step-2.txt @@ -9,7 +9,7 @@ tabulate>=0.8.2 slackweb>=1.0.5 tenacity>=4.8.0 python-telegram-bot>=10.0.1 -webcord>=0.2 +discord-webhook>=0.15.0 jinja2>=2.10 requests>=2.20.0 PyYAML>=5.1 From 6c78df27d7ef0e8b92a41a47588f5ba0e6a488d5 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sun, 20 Mar 2022 14:53:42 +0100 Subject: [PATCH 41/46] remove --- app/.requirements-step-2.txt.swp | Bin 12288 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/.requirements-step-2.txt.swp diff --git a/app/.requirements-step-2.txt.swp b/app/.requirements-step-2.txt.swp deleted file mode 100644 index cd43bad4df46f39bdc1b142d37775610159104a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI&J#P~+7zc0%2{zu!#(>%~pYJ6}n}Iv25(|h*MFIq)&oRlRmpgNQPPs6^gv2-C zGw==g94ssySr8j53y;&I14E?_43++t{&IXiKX!gw6z9>4M~B-JnFwyTg!uXNZvIx> z8$AASO>{-&=BDy9=QgX)oj#j<*B1;Q*1BDqid?9hwWT_6#jH|gsH_be^QJ8tvzZPY z85<;$Iyx(Y4-kOB#RXb=9Zar^VG>7v@%`PM?K@k~FJ1>LAOHafKmY;|fB*y_0D(&@ zKz1PB^Xaej-(ain2mkc_5FG>{009U<00Izz00bZa0SG_<0+&!gO@-)g2yw#k|NsB} z{r~Hx5Z^gpIbS%RIiENmIUQ%tdC7Ue2|2r*+nlSM-(2fQU-O% Date: Sun, 20 Mar 2022 14:59:31 +0100 Subject: [PATCH 42/46] new branch --- .merge_request | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .merge_request diff --git a/.merge_request b/.merge_request new file mode 100644 index 00000000..e69de29b From 13fb0eeb9507fd34835fd34cf49233c13501619c Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sun, 20 Mar 2022 15:20:06 +0100 Subject: [PATCH 43/46] reformat pull request --- .merge_request | 0 app/notification.py | 27 ++++++++++------------- app/notifiers/discord_client.py | 39 +++++++++++++++------------------ 3 files changed, 30 insertions(+), 36 deletions(-) delete mode 100644 .merge_request diff --git a/.merge_request b/.merge_request deleted file mode 100644 index e69de29b..00000000 diff --git a/app/notification.py b/app/notification.py index 9cde5e43..01655549 100755 --- a/app/notification.py +++ b/app/notification.py @@ -214,12 +214,16 @@ def notify_conditional(self, exchange, market_pair, messages): for indicator in condition[stat]: if msg['indicator'] in indicator.keys(): if indicator[msg['indicator']] == msg['indicator_number']: - new_message['values'].append(msg['values']) - new_message['indicator'].append(msg['indicator']) + new_message['values'].append( + msg['values']) + new_message['indicator'].append( + msg['indicator']) c_nb_conditions += 1 if alert_frequency != 'once': - key = ''.join([msg['market'], list(msg['values'])[0], candle_period]) - should_alert += self.should_i_alert(key, alert_frequency) + key = ''.join([msg['market'], list( + msg['values'])[0], candle_period]) + should_alert += self.should_i_alert( + key, alert_frequency) if msg['status'] == msg['last_status'] and alert_frequency == 'once' and not self.first_run: c_nb_once_muted += 1 if msg['status'] != msg['last_status']: @@ -272,19 +276,12 @@ def notify_discord(self, messages, chart_file): message_template = Template( self.notifier_config[notifier]['optional']['template']) - formatted_messages = [] - - for message in messages: - formatted_messages.append(message_template.render(message)) + formatted_messages = [message_template.render( + message) for message in messages] if self.enable_charts: - if chart_file and os.path.exists(chart_file): - try: - self.discord_clients[notifier].send_chart_messages( - open(chart_file, 'rb'), formatted_messages) - except (IOError, SyntaxError): - self.discord_clients[notifier].send_messages( - formatted_messages) + if chart_file: + self.discord_clients[notifier].send_chart_messages(chart_file, formatted_messages) else: self.logger.info( 'Chart file %s doesnt exist, sending text message.', chart_file) diff --git a/app/notifiers/discord_client.py b/app/notifiers/discord_client.py index 702296c5..4a1ece91 100644 --- a/app/notifiers/discord_client.py +++ b/app/notifiers/discord_client.py @@ -1,4 +1,3 @@ - """Notify a user via discord """ @@ -7,6 +6,9 @@ from notifiers.utils import NotifierUtils +__max_message_size__ = 2000 + + class DiscordNotifier(NotifierUtils): """Class for handling Discord notifications """ @@ -19,48 +21,43 @@ def __init__(self, webhook, username, avatar=None): username (str): Display name for the discord bot. avatar (str, optional): Defaults to None. Url of an image to use as an avatar. """ - self.logger = structlog.get_logger() self.discord_username = username - self.discord_client = Webhook(url=webhook, username=username, avatar_url=avatar, rate_limit_retry=True) + self.discord_client = Webhook( + url=webhook, username=username, avatar_url=avatar, rate_limit_retry=True) - def notify(self, message): + def notify(self, message: str): """Sends the message. Args: message (str): The message to send. """ - - max_message_size = 2000 message_chunks = self.chunk_message( - message=message, max_message_size=max_message_size) - # print(message_chunks) - # exit() + message=message, max_message_size=__max_message_size__) for message_chunk in message_chunks: try: self.discord_client.set_content(message_chunk) self.discord_client.execute() except Exception as e: self.logger.info('Unable to send message using Discord !') - self.logger.info('Check your configuration ...') self.logger.debug(e) - exit(0) - def send_chart_messages(self, photo_url, messages=[]): + def send_chart_messages(self, photo_url: str, messages=[]): """Send image chart Args: photo_url (str): The photo url to send. """ - - self.discord_client.set_content("") - self.discord_client.add_file(file=photo_url.read(), filename=photo_url.name) - self.discord_client.execute(remove_files=True) - - if len(messages) > 0: - for message in messages: - self.notify(message) + try: + self.discord_client.set_content('') + with open(photo_url, 'rb') as f: + self.discord_client.add_file(file=f.read(), filename=f.name) + self.discord_client.execute(remove_files=True) + except Exception as e: + self.logger.info('Unable to send chart messages using Discord !') + self.logger.debug(e) + self.send_messages(messages) def send_messages(self, messages=[]): - if len(messages) > 0: + if messages: for message in messages: self.notify(message) From 6271c5eaace7f941301e5329569892c23a1da2ac Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sun, 20 Mar 2022 22:06:25 +0100 Subject: [PATCH 44/46] reformat discord notification module --- app/notifiers/telegram_client.py | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/app/notifiers/telegram_client.py b/app/notifiers/telegram_client.py index 89c6ebeb..f9587ea4 100644 --- a/app/notifiers/telegram_client.py +++ b/app/notifiers/telegram_client.py @@ -11,6 +11,11 @@ from notifiers.utils import NotifierUtils +__con_pool_size__ = 10 +__connect_timeout__ = 40 +__stop_after_attempt__ = 3 +__wait_fixed__ = 5 +__max_message_size__ = 4096 class TelegramNotifier(NotifierUtils): """Used to notify user of events via telegram. @@ -23,17 +28,16 @@ def __init__(self, token, chat_id, parse_mode): token (str): The telegram API token. chat_id (str): The chat ID you want the bot to send messages to. """ - self.logger = structlog.get_logger() self.bot = telegram.Bot(token=token, request=Request( - con_pool_size=10, connect_timeout=40)) + con_pool_size=__con_pool_size__, connect_timeout=__connect_timeout__)) self.chat_id = chat_id self.parse_mode = parse_mode @retry( retry=retry_if_exception_type(telegram.error.TimedOut), - stop=stop_after_attempt(3), - wait=wait_fixed(5) + stop=stop_after_attempt(__stop_after_attempt__), + wait=wait_fixed(__wait_fixed__) ) def notify(self, message): """Send the notification. @@ -41,26 +45,20 @@ def notify(self, message): Args: message (str): The message to send. """ - - max_message_size = 4096 message_chunks = self.chunk_message( - message=message, max_message_size=max_message_size) - # print(message_chunks) - # exit() + message=message, max_message_size=__max_message_size__) for message_chunk in message_chunks: try: self.bot.send_message( chat_id=self.chat_id, text=message_chunk, parse_mode=self.parse_mode) except Exception as e: self.logger.info('Unable to send message using Telegram !') - self.logger.info('Check your configuration ...') self.logger.debug(e) - exit(0) @retry( retry=retry_if_exception_type(telegram.error.TimedOut), - stop=stop_after_attempt(6), - wait=wait_fixed(5) + stop=stop_after_attempt(__stop_after_attempt__), + wait=wait_fixed(__wait_fixed__) ) def send_chart_messages(self, photo_url, messages=[]): """Send image chart @@ -68,14 +66,10 @@ def send_chart_messages(self, photo_url, messages=[]): Args: photo_url (str): The photo url to send. """ - self.bot.send_photo(chat_id=self.chat_id, photo=photo_url, timeout=40) - - if len(messages) > 0: - for message in messages: - self.notify(message) + self.send_messages(messages) def send_messages(self, messages=[]): - if len(messages) > 0: + if messages: for message in messages: self.notify(message) From 6ec778b442172d7fd8b1cde48d4872586653be14 Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Sun, 20 Mar 2022 22:12:36 +0100 Subject: [PATCH 45/46] reformat discord notification module --- app/notification.py | 10 +++------- app/notifiers/telegram_client.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/notification.py b/app/notification.py index 9ea3eefe..6df74cb9 100755 --- a/app/notification.py +++ b/app/notification.py @@ -345,13 +345,9 @@ def notify_telegram(self, messages, chart_file): formatted_messages.append(message_template.render(message)) if self.enable_charts: - if chart_file and os.path.exists(chart_file): - try: - self.telegram_clients[notifier].send_chart_messages( - open(chart_file, 'rb'), formatted_messages) - except (IOError, SyntaxError): - self.telegram_clients[notifier].send_messages( - formatted_messages) + if chart_file: + self.telegram_clients[notifier].send_chart_messages( + chart_file, formatted_messages) else: self.logger.info( 'Chart file %s doesnt exist, sending text message.', chart_file) diff --git a/app/notifiers/telegram_client.py b/app/notifiers/telegram_client.py index f9587ea4..1436aaab 100644 --- a/app/notifiers/telegram_client.py +++ b/app/notifiers/telegram_client.py @@ -17,6 +17,7 @@ __wait_fixed__ = 5 __max_message_size__ = 4096 + class TelegramNotifier(NotifierUtils): """Used to notify user of events via telegram. """ @@ -39,7 +40,7 @@ def __init__(self, token, chat_id, parse_mode): stop=stop_after_attempt(__stop_after_attempt__), wait=wait_fixed(__wait_fixed__) ) - def notify(self, message): + def notify(self, message: str): """Send the notification. Args: @@ -60,13 +61,19 @@ def notify(self, message): stop=stop_after_attempt(__stop_after_attempt__), wait=wait_fixed(__wait_fixed__) ) - def send_chart_messages(self, photo_url, messages=[]): + def send_chart_messages(self, photo_url: str, messages=[]): """Send image chart Args: photo_url (str): The photo url to send. """ - self.bot.send_photo(chat_id=self.chat_id, photo=photo_url, timeout=40) + try: + with open(photo_url, 'rb') as f: + self.bot.send_photo(chat_id=self.chat_id, + photo=f.read(), timeout=__connect_timeout__) + except Exception as e: + self.logger.info('Unable to send chart messages using Telegram !') + self.logger.debug(e) self.send_messages(messages) def send_messages(self, messages=[]): From 8ae473999f83d3025e6e9429c3734429fb0e180d Mon Sep 17 00:00:00 2001 From: w1ld3r Date: Mon, 20 Nov 2023 20:39:24 +0000 Subject: [PATCH 46/46] Add numpy minimum version --- app/requirements-step-2.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/requirements-step-2.txt b/app/requirements-step-2.txt index 54fdae55..625ab94e 100644 --- a/app/requirements-step-2.txt +++ b/app/requirements-step-2.txt @@ -16,3 +16,4 @@ PyYAML>=5.1 newtulipy matplotlib>=3.0.1 scipy>=1.1.0 +numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability