From 8df889331e32ca289d8b31933a4317929d39e972 Mon Sep 17 00:00:00 2001 From: Patrick Baumgartner Date: Sat, 14 Aug 2021 20:15:41 +0200 Subject: [PATCH] Adding dev config & start refactorign again --- .gitignore | 2 + alerter/BinancePumpAndDumpAlerter.py | 48 +++++++-------- config.yml | 34 +++++------ docker-compose.yml | 1 + entrypoint.sh | 2 +- pumpAlerts.py | 11 +++- reporter/ReportGenerator.py | 90 +++++++++++++++------------- 7 files changed, 99 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index 79bbdc7..e5c0b15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ +config.dev.yml + # Created by https://www.toptal.com/developers/gitignore/api/python,pycharm+all,intellij+all,visualstudiocode # Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm+all,intellij+all,visualstudiocode diff --git a/alerter/BinancePumpAndDumpAlerter.py b/alerter/BinancePumpAndDumpAlerter.py index bf00dc5..20807f4 100644 --- a/alerter/BinancePumpAndDumpAlerter.py +++ b/alerter/BinancePumpAndDumpAlerter.py @@ -74,8 +74,7 @@ def create_new_asset(symbol, chart_intervals): for interval in chart_intervals: asset[interval] = {} - asset[interval]["change_alert"] = 0 - asset[interval]["change_top_report"] = 0 + asset[interval]["change_current"] = 0 asset[interval]["change_last"] = 0 return asset @@ -160,11 +159,13 @@ def update_all_monitored_assets_and_send_news_messages( asset, chart_intervals, extract_interval, - outlier_intervals, ) self.report_generator.send_pump_dump_message( - asset, chart_intervals, dump_enabled + asset, + chart_intervals, + outlier_intervals, + dump_enabled, ) def calculate_asset_change( @@ -172,7 +173,6 @@ def calculate_asset_change( asset, chart_intervals, extract_interval, - outlier_intervals, ): asset_length = len(asset["price"]) @@ -189,9 +189,8 @@ def calculate_asset_change( interval, ) break - # Gets change in % from last alert trigger. - price_delta = asset["price"][-1] - asset["price"][-data_points] + price_delta = asset["price"][-1] - asset["price"][-1-data_points] change = price_delta / asset["price"][-1] self.logger.debug( "Calculated asset: %s for interval: %s with change: %s", @@ -200,18 +199,11 @@ def calculate_asset_change( change, ) - # Stores change for the interval into asset dict. Only used for top pump dump report. - asset[interval]["change_top_report"] = change - # Set last change for next interval iteration - asset[interval]["change_last"] = asset[interval]["change_alert"] + asset[interval]["change_last"] = asset[interval]["change_current"] - # If change is bigger than the outliers set it for reporting, - # else reset the value to not produce accidentally spam - if abs(change) >= outlier_intervals[interval]: - asset[interval]["change_alert"] = change - else: - asset[interval]["change_alert"] = 0 + # Stores change for the current interval into asset dict. + asset[interval]["change_current"] = change return asset @@ -228,7 +220,7 @@ def reset_prices_data_when_due( message = "Emptying price data to prevent memory errors." self.logger.debug(message) - self.telegram.send_generic_message(message) + self.telegram.send_generic_message(message, is_alert_chat=True) # Do not delete everything, only elements older than the last monitored interval lastInterval = "1s" @@ -238,7 +230,7 @@ def reset_prices_data_when_due( data_points = int(chart_intervals[lastInterval]["value"] / extract_interval) for asset in assets: - asset["price"] = asset["price"][-data_points:] + asset["price"] = asset["price"][-1-data_points:] initial_time = current_time @@ -339,15 +331,6 @@ def run(self): while True: start_loop_time = time.time() - self.initial_time = self.reset_prices_data_when_due( - self.initial_time, - start_loop_time, - self.reset_interval, - self.extract_interval, - filtered_assets, - self.chart_intervals, - ) - exchange_assets = self.retrieve_exchange_assets( self.api_url, self.retry_interval ) @@ -383,6 +366,15 @@ def run(self): self.no_of_reported_coins, ) + self.initial_time = self.reset_prices_data_when_due( + self.initial_time, + start_loop_time, + self.reset_interval, + self.extract_interval, + filtered_assets, + self.chart_intervals, + ) + # Sleeps for the remainder of 1s, or loops through if extraction takes longer end_loop_time = time.time() diff --git a/config.yml b/config.yml index 2949e0e..62120a8 100644 --- a/config.yml +++ b/config.yml @@ -20,17 +20,17 @@ chartIntervals: # Values in % at which an alert is triggered. Ensure interval exists in chartIntervals as well. outlierIntervals: - "1s": 0.01 - "5s": 0.02 - "15s": 0.03 - "30s": 0.04 - "1m": 0.05 - "5m": 0.05 - "15m": 0.07 - "30m": 0.1 - "1h": 0.15 - "3h": 0.2 - "6h": 0.25 + "1s": 0.02 + "5s": 0.05 + "15s": 0.06 + "30s": 0.08 + "1m": 0.1 + "5m": 0.10 + "15m": 0.15 + "30m": 0.20 + "1h": 0.30 + "3h": 0.4 + "6h": 0.5 # Used for telegram bot updates @@ -73,8 +73,6 @@ additionalStatsEnabled: True noOfReportedCoins: 5 # Intervals for top pump and dump to be sent, ensure its in chartIntervals + outlierIntervals as well topReportIntervals: - - 30m - - 1h - 3h - 6h @@ -89,13 +87,13 @@ newsEmoji: ! "\U0001F4F0" # 📰 # If False we do not print unnecessary messages debug: False -# Skip alert at higher timeframes when change % did not change value by threshold -alertSkipThreshold: 0.005 -# Interval for clearing array to prevent MEM ERROR can handle up to 12h+ depending on system -resetInterval: 12h +# Skip alert at higher timeframes when change in % did not change value by threshold in percentage points +alertSkipThreshold: 0.75 +# Interval for clearing array to prevent memory can handle up to 12h+ depending on system +resetInterval: 6h # In the case of get price fail, this is the time delay before re-attempt priceRetryInterval: 5s # If telegram message fails to send, this is the time delay before re-attempt -telegramRetryInterval: 2s +telegramRetryInterval: 30s # Disables checking and adding of new listing pairs checkNewListingEnabled: True diff --git a/docker-compose.yml b/docker-compose.yml index 348ccd1..1e41381 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: # image: brianleect/binance-pump-alerts:latest restart: "no" environment: + - CONFIG_FILE=config.dev.yml - TELEGRAM_TOKEN=${TELEGRAM_TOKEN} - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID} - TELEGRAM_ALERT_CHAT_ID=${TELEGRAM_ALERT_CHAT_ID} diff --git a/entrypoint.sh b/entrypoint.sh index aaf0473..14166ce 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -4,7 +4,7 @@ set -e process_config() { # Please mount your own config file into the container for unsupported env parameters! - # Currently not supported config paramenters: chartIntervals, outlierIntervals, pairsOfInterest, watchlist, topReportIntervals + # Currently not supported config parameters: chartIntervals, outlierIntervals, pairsOfInterest, watchlist, topReportIntervals if [[ -n $API_URL ]]; then sed -i "s/apiUrl.*/apiUrl: ${API_URL}/" config.yml diff --git a/pumpAlerts.py b/pumpAlerts.py index 0d1b2e4..69b8b76 100644 --- a/pumpAlerts.py +++ b/pumpAlerts.py @@ -9,7 +9,15 @@ # Read config __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) -yaml_file = open(os.path.join(__location__, "config.yml"), "r", encoding="utf-8") +config_file = "config.yml" + +# Using dev config while development +config_dev_file = "config.dev.yml" +if os.path.isfile(config_dev_file): + config_file = config_dev_file + +yaml_file = open(os.path.join(__location__, config_file), "r", encoding="utf-8") + config = yaml.load(yaml_file, Loader=yaml.FullLoader) # Define the log format @@ -30,6 +38,7 @@ logger = logging.getLogger("binance-pump-alerts-app") # Logg whole configuration during the startup +logger.info("Using config file: %s", config_file) logger.debug("Config: %s", config) diff --git a/reporter/ReportGenerator.py b/reporter/ReportGenerator.py index 1348a63..38417c2 100644 --- a/reporter/ReportGenerator.py +++ b/reporter/ReportGenerator.py @@ -59,20 +59,41 @@ def send_pump_dump_message( self, asset, chart_intervals, + outlier_intervals, dump_enabled=True, ): - tmpChange = 0 - tmpInterval = 0 + change_last = 0 + change_biggest_delta = 0 + interval_last = 0 no_of_alerts = 0 message = "" + for interval in chart_intervals: - change = asset[interval]["change_alert"] + change = asset[interval]["change_current"] + + # Skip report if no outlier + if abs(change) < outlier_intervals[interval]: + self.logger.debug( + "Change for asset: %s for interval: %s is to low: %s. Skipping report creation.", + asset["symbol"], + interval, + change, + ) + continue + + # Remember biggest change of all intervals, to skip later notification + change_last = asset[interval]["change_last"] + change_delta = change - change_last + + if abs(change_delta) > abs(change_biggest_delta): + change_biggest_delta = change_delta + + # Remember the total number of alerts + no_of_alerts += 1 + interval_last = interval if change > 0: - tmpChange = change - tmpInterval = interval - no_of_alerts += 1 message += "{0} *[{1} Interval]* Change: _{2:.3f}%_ | Price: _{3:.10f}_\n".format( self.pump_emoji, interval, @@ -81,9 +102,6 @@ def send_pump_dump_message( ) if change < 0 and dump_enabled: - tmpChange = change - tmpInterval = interval - no_of_alerts += 1 message += "{0} *[{1} Interval]* Change: _{2:.3f}%_ | Price: _{3:.10f}_\n".format( self.dump_emoji, interval, @@ -91,39 +109,29 @@ def send_pump_dump_message( asset["price"][-1], ) - if no_of_alerts == 1: + # Skip alert if change is not big enough to avoid spam + if abs(change_biggest_delta) < (self.alert_skip_threshold / 100): + self.logger.debug( + "Change for asset: %s on all intervals is to low: %s. Skipping this alert report.", + asset["symbol"], + change_biggest_delta, + ) + return - # Skipping notification if the change is to low and we are on higher intervals - change_last = asset[tmpInterval]["change_last"] - change_delta = tmpChange - change_last - - if ( - tmpChange != 0 - and (abs(change_delta) < self.alert_skip_threshold) - and chart_intervals[tmpInterval]["value"] > 3 - ): - self.logger.warning( - "Change for asset: %s [%s] from %s to: %s is to low: %s. Skipping this alert.", - asset["symbol"], - tmpInterval, - tmpChange, - change_last, - change_delta, - ) - return + if no_of_alerts == 1: - if tmpChange > 0: + if change_last > 0: self.send_pump_message( asset["symbol"], - tmpInterval, - tmpChange, + interval_last, + change_last, asset["price"][-1], ) - if tmpChange < 0 and dump_enabled: + if change_last < 0 and dump_enabled: self.send_dump_message( asset["symbol"], - tmpInterval, - tmpChange, + interval_last, + change_last, asset["price"][-1], ) @@ -158,7 +166,7 @@ def send_top_pump_dump_statistics_report( if top_pump_enabled: pump_sorted_list = sorted( assets, - key=lambda item: item[interval]["change_top_report"], + key=lambda item: item[interval]["change_current"], reverse=True, )[0:no_of_reported_coins] @@ -168,13 +176,13 @@ def send_top_pump_dump_statistics_report( for asset in pump_sorted_list: message += "- {0}: _{1:.2f}_%\n".format( - asset["symbol"], asset[interval]["change_top_report"] * 100 + asset["symbol"], asset[interval]["change_current"] * 100 ) message += "\n" if top_dump_enabled: dump_sorted_list = sorted( - assets, key=lambda item: item[interval]["change_top_report"] + assets, key=lambda item: item[interval]["change_current"] )[0:no_of_reported_coins] message += "{0} *Top {1} Dumps*\n".format( @@ -183,7 +191,7 @@ def send_top_pump_dump_statistics_report( for asset in dump_sorted_list: message += "- {0}: _{1:.2f}_%\n".format( - asset["symbol"], asset[interval]["change_top_report"] * 100 + asset["symbol"], asset[interval]["change_current"] * 100 ) if additional_stats_enabled: @@ -199,12 +207,12 @@ def generate_additional_statistics_report(self, assets, interval): sum_change = 0 for asset in assets: - if asset[interval]["change_top_report"] > 0: + if asset[interval]["change_current"] > 0: up += 1 - elif asset[interval]["change_top_report"] < 0: + elif asset[interval]["change_current"] < 0: down += 1 - sum_change += asset[interval]["change_top_report"] + sum_change += asset[interval]["change_current"] avg_change = sum_change / len(assets)