Build your own crypto index fund.
MIT License
This is a bot which purchases a index of cryptocurrencies. A self-managed Vanguard VTI for crypto assets. Here's a good explanation of why this is probably a good idea. It's designed to be used with a dollar-cost averaging approach, but can be used with one-time deposit as well.
There are bots out there that do this, so why build another? There are missing features I wanted:
Here are some features I didn't want that I could imagine others would like:
Also, I wanted to learn python and learn django, and this was a perfect learning project. Please excuse any beginner-python code (and submit PRs to fix!).
This project uses asdf to install required runtime versions. asdf install
will source versions from .tool-versions
for you.
After installing python and poetry, install python dependencies:
poetry install
You'll need some API keys for the bot to work. First, copy the env template:
cp .env-example .env
Then grab API keys:
If you have externally-held assets, you'll want to represent them in external_portfolio.json
:
cp external_portfolio_example.json external_portfolio.json
Then, you'll be ready to jump into a venv and start playing:
poetry shell
python main.py
Not all configuration options are available via the CLI or the USER_*
env vars. Checkout bot/user.py
for more configuration options.
There are a bunch of configuration options available. I'm not going to document them all here, since they are documented in-code here. You can configure these preferences by using USER_PREFERENCES
in your .env
file.
For instance:
USER_PREFERENCES='{"allocation_drift_percentage_limit":1, "allocation_drift_multiple_limit": 4}
Right now, there are some variables that are hardcoded into the User
class that you may want to change. Assuming you've taken a look at the User
options and configured .env
you can run python main.py --help
to get the following information:
Usage: main.py [OPTIONS] COMMAND [ARGS]...
Tool for building your own crypto index fund.
Options:
-v, --verbose Enables verbose mode.
--help Show this message and exit.
Commands:
analyze Analyze configured exchanges
buy Buy additional tokens for your index
convert Convert stablecoins to USD for purchasing
cost-basis Calculate cost basis
index Print index by market cap
portfolio Print current portfolio with targets
Some examples:
python main.py index
# To view your current portfolio (including your externally held assets) with target allocations
# _and_ additional assets with targets that the bot will purchase towards.
python main.py portfolio --format=csv
python main.py buy --purchase-balance=200
python main.py buy --dry-run
This is the command you'll want to setup on a cron job:
python main.py buy
There are lots of ways to deploy the bot. Easiest will be heroku, although it works great on a Pi.
I'm not running this on Heroku, so it will need a bit of work. Here are some notes on what needs to be done, feel free to submit a PR!
worker: python main.py buy
which is triggered via the heroku schedulerexternal_portfolio.json
in the image, or modify the user_from_env
to parse JSON from an environment variable or something.celery.sh
DJANGO_SETTINGS_MODULE
https://hub.docker.com/r/iloveitaly/crypto-index-fund-bot
You can use docker to deploy a single-user instance of this bot to a VPS or a local machine like a Raspberry Pi.
docker build -t crypto-index-fund-bot .
docker run -d --env-file .env crypto-index-fund-bot
In single-user mode, all configuration is set via environment variables.
There is a SCHEDULE
variable which you can use to configure how often the account is checked for new deposits.
external_portfolio.json
is copied into the container if it exists locally.
In addition to sourcing the user configuration from .env
and running the bot in single-user mode, you can run the bot in multi-user mode. If you do this:
There's a docker-compose
which you can use to easily setup ths bot multi-user mode:
docker compose up -d
docker compose run worker python manage.py sqlcreate
docker compose run worker python manage.py migrate
# now that the database is setup, restart the worker to pick up on the new schema
docker compose restart worker
# after the application is setup you can run a python shell
docker compose run worker scripts/console.sh
Here's how to create a new user once you are in the django shell:
User.objects.create(name="peter pan", binance_api_key="...", binance_secret_key="...", preferences={"livemode": True})
Want to trigger some jobs manually for testing?
import users.celery
users.celery.initiate_user_buys.delay()
Want to run the CLI tools for a specific user?
docker compose run worker bash
USER_ID=10 python main.py portfolio
If you want to update your deployment to the latest version:
docker compose build
docker compose up -d
A separate database is used for the test environment. To create it and setup the schema:
poetry shell
DJANGO_SETTINGS_MODULE="botweb.settings.test" python manage.py sqlcreate
DJANGO_SETTINGS_MODULE="botweb.settings.test" python manage.py migrate
Then, you can run tests:
pytest
Note that VCR is used to record interactions for some of the tests. If tests are failing, you may need to re-record a test:
pytest -k 'test_test_name' --record-mode=rewrite
Debuggins something in particular? Probably useful to increase log level:
LOG_LEVEL=debug pytest
Want to break on unhandled exceptions?
pytest -s --pdb
I like Pylance, the preferred VS Code python extension, which uses pyright
for typechecking.
# install node either via asdf or directly
asdf install
npm install -g pyright@latest
pyright .
The linter currently doesn't pass, which is why it's not enabled on CI. You can run it here (feel free to submit PRs to get it closer to passing!):
poetry shell
pylint **/*.py
All of the buying prioritization happens in calculate_market_buy_preferences
which is decently documented with inline comments. I recommend taking a look if you are curious.
Basically, the logic does two things:
On many exchanges a market order pays higher fees than limit orders. But Binance fees are the same whether you're the maker or the taker. For simplicity, this bot just places instantly-fulfilled market orders. There's usually sufficient liquidity to assume your order will be filled without the price moving much in the milliseconds it takes to check the market and then place the order.
The only way to reduce Binance fees is to hold their BNB token in your account (currently 0.1% fees become 0.075%).
WIP limit order documentation. Right now, there is a limit order strategy, but it's not well thought through
If a limit order is not filled, by default it remains open indefinitely. This bot will automatically cancel any open limit orders that have not been filled based on the user configuration. The cancellation process does not differentiate between orders created by the bot and orders created by the user.
The bot will not submit an order for a token that has an existing open order.
Exchanges specify a minimum buy order value for each crypto (i.e. minNotional
in Binance). Let's say you're looking to buy equal amounts of 10 different cryptos and only want to spend 0.005 BTC altogether, which would result in 0.0005 BTC of each token being purchased.
However, let's say the minNotional
for BTC orders is 0.001; an exchange will not let you place an order whose value is smaller than that. In this case, we will ensure the minimum order amount is satisfied even if it means exceeding your target allocation (this would only happen small small amounts of total crypto holdings).
WIP requirements for adding support for new exchages. Right now only binance is supported
Hard requirements:
Nice to have:
Some helpful exchange-specific links:
I am not a qualified licensed investment advisor and I don't have any professional finance experience. This tool neither is, nor should be construed as an offer, solicitation, or recommendation to buy or sell any cryptocurrencies assets. Use it at your own risk.