master f867f31b06f0 cached
18 files
47.1 KB
11.8k tokens
31 symbols
1 requests
Download .txt
Repository: brianleect/binance-pump-alerts
Branch: master
Commit: f867f31b06f0
Files: 18
Total size: 47.1 KB

Directory structure:
gitextract_g_svvtu5/

├── .dockerignore
├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── Dockerfile
├── README.md
├── alerter/
│   ├── BinancePumpAndDumpAlerter.py
│   └── __init__.py
├── config.yml
├── docker-compose.yml
├── entrypoint.sh
├── pumpAlerts.py
├── reporter/
│   ├── ReportGenerator.py
│   └── __init__.py
├── requirements.txt
├── sender/
│   ├── TelegramSender.py
│   └── __init__.py
└── utils/
    ├── ConversionUtils.py
    └── __init__.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
.env
.idea
.gitignore

venv

config.dev.yml
docker-compose.yml
Readme.md


================================================
FILE: .github/workflows/main.yml
================================================
# This is a basic workflow to help you get started with Actions

name: Publish Docker image

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repo
        uses: actions/checkout@v2
      
      - name: Log in to Docker Hub
        uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: ${{ github.actor }}/binance-pump-alerts
      
      - name: Build and push Docker image
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}


================================================
FILE: .gitignore
================================================

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

### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf

# AWS User-specific
.idea/**/aws.xml

# Generated files
.idea/**/contentModel.xml

# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml

# Gradle
.idea/**/gradle.xml
.idea/**/libraries

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn.  Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr

# CMake
cmake-build-*/

# Mongo Explorer plugin
.idea/**/mongoSettings.xml

# File-based project format
*.iws

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Cursive Clojure plugin
.idea/replstate.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

# Editor-based Rest Client
.idea/httpRequests

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360

.idea/

# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023

*.iml
modules.xml
.idea/misc.xml
*.ipr

# Sonarlint plugin
.idea/sonarlint

### PyCharm+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff

# AWS User-specific

# Generated files

# Sensitive or high-churn files

# Gradle

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn.  Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr

# CMake

# Mongo Explorer plugin

# File-based project format

# IntelliJ

# mpeltonen/sbt-idea plugin

# JIRA plugin

# Cursive Clojure plugin

# Crashlytics plugin (for Android Studio and IntelliJ)

# Editor-based Rest Client

# Android studio 3.1+ serialized cache file

### PyCharm+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360


# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023


# Sonarlint plugin

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace

# Local History for Visual Studio Code
.history/

### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide


================================================
FILE: Dockerfile
================================================
FROM python:3.9-slim-buster as base

# Setup env
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONFAULTHANDLER 1
ENV PATH=/home/bpauser/.local/bin:$PATH
ENV FT_APP_ENV="docker"

# Prepare environment
RUN mkdir /binance-pump-alerts \
  && apt-get update \
  && apt-get -y install sudo \
  && apt-get clean \
  && useradd -u 1000 -G sudo -U -m bpauser \
  && chown bpauser:bpauser /binance-pump-alerts \
  # Allow sudoers
  && echo "bpauser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers

WORKDIR /binance-pump-alerts

# Install dependencies
FROM base as python-deps
RUN  apt-get update \
  && apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
  && apt-get clean \
  && pip install --upgrade pip

# Install dependencies
COPY --chown=bpauser:bpauser requirements.txt /binance-pump-alerts/
USER bpauser
RUN  pip install --user --no-cache-dir -r requirements.txt

# Copy dependencies to runtime-image
FROM base as runtime-image

COPY --from=python-deps /usr/local/lib /usr/local/lib
ENV LD_LIBRARY_PATH /usr/local/lib

COPY --from=python-deps --chown=bpauser:bpauser /home/bpauser/.local /home/bpauser/.local

USER bpauser

COPY --chown=bpauser:bpauser . /binance-pump-alerts/

RUN chmod a+x entrypoint.sh

ENTRYPOINT ["./entrypoint.sh", "python", "pumpAlerts.py"]


================================================
FILE: README.md
================================================
# Binance Pump Alerts

BPA is a simple application which gets the price data from Binance Spot or Futures API and sends Telegram messages based on parameters set used to detect pumps and dumps on the Binance Exchange.

