├── config ├── coins.example.yml └── config.example.yml ├── requirements.txt ├── trades ├── dca-tracker │ ├── BTC.png │ ├── DOT.png │ ├── ETH.png │ ├── SOL.png │ └── XLM.png └── metrics.py ├── auth ├── auth.example.yml └── binance_auth.py ├── system ├── load_data.py ├── logger.py └── store_order.py ├── service ├── email_service.py └── binance_service.py ├── README.md ├── .gitignore └── main.py /config/coins.example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | COINS: 3 | - BTC 4 | - ETH 5 | - DOT 6 | - SOL 7 | - XLM 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | binance==0.3 2 | matplotlib==3.4.3 3 | numpy==1.21.2 4 | python_binance==0.7.9 5 | PyYAML==6.0 6 | -------------------------------------------------------------------------------- /trades/dca-tracker/BTC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberPunkMetalHead/binance-dca-crypto-trading-bot/HEAD/trades/dca-tracker/BTC.png -------------------------------------------------------------------------------- /trades/dca-tracker/DOT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberPunkMetalHead/binance-dca-crypto-trading-bot/HEAD/trades/dca-tracker/DOT.png -------------------------------------------------------------------------------- /trades/dca-tracker/ETH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberPunkMetalHead/binance-dca-crypto-trading-bot/HEAD/trades/dca-tracker/ETH.png -------------------------------------------------------------------------------- /trades/dca-tracker/SOL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberPunkMetalHead/binance-dca-crypto-trading-bot/HEAD/trades/dca-tracker/SOL.png -------------------------------------------------------------------------------- /trades/dca-tracker/XLM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberPunkMetalHead/binance-dca-crypto-trading-bot/HEAD/trades/dca-tracker/XLM.png -------------------------------------------------------------------------------- /auth/auth.example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # CLIENT DETAILS 3 | binance_api: "BINANCE_API_KEY" 4 | binance_secret: "BINANCE_SECRET" 5 | binance_tld: "com" 6 | -------------------------------------------------------------------------------- /system/load_data.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | def load_data(file): 4 | with open(file) as file: 5 | return yaml.load(file, Loader=yaml.FullLoader) 6 | -------------------------------------------------------------------------------- /system/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | FORMAT = '%(levelname)s: %(asctime)s | FILE: %(filename)s:%(lineno)d | MESSAGE: %(message)s' 3 | logging.basicConfig(filename = 'log.log', level=logging.INFO, format = FORMAT ) 4 | logger = logging.getLogger(__name__) 5 | -------------------------------------------------------------------------------- /config/config.example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | TRADE_OPTIONS: 3 | # In your pairing coin 4 | QUANTITY: 15 5 | # BTCUSDT will be bought for example 6 | PAIRING: USDT 7 | # HOW OFTEN SHOULD THE BOT DCA, IN DAYS 8 | DCA_EVERY: 7 9 | TEST: True 10 | SEND_NOTIFICATIONS: True 11 | EMAIL_ADDRESS: your@email.address 12 | EMAIL_PASSWORD: your_pass 13 | -------------------------------------------------------------------------------- /system/store_order.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def store_order(file, order): 5 | """ 6 | Save order into local json file 7 | """ 8 | with open(file, 'w') as f: 9 | json.dump(order, f, indent=4) 10 | 11 | def load_order(file): 12 | """ 13 | Update Json file 14 | """ 15 | with open(file, "r+") as f: 16 | return json.load(f) 17 | -------------------------------------------------------------------------------- /auth/binance_auth.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | from binance.client import Client 4 | from binance.exceptions import BinanceAPIException 5 | 6 | 7 | def load_binance_creds(file): 8 | with open(file) as file: 9 | auth = yaml.load(file, Loader=yaml.FullLoader) 10 | 11 | return Client(api_key = auth['binance_api'], api_secret = auth['binance_secret'], tld = "com" if 'binance_tld' not in auth else auth['binance_tld']) 12 | -------------------------------------------------------------------------------- /service/email_service.py: -------------------------------------------------------------------------------- 1 | import smtplib, ssl 2 | 3 | from system.load_data import * 4 | from system.logger import logger 5 | 6 | config = load_data('config/config.yml') 7 | 8 | def send_notification(message): 9 | 10 | port = 465 # For SSL 11 | smtp_server = "smtp.gmail.com" 12 | sent_from = config['EMAIL_ADDRESS'] 13 | to = [config['EMAIL_ADDRESS']] 14 | subject = f'Notification from your DCA bot' 15 | body = message 16 | message = 'Subject: {}\n\n{}'.format(subject, body) 17 | 18 | try: 19 | context = ssl.create_default_context() 20 | with smtplib.SMTP_SSL(smtp_server, port, context=context) as server: 21 | server.login(sent_from, config['EMAIL_PASSWORD']) 22 | server.sendmail(sent_from, to, message) 23 | 24 | except Exception as e: 25 | logger.error(e) 26 | -------------------------------------------------------------------------------- /trades/metrics.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import yaml 4 | 5 | from system.load_data import load_data 6 | 7 | 8 | def get_all_order_prices(order): 9 | """ 10 | Takes a dict of all orders and organises the last_price 11 | """ 12 | coin_stats = {} 13 | for coin in order: 14 | coin_stats[coin] = [] 15 | for item in order[coin]['orders']: 16 | coin_stats[coin].append(float(item['price'])) 17 | 18 | return coin_stats 19 | 20 | 21 | def calculate_avg_dca(data): 22 | """ 23 | Takes a dict of lists cotaining last prices for each coin DCad 24 | And calculates the average DCA price for each coin 25 | """ 26 | avg_dca = {} 27 | for coin in data: 28 | avg_dca[coin] = np.array(data[coin]) 29 | avg_dca[coin] = np.average(avg_dca[coin]) 30 | 31 | return avg_dca 32 | 33 | 34 | def plot_dca_history(data, average): 35 | for coin in data: 36 | plt.plot(data[coin]) 37 | plt.title(f'{coin} | Average {round(average[coin], 3)}') 38 | plt.savefig(f'trades/dca-tracker/{coin}.png') 39 | plt.clf() 40 | #plt.show() 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # binance-dca-crypto-trading-bot 2 | This is a simple DCA crypto trading bot for Binance. It allows you to DCA any number of coins at a given interval. 3 | This DCA bot comes with better reporting than what you see on exchanges like Binance, by calculating your average DCA with each purchase. 4 | It also presents a historical graph of each DCA so you can compare against the market in the long run. 5 | 6 | This crypto trading bot is meant to accumulate coins on Binance, and therefore has no sell strategy. This needs to be manually managed by the user. 7 | 8 |
15 | 16 | **For a step-by-step guide on how to set it up and configure please see the guide here:** [DCA crypto trading bot Binance](https://www.cryptomaton.org/2021/12/22/dca-crypto-trading-bot-binance/) 17 | 18 | 19 |
20 | 21 | **See the video linked below for an explanation and rationale behind the bot. As well as the coding process** 22 | 23 | [](https://youtu.be/3H3WYLOiG4w) 24 | 25 | Want to talk trading bots? Join the discord [https://discord.gg/Ga56KXUUNn](https://discord.gg/Ga56KXUUNn) 26 | -------------------------------------------------------------------------------- /service/binance_service.py: -------------------------------------------------------------------------------- 1 | from system.logger import logger 2 | 3 | from auth.binance_auth import * 4 | from binance.enums import * 5 | 6 | client = load_binance_creds('auth/auth.yml') 7 | 8 | def get_price(coin, pairing): 9 | return client.get_ticker(symbol=coin+pairing)['lastPrice'] 10 | 11 | 12 | def convert_volume(coin, quantity, last_price): 13 | """Converts the volume given in QUANTITY from USDT to the each coin's volume""" 14 | 15 | try: 16 | info = client.get_symbol_info(coin) 17 | step_size = info['filters'][2]['stepSize'] 18 | lot_size = {coin:step_size.index('1') - 1} 19 | 20 | if lot_size[coin] < 0: 21 | lot_size[coin] = 0 22 | 23 | except exception as e: 24 | logger.debug(f'Converted {quantity} {coin} by setting lot size to 0') 25 | lot_size = {coin:0} 26 | 27 | # calculate the volume in coin from QUANTITY in USDT (default) 28 | volume = float(quantity / float(last_price)) 29 | 30 | # define the volume with the correct step size 31 | if coin not in lot_size: 32 | volume = float('{:.1f}'.format(volume)) 33 | 34 | else: 35 | # if lot size has 0 decimal points, make the volume an integer 36 | if lot_size[coin] == 0: 37 | volume = int(volume) 38 | else: 39 | volume = float('{:.{}f}'.format(volume, lot_size[coin])) 40 | 41 | logger.debug(f'Sucessfully converted {quantity} {coin} to {volume} in trading coin') 42 | return volume 43 | 44 | 45 | def create_order(coin, amount): 46 | """ 47 | Creates simple buy order and returns the order 48 | """ 49 | return client.order_market_buy( 50 | symbol=coin, 51 | quantity=amount) 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### Project ### 3 | auth/auth.yml 4 | config/config.yml 5 | config/coins.yml 6 | auth/__pychache__/ 7 | config/__pychache__/ 8 | system/__pychache__/ 9 | trades/__pychache__/ 10 | trades/order.json 11 | log.log 12 | #let's include dca-tracker as an example 13 | 14 | ### Python ### 15 | # Byte-compiled / optimized / DLL files 16 | __pycache__/ 17 | *.py[cod] 18 | *$py.class 19 | 20 | # C extensions 21 | *.so 22 | 23 | # Distribution / packaging 24 | .Python 25 | build/ 26 | develop-eggs/ 27 | dist/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | lib/ 32 | lib64/ 33 | parts/ 34 | sdist/ 35 | var/ 36 | wheels/ 37 | share/python-wheels/ 38 | *.egg-info/ 39 | .installed.cfg 40 | *.egg 41 | MANIFEST 42 | 43 | # PyInstaller 44 | # Usually these files are written by a python script from a template 45 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 46 | *.manifest 47 | *.spec 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | htmlcov/ 55 | .tox/ 56 | .nox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | *.py,cover 64 | .hypothesis/ 65 | .pytest_cache/ 66 | cover/ 67 | 68 | # Translations 69 | *.mo 70 | *.pot 71 | 72 | # Django stuff: 73 | *.log 74 | local_settings.py 75 | db.sqlite3 76 | db.sqlite3-journal 77 | 78 | # Flask stuff: 79 | instance/ 80 | .webassets-cache 81 | 82 | # Scrapy stuff: 83 | .scrapy 84 | 85 | # Sphinx documentation 86 | docs/_build/ 87 | 88 | # PyBuilder 89 | .pybuilder/ 90 | target/ 91 | 92 | # Jupyter Notebook 93 | .ipynb_checkpoints 94 | 95 | # IPython 96 | profile_default/ 97 | ipython_config.py 98 | 99 | # pyenv 100 | # For a library or package, you might want to ignore these files since the code is 101 | # intended to run in multiple environments; otherwise, check them in: 102 | # .python-version 103 | 104 | # pipenv 105 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 106 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 107 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 108 | # install all needed dependencies. 109 | #Pipfile.lock 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .env 123 | .venv 124 | env/ 125 | venv/ 126 | ENV/ 127 | env.bak/ 128 | venv.bak/ 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # Cython debug symbols 152 | cython_debug/ 153 | 154 | # End of https://www.toptal.com/developers/gitignore/api/python 155 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from service.binance_service import * 2 | from system.store_order import * 3 | from system.load_data import * 4 | from service.email_service import * 5 | from trades.metrics import * 6 | 7 | from collections import defaultdict 8 | from datetime import datetime, time 9 | import time 10 | from system.logger import logger 11 | import json 12 | import os.path 13 | 14 | 15 | # load coins to DCA 16 | coins_to_DCA = load_data('config/coins.yml')['COINS'] 17 | # loads local configuration 18 | config = load_data('config/config.yml') 19 | 20 | 21 | def main(): 22 | """ 23 | DCA every x number of days. 24 | """ 25 | while True: 26 | 27 | # load the order file if it exists 28 | if os.path.isfile('trades/order.json'): 29 | order = load_order('trades/order.json') 30 | else: 31 | logger.info("No order file found, creating new file") 32 | order = {} 33 | 34 | pairing = config['TRADE_OPTIONS']['PAIRING'] 35 | qty = config['TRADE_OPTIONS']['QUANTITY'] 36 | frequency = config['TRADE_OPTIONS']['DCA_EVERY'] 37 | test_mode = config['TRADE_OPTIONS']['TEST'] 38 | send_notification_flag = config['SEND_NOTIFICATIONS'] 39 | 40 | if not test_mode: 41 | logger.warning("RUNNING IN LIVE MODE! PAUSING FOR 1 MINUTE") 42 | time.sleep(60) 43 | 44 | # DCA each coin 45 | for coin in coins_to_DCA: 46 | last_price = get_price(coin, pairing) 47 | volume = convert_volume(coin+pairing, qty, last_price) 48 | 49 | try: 50 | # Run a test trade if true 51 | if config['TRADE_OPTIONS']['TEST']: 52 | if coin not in order: 53 | order[coin] = {} 54 | order[coin]["orders"] = [] 55 | 56 | order[coin]["orders"].append({ 57 | 'symbol':coin+pairing, 58 | 'price':last_price, 59 | 'volume':volume, 60 | 'time':datetime.timestamp(datetime.now()) 61 | }) 62 | 63 | logger.info('PLACING TEST ORDER') 64 | 65 | # place a live order if False 66 | else: 67 | if coin not in order: 68 | order[coin] = {} 69 | order[coin]["orders"] = [] 70 | 71 | order[coin]["orders"] = create_order(coin+pairing, volume) 72 | 73 | except Exception as e: 74 | logger.info(e) 75 | 76 | else: 77 | logger.info(f"Order created with {volume} on {coin} at {datetime.now()}") 78 | store_order('trades/order.json', order) 79 | 80 | message = f'DCA complete, bought {coins_to_DCA}. Waiting {frequency} days.' 81 | logger.info(message) 82 | 83 | # sends an e-mail if enabled. 84 | if send_notification_flag: 85 | send_notification(message) 86 | 87 | # report on DCA performance. Files saved in trades/dca-tracker 88 | all_prices = get_all_order_prices(order) 89 | avg_dca = calculate_avg_dca(all_prices) 90 | dca_history = plot_dca_history(all_prices, avg_dca) 91 | 92 | time.sleep(frequency*86400) 93 | 94 | 95 | if __name__ == '__main__': 96 | logger.info('working...') 97 | main() 98 | --------------------------------------------------------------------------------