[Demo Telegram Channel](https://t.me/binance_pump_alerts) hosted on AWS ec2 running the 'Base Stable Version' release 24/7.

![image](https://user-images.githubusercontent.com/63389110/128601355-4be90b36-5e54-4be6-bf85-00fc395645de.png)

## Manual Setup

1. On the command-line, run the command `pip install -r requirements.txt` while located at folder with code.
1. Create a new telegram bot token from [@botfather](https://t.me/BotFather).
1. Get telegram `chat_id` from [@get_id_bot](https://telegram.me/get_id_bot).
   - Alternatively, a `channel_id` can be used as well.
1. Add pairs to watch into the watchlist or to ignore in blacklist or leave it empty to monitor all tickers on Binance.
1. Run the script with the command `python pumpAlerts.py`.

## Docker Setup

1. Use environment variables in the `docker-compse.yml` file to provide your config.
   - See `entrypoint.sh` for environment variable names and the config possibilities.
   - You can also use a `.env` file during development.
   - If changing the config parameters, you have to make sure that search and replace will place the right parameter in the `config.yml`
   - Emojis are more tricky therefore defining it with some tricks e.g. `PUMP_EMOJI="! \"\\\\U0001F4B9\""`
1. On the command line run `docker-compose up -d --build` to create and run the docker image/container.

## Configuration

### Mandatory Params

1. `telegramToken`: The token obtained from[@botfather](https://t.me/BotFather).
2. `telegramChatId`: The bot will send the messages to this `chat_id`. It can be a group or channel as well.

## Main Customizable Params

1. `chartIntervals`: Can be modified to consider other timeframes, follow the format of 's' for seconds, 'm' for minutes, 'h' for hours.
1. `outlierIntervals`: (0.01 -> 1% , 0.1 -> 10%), modify accordingly based on needs. Avoid setting it too low to avoid noise.
1. `extractInterval`: Default is `1s`, Interval at which we retrieve the price information from Binance.
1. `pairsOfInterest`: Default is _USDT_. Other options include BUSD, BTC, ETH etc.
1. `topReportIntervals`: Default is `1h`,`3h`and `6h` Intervals for top pump and dump reports to be sent, ensure it is in chartIntervals + outlierIntervals as well.

### Optional features to enable

1. `watchlist`: Default if left empty it'll look at ALL symbols after filtering by pairs of interest. If pairs are added to the watchlist, the application will _only TRACK the pairs specified_. pairsOfInterest will be ignored.
1. `blacklist`: Default if left empty it'll look at ALL symbols after filtering by pairs of interest. If pairs are added to the blacklist, the application will ignore pairs specified. pairs of Interest will NOT be impacted.
1. `dumpEnabled`: If `True`, the application will alert on dumps as well.

#### Top Pump & Dump Params

1. `topPumpEnabled`: If `True`, the application will send the Top X pumps at the defined interval.
1. `topDumpEnabled`: If `True`, the application will send the Top X dumps at the defined interval.
   - Together with pump information, if enabled.
1. `noOfReportedCoins`: Top X amount of coins shown, adjust to show more or less within the timeframe.
1. `telegramAlertChatId`: Insert the alert chat_id for top pump dump alert, if left at `0`, it'll send messages to the telegram `chat_Id`.
   For params not indicated above, refer to comments besides parameter for its use.

#### Debug Params (Avoid modifying if possible!)

1. `debug`: Default is `False`. Please, only enable for debugging purposes. Default logging set to info level.
1. `resetInterval`: Default `12h`. It clears the array used to store data price points to prevent memory issues.
1. `priceRetryInterval`: Default `5s`. In the case of get price fail, this is the time delay before re-attempt
1. `checkNewListingEnabled`: Default `True`. Enables checking and adding of new listing pairs.

## Todo

1. Integrate with Binance API to make trades on pumps.
1. Integrate with Binance Websocket API to get volume information.
1. Integrate with listing-predictor to monitor movements for potential listings.

## Completed features

1. Telegram integration
1. Price update every 1s
1. Adjustable alert % param (outliers)
1. Watchlist feature
1. Monitor future markets
1. Optional alert on dumps
1. Customizable minimum alert interval for spam prevention
1. Option to disable print debugs on extraction
1. [Test] Volume Change Updates (TEST_VOL version)
1. Allows long period of running without memory issues
1. Send periodic Top X Pump & Dump reports
1. Docker integration (Thanks to [@patbaumgartner](https://github.com/patbaumgartner))
1. Logging integration (Thanks to [@patbaumgartner](https://github.com/patbaumgartner))
1. Major Refactoring and cleanup (Thanks to [@patbaumgartner](https://github.com/patbaumgartner))
1. Blacklist feature


================================================
FILE: alerter/BinancePumpAndDumpAlerter.py
================================================
import logging
import requests
import time

from time import sleep
from utils import ConversionUtils


class BinancePumpAndDumpAlerter:
    def __init__(
        self,
        api_url,
        watchlist,
        blacklist,
        pairs_of_interest,
        chart_intervals,
        outlier_intervals,
        top_report_intervals,
        extract_interval,
        retry_interval,
        reset_interval,
        top_pump_enabled,
        top_dump_enabled,
        additional_statistics_enabled,
        no_of_reported_coins,
        dump_enabled,
        check_new_listing_enabled,
        top_report_nearest_hour,
        telegram,
        report_generator,
    ):
        self.api_url = api_url
        self.watchlist = watchlist
        self.blacklist = blacklist
        self.pairs_of_interest = pairs_of_interest
        self.outlier_intervals = outlier_intervals
        self.extract_interval = extract_interval
        self.retry_interval = retry_interval
        self.reset_interval = reset_interval
        self.top_pump_enabled = top_pump_enabled
        self.top_dump_enabled = top_dump_enabled
        self.additional_statistics_enabled = additional_statistics_enabled
        self.no_of_reported_coins = no_of_reported_coins
        self.dump_enabled = dump_enabled
        self.check_new_listing_enabled = check_new_listing_enabled
        self.telegram = telegram
        self.report_generator = report_generator

        self.logger = logging.getLogger("pump-and-dump-alerter")

        self.initial_time = int(time.time())
        nearest_hour = self.initial_time - (self.initial_time % 3600) + 3600
        self.logger.info(
            "Nearest hour is %i seconds away", nearest_hour - self.initial_time
        )

        self.chart_intervals = {}
        for interval in chart_intervals:
            self.chart_intervals[interval] = {}
            self.chart_intervals[interval][
                "value"
            ] = ConversionUtils.duration_to_seconds(interval)

        self.top_report_intervals = {}
        for interval in top_report_intervals:
            self.top_report_intervals[interval] = {}

            # Determine initial start time for TPD. Should conveniently solve original 0% issue together.
            if top_report_nearest_hour:
                self.top_report_intervals[interval]["start"] = nearest_hour
            else:
                self.top_report_intervals[interval]["start"] = self.initial_time

            self.top_report_intervals[interval][
                "value"
            ] = ConversionUtils.duration_to_seconds(interval)

    @staticmethod
    def extract_ticker_data(symbol, assets):
        for asset in assets:
            if asset["symbol"] == symbol:
                return asset

    @staticmethod
    def create_new_asset(symbol, chart_intervals):
        asset = {"symbol": symbol, "price": [], "volume": []}

        for interval in chart_intervals:
            asset[interval] = {}
            asset[interval]["change_current"] = 0
            asset[interval]["change_last"] = 0
            asset[interval]["change_volume"] = 0

        return asset

    def retrieve_exchange_assets(self, api_url):
        try:
            self.logger.debug(
                "Retrieving price information from the ticker. ApiUrl: %s.", api_url
            )
            return requests.get(api_url).json()
        except Exception as e:
            self.logger.error(
                "Issue occurred while getting prices. Error: %s.",
                e,
                exc_info=True,
            )
            sleep(5)  # Sleep 5s and try again
            return self.retrieve_exchange_assets(api_url)

    def is_symbol_valid(self, symbol, watchlist, blacklist, pairs_of_interest):
        # Filter symbols in watchlist if set - This disables the pairsOfInterest feature
        if len(watchlist) > 0:
            if symbol not in watchlist:
                self.logger.debug("Ignoring symbol not in watchlist: %s.", symbol)
                return False
            return True

        # Filter symbols in blacklist if set - This DOES NOT IMPACT the pairsOfInterest feature
        if len(blacklist) > 0:
            if symbol in blacklist:
                self.logger.info(
                    "Ignoring symbol found in blacklist: %s.", symbol)
                return False

        # Filter pairsOfInterest to reduce the noise. E.g. BUSD, USDT, ETH, BTC
        is_in_pairs_of_interest = False
        for pair in pairs_of_interest:
            if symbol.endswith(pair):
                is_in_pairs_of_interest = True
                break

        if not is_in_pairs_of_interest:
            self.logger.debug("Ignoring symbol not in pairsOfInterests: %s.", symbol)
            return False

        # Filter leverage symbols
        for pair in pairs_of_interest:
            coin = symbol.replace(pair, "")
            if (
                coin.endswith("UP")
                or coin.endswith("DOWN")
                or coin.endswith("BULL")
                or coin.endswith("BEAR")
            ):
                self.logger.debug("Ignoring leverage symbol: %s.", symbol)
                return False

        return True

    def filter_and_convert_assets(
        self, exchange_assets, watchlist, blacklist, pairs_of_interest, chart_intervals
    ):
        filtered_assets = []

        for exchange_asset in exchange_assets:
            symbol = exchange_asset["symbol"]

            if self.is_symbol_valid(symbol, watchlist, blacklist, pairs_of_interest):
                filtered_assets.append(self.create_new_asset(symbol, chart_intervals))
                self.logger.info("Adding symbol: %s.", symbol)

        return filtered_assets

    def update_all_monitored_assets_and_send_news_messages(
        self,
        monitored_assets,
        exchange_assets,
        current_time,
        dump_enabled,
        chart_intervals,
        extract_interval,
        outlier_intervals,
    ):
        for asset in monitored_assets:
            exchange_asset = self.extract_ticker_data(asset["symbol"], exchange_assets)
            asset["price"].append(float(exchange_asset["price"]))

            self.calculate_asset_change(
                asset,
                chart_intervals,
                extract_interval,
            )

            self.report_generator.send_pump_dump_message(
                asset,
                chart_intervals,
                outlier_intervals,
                current_time,
                dump_enabled,
            )

    def calculate_asset_change(
        self,
        asset,
        chart_intervals,
        extract_interval,
    ):
        asset_length = len(asset["price"])

        for interval in chart_intervals:
            data_points = chart_intervals[interval]["value"] // extract_interval

            # If data is not enough yet after restart for interval, stop here.
            if data_points >= asset_length:
                self.logger.debug(
                    "Not enough datapoints (%s/%s) for interval: %s",
                    asset_length,
                    data_points,
                    interval,
                )
                break

            # Gets change in % from last alert trigger.
            current_price = asset["price"][-1]
            if current_price == 0:
                self.logger.warning(
                    "Received zero price for asset %s, skipping calculation",
                    asset["symbol"]
                )
                change = 0
            else:
                price_delta = current_price - asset["price"][-1 - data_points]
                change = price_delta / current_price

            self.logger.debug(
                "Calculated asset: %s for interval: %s with change: %s",
                asset["symbol"],
                interval,
                change,
            )

            # Set last change for next interval iteration
            asset[interval]["change_last"] = asset[interval]["change_current"]

            # Stores change for the current interval into asset dict.
            asset[interval]["change_current"] = change

        return asset

    def reset_prices_data_when_due(
        self,
        initial_time,
        current_time,
        reset_interval,
        extract_interval,
        assets,
        chart_intervals,
    ):
        if current_time - initial_time > reset_interval:

            message = "Emptying price data to prevent memory errors."
            self.logger.debug(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"
            for interval in chart_intervals:
                lastInterval = interval

            data_points = chart_intervals[lastInterval]["value"] // extract_interval

            for asset in assets:
                asset["price"] = asset["price"][-1 - data_points :]

            initial_time = current_time

        return initial_time

    def add_new_asset_listings(
        self,
        initial_assets,
        filtered_assets,
        exchange_assets,
        watchlist,
        blacklist,
        pairs_of_interest,
        chart_intervals,
    ):

        if len(initial_assets) >= len(exchange_assets):
            # If initial_assets has more than assets we just ignore it
            self.logger.debug("No new listing found.")
            return filtered_assets

        init_symbols = [asset["symbol"] for asset in initial_assets]
        retrieved_symbols_to_add = [
            exchange_asset["symbol"]
            for exchange_asset in exchange_assets
            if exchange_asset["symbol"] not in init_symbols
        ]

        self.logger.debug("New listings found: %s.", retrieved_symbols_to_add)

        filtered_symbols_to_add = []
        for symbol in retrieved_symbols_to_add:
            if self.is_symbol_valid(symbol, watchlist, blacklist, pairs_of_interest):
                filtered_symbols_to_add.append(symbol)
                filtered_assets.append(self.create_new_asset(symbol, chart_intervals))

        self.logger.debug("Filtered new listings found: %s.", filtered_symbols_to_add)

        if len(filtered_symbols_to_add) > 0:
            self.report_generator.send_new_listings(filtered_symbols_to_add)

        return filtered_assets

    def check_and_send_top_pump_dump_statistics_report(
        self,
        assets,
        current_time,
        top_report_intervals,
        top_pump_enabled,
        top_dump_enabled,
        additional_stats_enabled,
        no_of_reported_coins,
    ):

        for interval in top_report_intervals:

            if (
                current_time
                > top_report_intervals[interval]["start"]
                + top_report_intervals[interval]["value"]
                + 1
            ):
            
                # Update time for new trigger, rounded down to nearest interval. Avoid delay over time.
                top_report_intervals[interval]["start"] = current_time - (current_time % ConversionUtils.duration_to_seconds(interval))

                self.logger.debug(
                    "Sending out top pump dump report. Interval: %s.", interval
                )

                self.report_generator.send_top_pump_dump_statistics_report(
                    assets,
                    interval,
                    top_pump_enabled,
                    top_dump_enabled,
                    additional_stats_enabled,
                    no_of_reported_coins,
                )

    def run(self):

        initial_assets = self.retrieve_exchange_assets(self.api_url)

        filtered_assets = self.filter_and_convert_assets(
            initial_assets,
            self.watchlist,
            self.blacklist,
            self.pairs_of_interest,
            self.chart_intervals,
        )

        message = "*Bot has started.* Following _{0}_ pairs."
        self.telegram.send_generic_message(message, len(filtered_assets))
        if self.telegram.is_alert_chat_enabled():
            self.telegram.send_generic_message(
                message,
                len(filtered_assets),
                is_alert_chat=True,
            )

        while True:
            start_loop_time = time.time()
            loop_time = int(start_loop_time)

            exchange_assets = self.retrieve_exchange_assets(self.api_url)

            if self.check_new_listing_enabled:
                filtered_assets = self.add_new_asset_listings(
                    initial_assets,
                    filtered_assets,
                    exchange_assets,
                    self.watchlist,
                    self.blacklist,
                    self.pairs_of_interest,
                    self.chart_intervals,
                )
                # Reset initial exchange asset
                initial_assets = exchange_assets

            self.update_all_monitored_assets_and_send_news_messages(
                filtered_assets,
                exchange_assets,
                loop_time,
                self.dump_enabled,
                self.chart_intervals,
                self.extract_interval,
                self.outlier_intervals,
            )

            self.check_and_send_top_pump_dump_statistics_report(
                filtered_assets,
                loop_time,
                self.top_report_intervals,
                self.top_pump_enabled,
                self.top_dump_enabled,
                self.additional_statistics_enabled,
                self.no_of_reported_coins,
            )

            self.initial_time = self.reset_prices_data_when_due(
                self.initial_time,
                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()

            self.logger.info(
                "Extracting loop started at %d and finished at %d. Taking %f seconds.",
                start_loop_time,
                end_loop_time,
                end_loop_time - start_loop_time,
            )

            if end_loop_time < start_loop_time + self.extract_interval:
                sleep_time = start_loop_time + self.extract_interval - end_loop_time
                self.logger.debug("Now sleeping %f seconds.", sleep_time)
                sleep(sleep_time)


================================================
FILE: alerter/__init__.py
================================================
from .BinancePumpAndDumpAlerter import BinancePumpAndDumpAlerter


================================================
FILE: config.yml
================================================
# Binance Pump and Dump Alert Configuration

# Using Spot API with url set to https://api.binance.com/api/v3/ticker/price
# Using Futures API with url set to: https://fapi.binance.com/fapi/v1/ticker/price
apiUrl: https://api.binance.com/api/v3/ticker/price

# Intervals which are monitored. Add or remove intervals as you like.
chartIntervals:
  - 1s
  - 5s
  - 15s
  - 30s
  - 1m
  - 5m
  - 15m
  - 30m
  - 1h
  - 3h
  - 6h

# Values in % at which an alert is triggered. Ensure interval exists in chartIntervals as well.
outlierIntervals:
  "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

# Insert telegramToken obtained from @BotFather here
telegramToken: mySecretToken
# Insert Chat ID obtained from @get_id_bot here
telegramChatId: mySecretChatId
# Insert Chat ID for top pump dump alert, if left on `0` it'll send the message to telegram chat id
telegramAlertChatId: 0

# Useful Params

# Interval between each price extract from Binance REST API
extractInterval: 1s

# Watchlist only mode, if enabled, ONLY pairs in watchlist will be monitored
# E.g. 'ADAUSDT', 'ETHUSDT'
# watchlist:
#  - ADAUSDT
#  - ETHUSDT

# Blacklist only mode, if enabled, pairs in blacklist will be IGNORED it DOES NOT IMPACT pairsOfInterest
blacklist:
  - NBTUSDT
#  - ETHUSDT

# List the trading currency you are interested in. Other options include 'BUSD', 'BTC' , 'ETH'
pairsOfInterest:
  - USDT

# Feature params

# Determine whether to look at DUMPs
dumpEnabled: True

# Top Pump & Dump Feature Params

# Set to false if not interested in top pump info
topPumpEnabled: True
# Set to false if not interested in top dump info
topDumpEnabled: True
# Set to false if not interested in net movement of coins
additionalStatsEnabled: True
# Top X amount of coins shown in the interval report, adjust to show more or less within the timeframe
noOfReportedCoins: 5
# Intervals for top pump and dump to be sent, ensure its in chartIntervals + outlierIntervals as well
topReportIntervals:
  - 3h
  - 6h

# Define your own bot emojis.
botEmoji: ! "\U0001F916" # 🤖
pumpEmoji: ! "\U0001F7E2" # 🟢 or '\U0001F4C8' 📈 '\U0001F53C'🔼
dumpEmoji: ! "\U0001F534" # 🔴 or '\U0001F4C9' 📉 '\U0001F53D'🔽
topEmoji: ! "\U0001F3C6" # 🏆
newsEmoji: ! "\U0001F4F0" # 📰 or '\U0001F680' 🚀

# Debug Params (Avoid touching it if there's no issues)

# If False we do not print unnecessary messages
debug: False
# 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
# Disables checking and adding of new listing pairs
checkNewListingEnabled: True
# Disables rounding to nearest hour for first TPD if false
topReportNearestHour: True


================================================
FILE: docker-compose.yml
================================================
version: "3.8"

services:
  binance-pump-alerts:
    build:
      context: .
      dockerfile: "./Dockerfile"
    image: patbaumgartner/binance-pump-alerts:latest
    # image: brianleect/binance-pump-alerts:latest
    restart: "no"
    environment:
      - TELEGRAM_TOKEN=${TELEGRAM_TOKEN}
      - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
      - TELEGRAM_ALERT_CHAT_ID=${TELEGRAM_ALERT_CHAT_ID}
      - PUMP_EMOJI=${PUMP_EMOJI}
      - DUMP_EMOJI=${DUMP_EMOJI}
      - NO_OF_REPORTED_COINS=${NO_OF_REPORTED_COINS}
      - DEBUG=${DEBUG}
      - RESET_INTERVAL=${RESET_INTERVAL}


================================================
FILE: entrypoint.sh
================================================
#!/bin/bash
set -e

process_config() {

    # Please mount your own config file into the container for unsupported env parameters!
    # Currently not supported config parameters: chartIntervals, outlierIntervals, pairsOfInterest, watchlist, blacklist, topReportIntervals

    if [[ -n $API_URL ]]; then
        sed -i "s/apiUrl.*/apiUrl: ${API_URL}/" config.yml
    fi

    if [[ -n $TELEGRAM_TOKEN ]]; then
        sed -i "s/telegramToken.*/telegramToken: ${TELEGRAM_TOKEN}/" config.yml
    fi
    if [[ -n $TELEGRAM_CHAT_ID ]]; then
        sed -i "s/telegramChatId.*/telegramChatId: ${TELEGRAM_CHAT_ID}/" config.yml
    fi
    if [[ -n $TELEGRAM_ALERT_CHAT_ID ]]; then
        sed -i "s/telegramAlertChatId.*/telegramAlertChatId: ${TELEGRAM_ALERT_CHAT_ID}/" config.yml
    fi

    if [[ -n $EXTRACT_INTERVAL ]]; then
        sed -i "s/extractInterval.*/extractInterval: ${EXTRACT_INTERVAL}/" config.yml
    fi

    if [[ -n $DUMP_ENABLED ]]; then
        sed -i "s/dumpEnabled.*/dumpEnabled: ${DUMP_ENABLED}/" config.yml
    fi
    
    if [[ -n $TOP_PUMP_ENABLED ]]; then
        sed -i "s/topPumpEnabled.*/topPumpEnabled: ${TOP_PUMP_ENABLED}/" config.yml
    fi
    if [[ -n $TOP_DUMP_ENABLED ]]; then
        sed -i "s/topDumpEnabled.*/topDumpEnabled: ${TOP_DUMP_ENABLED}/" config.yml
    fi
    if [[ -n $ADDITIONAL_STATS_ENABLED ]]; then
        sed -i "s/additionalStatsEnabled.*/additionalStatsEnabled: ${ADDITIONAL_STATS_ENABLED}/" config.yml
    fi
    if [[ -n $NO_OF_REPORTED_COINS ]]; then
        sed -i "s/noOfReportedCoins.*/noOfReportedCoins: ${NO_OF_REPORTED_COINS}/" config.yml
    fi

    if [[ -n $BOT_EMOJI ]]; then
        sed -i "s/botEmoji.*/botEmoji: ${BOT_EMOJI}/" config.yml
    fi
    if [[ -n $PUMP_EMOJI ]]; then
        sed -i "s/pumpEmoji.*/pumpEmoji: ${PUMP_EMOJI}/" config.yml
    fi
    if [[ -n $DUMP_EMOJI ]]; then
        sed -i "s/dumpEmoji.*/dumpEmoji: ${DUMP_EMOJI}/" config.yml
    fi
    if [[ -n $TOP_EMOJI ]]; then
        sed -i "s/topEmoji.*/topEmoji: ${TOP_EMOJI}/" config.yml
    fi
    if [[ -n $NEWS_EMOJI ]]; then
        sed -i "s/newsEmoji.*/newsEmoji: ${NEWS_EMOJI}/" config.yml
    fi

    if [[ -n $DEBUG ]]; then
        sed -i "s/debug.*/debug: ${DEBUG}/" config.yml
    fi
    if [[ -n $ALERT_SKIP_THRESHOLD ]]; then
        sed -i "s/alertSkipThreshold.*/alertSkipThreshold: ${ALERT_SKIP_THRESHOLD}/" config.yml
    fi
    if [[ -n $RESET_INTERVAL ]]; then
        sed -i "s/resetInterval.*/resetInterval: ${RESET_INTERVAL}/" config.yml
    fi
    if [[ -n $PRICE_RETRY_INTERVAL ]]; then
        sed -i "s/priceRetryInterval.*/priceRetryInterval: ${PRICE_RETRY_INTERVAL}/" config.yml
    fi
    if [[ -n $CHECK_NEW_LISTING_ENABLED ]]; then
        sed -i "s/checkNewListingEnabled.*/checkNewListingEnabled: ${CHECK_NEW_LISTING_ENABLED}/" config.yml
    fi
    if [[ -n $TOP_REPORT_NEAREST_HOUR ]]; then
        sed -i "s/topReportNearestHour.*/topReportNearestHour: ${TOP_REPORT_NEAREST_HOUR}/" config.yml
    fi
}

# Adding parameters set from the environment variables to the config yaml file.
process_config

echo 
echo "Running $@"
echo

exec "$@"


================================================
FILE: pumpAlerts.py
================================================
import colorlog, logging
import os
import yaml

from alerter import BinancePumpAndDumpAlerter
from reporter import ReportGenerator
from sender import TelegramSender
from utils import ConversionUtils

# Read config
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
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
bold_seq = "\033[1m"
log_format = "[%(asctime)s] %(processName)-12s %(threadName)-23s %(levelname)-8s %(name)-23s %(message)s"
color_format = f"{bold_seq} " "%(log_color)s " f"{log_format}"

colorlog.basicConfig(
    # Define logging level according to the configuration
    level=logging.DEBUG if config["debug"] is True else logging.INFO,
    # Declare the object we created to format the log messages
    format=color_format,
    # Declare handlers for the Console
    handlers=[logging.StreamHandler()],
)

# Define your logger name
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)


def main():
    telegram = TelegramSender(
        token=config["telegramToken"],
        chat_id=config["telegramChatId"],
        alert_chat_id=config["telegramAlertChatId"]
        if "telegramAlertChatId" in config and config["telegramAlertChatId"] != 0
        else config["telegramChatId"],
        bot_emoji=config["botEmoji"],
        top_emoji=config["topEmoji"],
        news_emoji=config["newsEmoji"],
    )

    reporter = ReportGenerator(
        telegram=telegram,
        alert_skip_threshold=config["alertSkipThreshold"],
        pump_emoji=config["pumpEmoji"],
        dump_emoji=config["dumpEmoji"],
    )

    alerter = BinancePumpAndDumpAlerter(
        api_url=config["apiUrl"],
        watchlist=[] if "watchlist" not in config else config["watchlist"],
        blacklist=[] if "blacklist" not in config else config["blacklist"],
        pairs_of_interest=config["pairsOfInterest"],
        chart_intervals=config["chartIntervals"],
        outlier_intervals=config["outlierIntervals"],
        top_report_intervals=config["topReportIntervals"],
        extract_interval=ConversionUtils.duration_to_seconds(config["extractInterval"]),
        retry_interval=ConversionUtils.duration_to_seconds(
            config["priceRetryInterval"]
        ),
        reset_interval=ConversionUtils.duration_to_seconds(config["resetInterval"]),
        top_pump_enabled=config["topPumpEnabled"],
        top_dump_enabled=config["topDumpEnabled"],
        additional_statistics_enabled=config["additionalStatsEnabled"],
        no_of_reported_coins=config["noOfReportedCoins"],
        dump_enabled=config["dumpEnabled"],
        check_new_listing_enabled=config["checkNewListingEnabled"],
        top_report_nearest_hour=config["topReportNearestHour"],
        telegram=telegram,
        report_generator=reporter,
    )

    alerter.run()


if __name__ == "__main__":
    main()


================================================
FILE: reporter/ReportGenerator.py
================================================
import logging

from datetime import datetime


class ReportGenerator:
    def __init__(
        self,
        telegram,
        alert_skip_threshold,
        pump_emoji="\U0001F7E2",  # 🟢
        dump_emoji="\U0001F534",  # 🔴
    ):
        self.telegram = telegram
        self.alert_skip_threshold = alert_skip_threshold
        self.pump_emoji = pump_emoji
        self.dump_emoji = dump_emoji

        self.logger = logging.getLogger("report-generator")

    def send_pump_message(self, symbol, interval, change, price):
        self.telegram.send_message(
            """\
{0} *{1} [{2} Interval]* | Change: _{3:.3f}%_ | Price: _{4:.10f}_

Open in [Binance Spot](https://www.binance.com/en/trade/{1})\
            """.format(
                self.pump_emoji, symbol, interval, change * 100, price
            ),
            is_alert_chat=False,
        )

    def send_dump_message(self, symbol, interval, change, price):
        self.telegram.send_message(
            """\
{0} *{1} [{2} Interval]* | Change: _{3:.3f}%_ | Price: _{4:.10f}_

Open in [Binance Spot](https://www.binance.com/en/trade/{1})\
            """.format(
                self.dump_emoji, symbol, interval, change * 100, price
            ),
            is_alert_chat=False,
        )

    def send_new_listings(self, symbols_to_add):
        message = """\
*New Listings*"
{0} new pairs found, adding to monitored list."

*Adding Pairs:*\
            """.format(
            len(symbols_to_add)
        )

        message += "\n"
        for symbol in symbols_to_add:
            message += "- _{0}_\n".format(symbol)

        self.telegram.send_news_message(message, is_alert_chat=True)

    def send_pump_dump_message(
        self,
        asset,
        chart_intervals,
        outlier_intervals,
        current_time,
        dump_enabled=True,
    ):
        change_biggest_delta = 0
        no_of_alerts = 0
        message = ""

        for interval in chart_intervals:

            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

            if change > 0:
                message += "{0} *{1} Interval* | Change: _{2:.3f}%_\n".format(
                    self.pump_emoji,
                    interval,
                    change * 100,
                    asset["price"][-1],
                )

            if change < 0 and dump_enabled:
                message += "{0} *{1} Interval* | Change: _{2:.3f}%_\n".format(
                    self.dump_emoji,
                    interval,
                    change * 100,
                    asset["price"][-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

        news_message = """\
*{0}* | {1} Alert(s) | {2}

Price: _{3:.10f}_ | Volume: _{4}_

{5}
Open in [Binance Spot](https://www.binance.com/en/trade/{0})\
            """.format(
            asset["symbol"],
            no_of_alerts,
            datetime.fromtimestamp(current_time).strftime("%Y-%m-%d %H:%M:%S"),
            asset["price"][-1],
            0,
            message,
        )

        self.telegram.send_news_message(news_message)

    def send_top_pump_dump_statistics_report(
        self,
        assets,
        interval,
        top_pump_enabled=True,
        top_dump_enabled=True,
        additional_stats_enabled=True,
        no_of_reported_coins=5,
    ):

        if not top_pump_enabled or not top_dump_enabled:
            return

        message = "*[{0} Interval]*\n\n".format(interval)

        if top_pump_enabled:
            pump_sorted_list = sorted(
                assets,
                key=lambda item: item[interval]["change_current"],
                reverse=True,
            )[0:no_of_reported_coins]

            message += "{0} *Top {1} Pumps*\n".format(
                self.pump_emoji, no_of_reported_coins
            )

            for asset in pump_sorted_list:
                message += "- {0}: _{1:.2f}_%\n".format(
                    asset["symbol"], asset[interval]["change_current"] * 100
                )
            message += "\n"

        if top_dump_enabled:
            dump_sorted_list = sorted(
                assets, key=lambda item: item[interval]["change_current"]
            )[0:no_of_reported_coins]

            message += "{0} *Top {1} Dumps*\n".format(
                self.dump_emoji, no_of_reported_coins
            )

            for asset in dump_sorted_list:
                message += "- {0}: _{1:.2f}_%\n".format(
                    asset["symbol"], asset[interval]["change_current"] * 100
                )

        if additional_stats_enabled:
            if top_pump_enabled or top_dump_enabled:
                message += "\n"
            message += self.generate_additional_statistics_report(assets, interval)

        self.telegram.send_report_message(message, is_alert_chat=True)

    def generate_additional_statistics_report(self, assets, interval):
        up = 0
        down = 0
        sum_change = 0

        for asset in assets:
            if asset[interval]["change_current"] > 0:
                up += 1
            elif asset[interval]["change_current"] < 0:
                down += 1

            sum_change += asset[interval]["change_current"]

        avg_change = sum_change / len(assets)

        return "*Average Change:* {0:.2f}%\n {1} {2} / {3} {4}".format(
            avg_change * 100,
            self.pump_emoji,
            up,
            self.dump_emoji,
            down,
        )


================================================
FILE: reporter/__init__.py
================================================
from .ReportGenerator import ReportGenerator


================================================
FILE: requirements.txt
================================================
colorlog
requests
pyyaml
python-telegram-bot==13.13


================================================
FILE: sender/TelegramSender.py
================================================
import logging

from concurrent.futures import ThreadPoolExecutor
from telegram import Bot, ParseMode
from telegram.error import RetryAfter
from telegram.utils.request import Request
from time import sleep


class TelegramSender:
    def __init__(
        self,
        token,
        chat_id,
        alert_chat_id=0,
        bot_emoji="\U0001F916",  # 🤖
        top_emoji="\U0001F3C6",  # 🏆
        news_emoji="\U0001F4F0",  # 📰
    ):

        self.token = token
        self.chat_id = chat_id
        self.alert_chat_id = alert_chat_id

        self.bot_emoji = bot_emoji
        self.top_emoji = top_emoji
        self.news_emoji = news_emoji

        self.telegram_executor = ThreadPoolExecutor(max_workers=3)

        self.request = Request(con_pool_size=3)
        self.bot = Bot(self.token, request=self.request)

        self.logger = logging.getLogger("telegram-sender")

    def is_alert_chat_enabled(self):
        return self.alert_chat_id != 0 and self.alert_chat_id != self.chat_id

    def send_message(self, message, is_alert_chat=False):
        chat_id = self.chat_id if not is_alert_chat else self.alert_chat_id

        def push_message(bot, chat_id, message):
            self.logger.info(message)

            try:
                bot.send_message(
                    chat_id=chat_id,
                    text=message,
                    parse_mode=ParseMode.MARKDOWN,
                    disable_web_page_preview=True,
                )
            except RetryAfter as e:
                self.logger.error(
                    "Flood limit is exceeded. Sleep {} seconds.", e.retry_after
                )
                sleep(e.retry_after)
                # Resend message to the queue
                self.send_message(message, is_alert_chat)
            except Exception as e:
                self.logger.error(str(e))

        self.telegram_executor.submit(
            lambda p: push_message(*p), (self.bot, chat_id, message)
        )

    def send_generic_message(self, message, args=None, is_alert_chat=False):
        if args is not None:
            message = message.format(args)
        self.send_message(self.bot_emoji + " " + message, is_alert_chat)

    def send_report_message(self, message, args=None, is_alert_chat=False):
        if args is not None:
            message = message.format(args)
        self.send_message(self.top_emoji + " " + message, is_alert_chat)

    def send_news_message(self, message, args=None, is_alert_chat=False):
        if args is not None:
            message = message.format(args)
        self.send_message(self.news_emoji + " " + message, is_alert_chat)


================================================
FILE: sender/__init__.py
================================================
from .TelegramSender import TelegramSender


================================================
FILE: utils/ConversionUtils.py
================================================
class ConversionUtils:
    @staticmethod
    def duration_to_seconds(duration):
        unit = duration[-1]
        if unit == "s":
            unit = 1
        elif unit == "m":
            unit = 60
        elif unit == "h":
            unit = 3600

        return int(duration[:-1]) * unit


================================================
FILE: utils/__init__.py
================================================
from .ConversionUtils import ConversionUtils
Download .txt
gitextract_g_svvtu5/

├── .dockerignore
├── .github/
│   └── workflows/
│       └── main.yml
├── .gitignore
├── Dockerfile
├── README.md
├── alerter/
│   ├── BinancePumpAndDumpAlerter.py
│   └── __init__.py
├── config.yml
├── docker-compose.yml
├── entrypoint.sh
├── pumpAlerts.py
├── reporter/
│   ├── ReportGenerator.py
│   └── __init__.py
├── requirements.txt
├── sender/
│   ├── TelegramSender.py
│   └── __init__.py
└── utils/
    ├── ConversionUtils.py
    └── __init__.py
Download .txt
SYMBOL INDEX (31 symbols across 5 files)

FILE: alerter/BinancePumpAndDumpAlerter.py
  class BinancePumpAndDumpAlerter (line 9) | class BinancePumpAndDumpAlerter:
    method __init__ (line 10) | def __init__(
    method extract_ticker_data (line 79) | def extract_ticker_data(symbol, assets):
    method create_new_asset (line 85) | def create_new_asset(symbol, chart_intervals):
    method retrieve_exchange_assets (line 96) | def retrieve_exchange_assets(self, api_url):
    method is_symbol_valid (line 111) | def is_symbol_valid(self, symbol, watchlist, blacklist, pairs_of_inter...
    method filter_and_convert_assets (line 151) | def filter_and_convert_assets(
    method update_all_monitored_assets_and_send_news_messages (line 165) | def update_all_monitored_assets_and_send_news_messages(
    method calculate_asset_change (line 193) | def calculate_asset_change(
    method reset_prices_data_when_due (line 241) | def reset_prices_data_when_due(
    method add_new_asset_listings (line 270) | def add_new_asset_listings(
    method check_and_send_top_pump_dump_statistics_report (line 308) | def check_and_send_top_pump_dump_statistics_report(
    method run (line 344) | def run(self):

FILE: pumpAlerts.py
  function main (line 45) | def main():

FILE: reporter/ReportGenerator.py
  class ReportGenerator (line 6) | class ReportGenerator:
    method __init__ (line 7) | def __init__(
    method send_pump_message (line 21) | def send_pump_message(self, symbol, interval, change, price):
    method send_dump_message (line 33) | def send_dump_message(self, symbol, interval, change, price):
    method send_new_listings (line 45) | def send_new_listings(self, symbols_to_add):
    method send_pump_dump_message (line 61) | def send_pump_dump_message(
    method send_top_pump_dump_statistics_report (line 140) | def send_top_pump_dump_statistics_report(
    method generate_additional_statistics_report (line 193) | def generate_additional_statistics_report(self, assets, interval):

FILE: sender/TelegramSender.py
  class TelegramSender (line 10) | class TelegramSender:
    method __init__ (line 11) | def __init__(
    method is_alert_chat_enabled (line 36) | def is_alert_chat_enabled(self):
    method send_message (line 39) | def send_message(self, message, is_alert_chat=False):
    method send_generic_message (line 66) | def send_generic_message(self, message, args=None, is_alert_chat=False):
    method send_report_message (line 71) | def send_report_message(self, message, args=None, is_alert_chat=False):
    method send_news_message (line 76) | def send_news_message(self, message, args=None, is_alert_chat=False):

FILE: utils/ConversionUtils.py
  class ConversionUtils (line 1) | class ConversionUtils:
    method duration_to_seconds (line 3) | def duration_to_seconds(duration):
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (52K chars).
[
  {
    "path": ".dockerignore",
    "chars": 73,
    "preview": ".env\n.idea\n.gitignore\n\nvenv\n\nconfig.dev.yml\ndocker-compose.yml\nReadme.md\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 1395,
    "preview": "# This is a basic workflow to help you get started with Actions\n\nname: Publish Docker image\n\n# Controls when the workflo"
  },
  {
    "path": ".gitignore",
    "chars": 5738,
    "preview": "\nconfig.dev.yml\n\n# Created by https://www.toptal.com/developers/gitignore/api/python,pycharm+all,intellij+all,visualstud"
  },
  {
    "path": "Dockerfile",
    "chars": 1335,
    "preview": "FROM python:3.9-slim-buster as base\n\n# Setup env\nENV LANG C.UTF-8\nENV LC_ALL C.UTF-8\nENV PYTHONDONTWRITEBYTECODE 1\nENV P"
  },
  {
    "path": "README.md",
    "chars": 5036,
    "preview": "# Binance Pump Alerts\n\nBPA is a simple application which gets the price data from Binance Spot or Futures API and sends "
  },
  {
    "path": "alerter/BinancePumpAndDumpAlerter.py",
    "chars": 14991,
    "preview": "import logging\r\nimport requests\r\nimport time\r\n\r\nfrom time import sleep\r\nfrom utils import ConversionUtils\r\n\r\n\r\nclass Bin"
  },
  {
    "path": "alerter/__init__.py",
    "chars": 65,
    "preview": "from .BinancePumpAndDumpAlerter import BinancePumpAndDumpAlerter\n"
  },
  {
    "path": "config.yml",
    "chars": 2990,
    "preview": "# Binance Pump and Dump Alert Configuration\n\n# Using Spot API with url set to https://api.binance.com/api/v3/ticker/pric"
  },
  {
    "path": "docker-compose.yml",
    "chars": 575,
    "preview": "version: \"3.8\"\n\nservices:\n  binance-pump-alerts:\n    build:\n      context: .\n      dockerfile: \"./Dockerfile\"\n    image:"
  },
  {
    "path": "entrypoint.sh",
    "chars": 3117,
    "preview": "#!/bin/bash\nset -e\n\nprocess_config() {\n\n    # Please mount your own config file into the container for unsupported env p"
  },
  {
    "path": "pumpAlerts.py",
    "chars": 3246,
    "preview": "import colorlog, logging\nimport os\nimport yaml\n\nfrom alerter import BinancePumpAndDumpAlerter\nfrom reporter import Repor"
  },
  {
    "path": "reporter/ReportGenerator.py",
    "chars": 6502,
    "preview": "import logging\n\nfrom datetime import datetime\n\n\nclass ReportGenerator:\n    def __init__(\n        self,\n        telegram,"
  },
  {
    "path": "reporter/__init__.py",
    "chars": 45,
    "preview": "from .ReportGenerator import ReportGenerator\n"
  },
  {
    "path": "requirements.txt",
    "chars": 52,
    "preview": "colorlog\nrequests\npyyaml\npython-telegram-bot==13.13\n"
  },
  {
    "path": "sender/TelegramSender.py",
    "chars": 2636,
    "preview": "import logging\n\nfrom concurrent.futures import ThreadPoolExecutor\nfrom telegram import Bot, ParseMode\nfrom telegram.erro"
  },
  {
    "path": "sender/__init__.py",
    "chars": 43,
    "preview": "from .TelegramSender import TelegramSender\n"
  },
  {
    "path": "utils/ConversionUtils.py",
    "chars": 305,
    "preview": "class ConversionUtils:\r\n    @staticmethod\r\n    def duration_to_seconds(duration):\r\n        unit = duration[-1]\r\n        "
  },
  {
    "path": "utils/__init__.py",
    "chars": 45,
    "preview": "from .ConversionUtils import ConversionUtils\n"
  }
]

About this extraction

This page contains the full source code of the brianleect/binance-pump-alerts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (47.1 KB), approximately 11.8k tokens, and a symbol index with 31 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!