{% blocktrans %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}
11 |
12 | {% endblock %}
13 |
14 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/users/models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import AbstractUser
2 | from django.db.models import CharField
3 | from django.urls import reverse
4 | from django.utils.translation import ugettext_lazy as _
5 |
6 |
7 | class User(AbstractUser):
8 |
9 | # First Name and Last Name do not cover name patterns
10 | # around the globe.
11 | name = CharField(_("Name of User"), blank=True, max_length=255)
12 |
13 | def get_absolute_url(self):
14 | return reverse("users:detail", kwargs={"username": self.username})
15 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/templates/users/user_form.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load crispy_forms_tags %}
3 |
4 | {% block title %}{{ user.username }}{% endblock %}
5 |
6 | {% block content %}
7 |
{% blocktrans %}This part of the site requires us to verify that
13 | you are who you claim to be. For this purpose, we require that you
14 | verify ownership of your e-mail address. {% endblocktrans %}
15 |
16 |
{% blocktrans %}We have sent an e-mail to you for
17 | verification. Please click on the link inside this e-mail. Please
18 | contact us if you do not receive it within a few minutes.{% endblocktrans %}
{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}
{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}
9 |
10 | {% if token_fail %}
11 | {% url 'account_reset_password' as passwd_reset_url %}
12 |
{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}
13 | {% else %}
14 | {% if form %}
15 |
20 | {% else %}
21 |
{% trans 'Your password is now changed.' %}
22 | {% endif %}
23 | {% endif %}
24 | {% endblock %}
25 |
26 |
--------------------------------------------------------------------------------
/requirements/base.txt:
--------------------------------------------------------------------------------
1 | pytz==2020.1 # https://github.com/stub42/pytz
2 | python-slugify==4.0.0 # https://github.com/un33k/python-slugify
3 | Pillow==7.1.2 # https://github.com/python-pillow/Pillow
4 | argon2-cffi==19.2.0 # https://github.com/hynek/argon2_cffi
5 | whitenoise==5.0.1 # https://github.com/evansd/whitenoise
6 | redis==3.5.0 # https://github.com/andymccurdy/redis-py
7 |
8 | # Django
9 | # ------------------------------------------------------------------------------
10 | django==3.0.5 # pyup: < 3.1 # https://www.djangoproject.com/
11 | django-environ==0.4.5 # https://github.com/joke2k/django-environ
12 | django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils
13 | django-allauth==0.41.0 # https://github.com/pennersr/django-allauth
14 | django-crispy-forms==1.9.0 # https://github.com/django-crispy-forms/django-crispy-forms
15 | django-redis==4.11.0 # https://github.com/jazzband/django-redis
16 | # Django REST Framework
17 | djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework
18 |
19 | # trading bot
20 | ccxt==1.30.74 # https://github.com/ccxt/ccxt
21 |
22 | # cronjob
23 | crontab==0.22.9
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 | from pathlib import Path
5 |
6 | if __name__ == "__main__":
7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
8 |
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError:
12 | # The above import may fail for some other reason. Ensure that the
13 | # issue is really that Django is missing to avoid masking other
14 | # exceptions on Python 2.
15 | try:
16 | import django # noqa
17 | except ImportError:
18 | raise ImportError(
19 | "Couldn't import Django. Are you sure it's installed and "
20 | "available on your PYTHONPATH environment variable? Did you "
21 | "forget to activate a virtual environment?"
22 | )
23 |
24 | raise
25 |
26 | # This allows easy placement of apps within the interior
27 | # django_crypto_trading_bot directory.
28 | current_path = Path(__file__).parent.resolve()
29 | sys.path.append(str(current_path / "django_crypto_trading_bot"))
30 |
31 | execute_from_command_line(sys.argv)
32 |
--------------------------------------------------------------------------------
/compose/production/postgres/maintenance/backup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 |
4 | ### Create a database backup.
5 | ###
6 | ### Usage:
7 | ### $ docker-compose -f .yml (exec |run --rm) postgres backup
8 |
9 |
10 | set -o errexit
11 | set -o pipefail
12 | set -o nounset
13 |
14 |
15 | working_dir="$(dirname ${0})"
16 | source "${working_dir}/_sourced/constants.sh"
17 | source "${working_dir}/_sourced/messages.sh"
18 |
19 |
20 | message_welcome "Backing up the '${POSTGRES_DB}' database..."
21 |
22 |
23 | if [[ "${POSTGRES_USER}" == "postgres" ]]; then
24 | message_error "Backing up as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
25 | exit 1
26 | fi
27 |
28 | export PGHOST="${POSTGRES_HOST}"
29 | export PGPORT="${POSTGRES_PORT}"
30 | export PGUSER="${POSTGRES_USER}"
31 | export PGPASSWORD="${POSTGRES_PASSWORD}"
32 | export PGDATABASE="${POSTGRES_DB}"
33 |
34 | backup_filename="${BACKUP_FILE_PREFIX}_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz"
35 | pg_dump | gzip > "${BACKUP_DIR_PATH}/${backup_filename}"
36 |
37 |
38 | message_success "'${POSTGRES_DB}' database backup '${backup_filename}' has been created and placed in '${BACKUP_DIR_PATH}'."
39 |
--------------------------------------------------------------------------------
/docs/_source/api/django_crypto_trading_bot.trading_bot.api.rst:
--------------------------------------------------------------------------------
1 | django\_crypto\_trading\_bot.trading\_bot.api package
2 | =====================================================
3 |
4 | Submodules
5 | ----------
6 |
7 | django\_crypto\_trading\_bot.trading\_bot.api.client module
8 | -----------------------------------------------------------
9 |
10 | .. automodule:: django_crypto_trading_bot.trading_bot.api.client
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | django\_crypto\_trading\_bot.trading\_bot.api.market module
16 | -----------------------------------------------------------
17 |
18 | .. automodule:: django_crypto_trading_bot.trading_bot.api.market
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | django\_crypto\_trading\_bot.trading\_bot.api.order module
24 | ----------------------------------------------------------
25 |
26 | .. automodule:: django_crypto_trading_bot.trading_bot.api.order
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 | Module contents
32 | ---------------
33 |
34 | .. automodule:: django_crypto_trading_bot.trading_bot.api
35 | :members:
36 | :undoc-members:
37 | :show-inheritance:
38 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 |
8 | if "%SPHINXBUILD%" == "" (
9 | set SPHINXBUILD=sphinx-build -c .
10 | )
11 | set SOURCEDIR=_source
12 | set BUILDDIR=_build
13 | set APP=..\{{cookiecutter.project_slug}}
14 |
15 | if "%1" == "" goto help
16 |
17 | %SPHINXBUILD% >NUL 2>NUL
18 | if errorlevel 9009 (
19 | echo.
20 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
21 | echo.installed, then set the SPHINXBUILD environment variable to point
22 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
23 | echo.may add the Sphinx directory to PATH.
24 | echo.
25 | echo.Install sphinx-autobuild for live serving.
26 | echo.If you don't have Sphinx installed, grab it from
27 | echo.http://sphinx-doc.org/
28 | exit /b 1
29 | )
30 |
31 | %SPHINXBUILD% -b %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
32 | goto end
33 |
34 | :livehtml
35 | sphinx-autobuild -b html --open-browser -p 7000 --watch %APP% -c . %SOURCEDIR% %BUILDDIR%/html
36 | GOTO :EOF
37 |
38 | :apidocs
39 | sphinx-apidoc -o %SOURCEDIR%/api %APP%
40 | GOTO :EOF
41 |
42 | :help
43 | %SPHINXBUILD% -b help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
44 |
45 | :end
46 | popd
47 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/contrib/sites/migrations/0003_set_site_domain_and_name.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 | from django.conf import settings
7 | from django.db import migrations
8 |
9 |
10 | def update_site_forward(apps, schema_editor):
11 | """Set site domain and name."""
12 | Site = apps.get_model("sites", "Site")
13 | Site.objects.update_or_create(
14 | id=settings.SITE_ID,
15 | defaults={
16 | "domain": "django-crypto-trading-bot.org",
17 | "name": "Django Crypto Trading Bot",
18 | },
19 | )
20 |
21 |
22 | def update_site_backward(apps, schema_editor):
23 | """Revert site domain and name to default."""
24 | Site = apps.get_model("sites", "Site")
25 | Site.objects.update_or_create(
26 | id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"}
27 | )
28 |
29 |
30 | class Migration(migrations.Migration):
31 |
32 | dependencies = [("sites", "0002_alter_domain_unique")]
33 |
34 | operations = [migrations.RunPython(update_site_forward, update_site_backward)]
35 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/trading_bot/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from decimal import Decimal
2 |
3 | import pytest
4 | from django.test import RequestFactory
5 |
6 | from django_crypto_trading_bot.trading_bot.models import OHLCV
7 | from django_crypto_trading_bot.trading_bot.tests.factories import (
8 | BnbCurrencyFactory,
9 | EurCurrencyFactory,
10 | OHLCVBnbEurFactory,
11 | OHLCVTrxBnbFactory,
12 | TrxCurrencyFactory,
13 | )
14 |
15 |
16 | @pytest.fixture
17 | def request_factory() -> RequestFactory:
18 | return RequestFactory()
19 |
20 |
21 | @pytest.fixture
22 | def coin_exchange() -> dict:
23 | ohlcv_bnb_eur: OHLCV = OHLCVBnbEurFactory()
24 | ohlcv_trx_bnb: OHLCV = OHLCVTrxBnbFactory()
25 | return {
26 | "timestamp": ohlcv_bnb_eur.timestamp,
27 | "currency": {
28 | "EUR": {"price": Decimal(1), "base_currency": EurCurrencyFactory()},
29 | "BNB": {
30 | "price": ohlcv_bnb_eur.closing_price,
31 | "base_currency": BnbCurrencyFactory(),
32 | },
33 | "TRX": {
34 | "price": ohlcv_trx_bnb.closing_price * ohlcv_bnb_eur.closing_price,
35 | "base_currency": TrxCurrencyFactory(),
36 | },
37 | },
38 | }
39 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/users/tests/test_forms.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from django_crypto_trading_bot.users.forms import UserCreationForm
4 | from django_crypto_trading_bot.users.tests.factories import UserFactory
5 |
6 | pytestmark = pytest.mark.django_db
7 |
8 |
9 | class TestUserCreationForm:
10 | def test_clean_username(self):
11 | # A user with proto_user params does not exist yet.
12 | proto_user = UserFactory.build()
13 |
14 | form = UserCreationForm(
15 | {
16 | "username": proto_user.username,
17 | "password1": proto_user._password,
18 | "password2": proto_user._password,
19 | }
20 | )
21 |
22 | assert form.is_valid()
23 | assert form.clean_username() == proto_user.username
24 |
25 | # Creating a user.
26 | form.save()
27 |
28 | # The user with proto_user params already exists,
29 | # hence cannot be created.
30 | form = UserCreationForm(
31 | {
32 | "username": proto_user.username,
33 | "password1": proto_user._password,
34 | "password2": proto_user._password,
35 | }
36 | )
37 |
38 | assert not form.is_valid()
39 | assert len(form.errors) == 1
40 | assert "username" in form.errors
41 |
--------------------------------------------------------------------------------
/compose/production/django/Dockerfile:
--------------------------------------------------------------------------------
1 |
2 | FROM python:3.10-slim-buster
3 |
4 | ENV PYTHONUNBUFFERED 1
5 |
6 | RUN apt-get update \
7 | # dependencies for building Python packages
8 | && apt-get install -y build-essential \
9 | # psycopg2 dependencies
10 | && apt-get install -y libpq-dev \
11 | # Translations dependencies
12 | && apt-get install -y gettext \
13 | # cleaning up unused files
14 | && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
15 | && rm -rf /var/lib/apt/lists/*
16 |
17 | RUN addgroup --system django \
18 | && adduser --system --ingroup django django
19 |
20 | # Requirements are installed here to ensure they will be cached.
21 | COPY ./requirements /requirements
22 | RUN pip install --no-cache-dir -r /requirements/production.txt \
23 | && rm -rf /requirements
24 |
25 | COPY ./compose/production/django/entrypoint /entrypoint
26 | RUN sed -i 's/\r$//g' /entrypoint
27 | RUN chmod +x /entrypoint
28 | RUN chown django /entrypoint
29 |
30 | COPY ./compose/production/django/cron /cron
31 | RUN sed -i 's/\r$//g' /cron
32 | RUN chmod +x /cron
33 | RUN chown django /cron
34 |
35 | COPY ./compose/production/django/start /start
36 | RUN sed -i 's/\r$//g' /start
37 | RUN chmod +x /start
38 | RUN chown django /start
39 | COPY --chown=django:django . /app
40 |
41 | USER django
42 |
43 | WORKDIR /app
44 |
45 | ENTRYPOINT ["/entrypoint"]
46 |
--------------------------------------------------------------------------------
/docs/_source/api/django_crypto_trading_bot.contrib.sites.migrations.rst:
--------------------------------------------------------------------------------
1 | django\_crypto\_trading\_bot.contrib.sites.migrations package
2 | =============================================================
3 |
4 | Submodules
5 | ----------
6 |
7 | django\_crypto\_trading\_bot.contrib.sites.migrations.0001\_initial module
8 | --------------------------------------------------------------------------
9 |
10 | .. automodule:: django_crypto_trading_bot.contrib.sites.migrations.0001_initial
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | django\_crypto\_trading\_bot.contrib.sites.migrations.0002\_alter\_domain\_unique module
16 | ----------------------------------------------------------------------------------------
17 |
18 | .. automodule:: django_crypto_trading_bot.contrib.sites.migrations.0002_alter_domain_unique
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | django\_crypto\_trading\_bot.contrib.sites.migrations.0003\_set\_site\_domain\_and\_name module
24 | -----------------------------------------------------------------------------------------------
25 |
26 | .. automodule:: django_crypto_trading_bot.contrib.sites.migrations.0003_set_site_domain_and_name
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 | Module contents
32 | ---------------
33 |
34 | .. automodule:: django_crypto_trading_bot.contrib.sites.migrations
35 | :members:
36 | :undoc-members:
37 | :show-inheritance:
38 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/trading_bot/exceptions.py:
--------------------------------------------------------------------------------
1 | class InsufficientTradingAmount(Exception):
2 | """
3 | Called when try to create a reader with insufficient trading amount
4 | """
5 |
6 |
7 | class TickerWasNotSet(Exception):
8 | """
9 | Call when try to create a reorder without to set the ticker
10 | """
11 |
12 |
13 | class PriceToLow(Exception):
14 | """
15 | Call when try use a price below minimum amount
16 | """
17 |
18 |
19 | class PriceToHigh(Exception):
20 | """
21 | Call when try use a price above maximum amount
22 | """
23 |
24 |
25 | class FunktionNotForTradeMode(Exception):
26 | """
27 | Funktion is not implemented for trade mode
28 | """
29 |
30 |
31 | class NoQuoteCurrency(Exception):
32 | """
33 | Bot has not quote currency
34 | """
35 |
36 |
37 | class NoMarket(Exception):
38 | """
39 | Bot & order has no market!
40 | """
41 |
42 |
43 | class NoTimeFrame(Exception):
44 | """
45 | Bot has no Timeframe!
46 | """
47 |
48 |
49 | class BotHasNoStopLoss(Exception):
50 | """
51 | Bot has no stop loss!
52 | """
53 |
54 |
55 | class BotHasNoQuoteCurrency(Exception):
56 | """
57 | Bot has no quote currency!
58 | """
59 |
60 |
61 | class BotHasNoMinRise(Exception):
62 | """
63 | Bot has no min rise!
64 | """
65 |
66 |
67 | class OrderHasNoLastPrice(Exception):
68 | """
69 | Order has no last price tick!
70 | """
71 |
--------------------------------------------------------------------------------
/docs/_source/howto.rst:
--------------------------------------------------------------------------------
1 | How To - Project Documentation
2 | ======================================================================
3 |
4 | Get Started
5 | ----------------------------------------------------------------------
6 |
7 | Documentation can be written as rst files in the `django_crypto_trading_bot/docs/_source`.
8 |
9 |
10 | To build and serve docs, use the commands:
11 | ::
12 |
13 | docker-compose -f local.yml up docs
14 |
15 | Changes to files in `docs/_source` will be picked up and reloaded automatically.
16 |
17 | `Sphinx `_ is the tool used to build documentation.
18 |
19 | Docstrings to Documentation
20 | ----------------------------------------------------------------------
21 |
22 | The sphinx extension `apidoc `_ is used to automatically document code using signatures and docstrings.
23 |
24 | Numpy or Google style docstrings will be picked up from project files and availble for documentation. See the `Napoleon `_ extension for details.
25 |
26 | For an in-use example, see the `page source <_sources/users.rst.txt>`_ for :ref:`users`.
27 |
28 | To compile all docstrings automatically into documentation source files, use the command:
29 | ::
30 |
31 | make apidocs
32 |
33 | This can be done in the docker container:
34 | ::
35 |
36 | docker-compose -f local.yml run --rm docs make apidocs
37 |
--------------------------------------------------------------------------------
/local.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | volumes:
4 | local_postgres_data: {}
5 | local_postgres_data_backups: {}
6 |
7 | services:
8 | django:
9 | build:
10 | context: .
11 | dockerfile: ./compose/local/django/Dockerfile
12 | image: django_crypto_trading_bot_local_django
13 | container_name: django
14 | depends_on:
15 | - postgres
16 | - mailhog
17 | volumes:
18 | - .:/app
19 | env_file:
20 | - ./.envs/.local/.django
21 | - ./.envs/.local/.postgres
22 | ports:
23 | - "8000:8000"
24 | command: /start
25 |
26 | docs:
27 | image: django_crypto_trading_bot_local_docs
28 | container_name: docs
29 | build:
30 | context: .
31 | dockerfile: ./compose/local/docs/Dockerfile
32 | env_file:
33 | - ./.envs/.local/.django
34 | volumes:
35 | - ./docs:/docs
36 | - ./config:/app/config
37 | - ./django_crypto_trading_bot:/app/django_crypto_trading_bot
38 | ports:
39 | - "7000:7000"
40 |
41 | postgres:
42 | build:
43 | context: .
44 | dockerfile: ./compose/production/postgres/Dockerfile
45 | image: django_crypto_trading_bot_production_postgres
46 | container_name: postgres
47 | volumes:
48 | - local_postgres_data:/var/lib/postgresql/data
49 | - local_postgres_data_backups:/backups
50 | env_file:
51 | - ./.envs/.local/.postgres
52 |
53 | mailhog:
54 | image: mailhog/mailhog:v1.0.0
55 | container_name: mailhog
56 | ports:
57 | - "8025:8025"
58 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/users/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib import messages
2 | from django.contrib.auth import get_user_model
3 | from django.contrib.auth.mixins import LoginRequiredMixin
4 | from django.urls import reverse
5 | from django.utils.translation import ugettext_lazy as _
6 | from django.views.generic import DetailView, RedirectView, UpdateView
7 |
8 | User = get_user_model()
9 |
10 |
11 | class UserDetailView(LoginRequiredMixin, DetailView):
12 |
13 | model = User
14 | slug_field = "username"
15 | slug_url_kwarg = "username"
16 |
17 |
18 | user_detail_view = UserDetailView.as_view()
19 |
20 |
21 | class UserUpdateView(LoginRequiredMixin, UpdateView):
22 |
23 | model = User
24 | fields = ["name"]
25 |
26 | def get_success_url(self):
27 | return reverse("users:detail", kwargs={"username": self.request.user.username})
28 |
29 | def get_object(self):
30 | return User.objects.get(username=self.request.user.username)
31 |
32 | def form_valid(self, form):
33 | messages.add_message(
34 | self.request, messages.INFO, _("Infos successfully updated")
35 | )
36 | return super().form_valid(form)
37 |
38 |
39 | user_update_view = UserUpdateView.as_view()
40 |
41 |
42 | class UserRedirectView(LoginRequiredMixin, RedirectView):
43 |
44 | permanent = False
45 |
46 | def get_redirect_url(self):
47 | return reverse("users:detail", kwargs={"username": self.request.user.username})
48 |
49 |
50 | user_redirect_view = UserRedirectView.as_view()
51 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/contrib/sites/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | import django.contrib.sites.models
2 | from django.contrib.sites.models import _simple_domain_name_validator
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = []
9 |
10 | operations = [
11 | migrations.CreateModel(
12 | name="Site",
13 | fields=[
14 | (
15 | "id",
16 | models.AutoField(
17 | verbose_name="ID",
18 | serialize=False,
19 | auto_created=True,
20 | primary_key=True,
21 | ),
22 | ),
23 | (
24 | "domain",
25 | models.CharField(
26 | max_length=100,
27 | verbose_name="domain name",
28 | validators=[_simple_domain_name_validator],
29 | ),
30 | ),
31 | ("name", models.CharField(max_length=50, verbose_name="display name")),
32 | ],
33 | options={
34 | "ordering": ("domain",),
35 | "db_table": "django_site",
36 | "verbose_name": "site",
37 | "verbose_name_plural": "sites",
38 | },
39 | bases=(models.Model,),
40 | managers=[("objects", django.contrib.sites.models.SiteManager())],
41 | )
42 | ]
43 |
--------------------------------------------------------------------------------
/docs/_source/api/django_crypto_trading_bot.trading_bot.tests.rst:
--------------------------------------------------------------------------------
1 | django\_crypto\_trading\_bot.trading\_bot.tests package
2 | =======================================================
3 |
4 | Submodules
5 | ----------
6 |
7 | django\_crypto\_trading\_bot.trading\_bot.tests.conftest module
8 | ---------------------------------------------------------------
9 |
10 | .. automodule:: django_crypto_trading_bot.trading_bot.tests.conftest
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | django\_crypto\_trading\_bot.trading\_bot.tests.factories module
16 | ----------------------------------------------------------------
17 |
18 | .. automodule:: django_crypto_trading_bot.trading_bot.tests.factories
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | django\_crypto\_trading\_bot.trading\_bot.tests.test\_models module
24 | -------------------------------------------------------------------
25 |
26 | .. automodule:: django_crypto_trading_bot.trading_bot.tests.test_models
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 | django\_crypto\_trading\_bot.trading\_bot.tests.test\_trade module
32 | ------------------------------------------------------------------
33 |
34 | .. automodule:: django_crypto_trading_bot.trading_bot.tests.test_trade
35 | :members:
36 | :undoc-members:
37 | :show-inheritance:
38 |
39 | Module contents
40 | ---------------
41 |
42 | .. automodule:: django_crypto_trading_bot.trading_bot.tests
43 | :members:
44 | :undoc-members:
45 | :show-inheritance:
46 |
--------------------------------------------------------------------------------
/.pyup.yml:
--------------------------------------------------------------------------------
1 | # config for pyup.io -> https://pyup.io/docs/bot/config/
2 |
3 | # configure updates globally
4 | # default: all
5 | # allowed: all, insecure, False
6 | update: all
7 |
8 | # configure dependency pinning globally
9 | # default: True
10 | # allowed: True, False
11 | pin: True
12 |
13 | # set the default branch
14 | # default: empty, the default branch on GitHub
15 | # branch: dev
16 |
17 | # update schedule
18 | # default: empty
19 | # allowed: "every day", "every week", ..
20 | schedule: "every week"
21 |
22 | # search for requirement files
23 | # default: True
24 | # allowed: True, False
25 | search: False
26 |
27 | # Specify requirement files by hand, default is empty
28 | # default: empty
29 | # allowed: list
30 | requirements:
31 | - requirements/base.txt:
32 | update: all
33 | pin: True
34 | - requirements/local.txt:
35 | update: all
36 | pin: True
37 | - requirements/production.txt:
38 | update: all
39 | pin: True
40 |
41 | # add a label to pull requests, default is not set
42 | # requires private repo permissions, even on public repos
43 | # default: empty
44 | label_prs: update
45 |
46 | # assign users to pull requests, default is not set
47 | # requires private repo permissions, even on public repos
48 | # default: empty
49 | # assignees:
50 | # - carl
51 | # - carlsen
52 |
53 | # configure the branch prefix the bot is using
54 | # default: pyup-
55 | branch_prefix: pyup/
56 |
57 | # set a global prefix for PRs
58 | # default: empty
59 | # pr_prefix: "Bug #12345"
60 |
61 | # allow to close stale PRs
62 | # default: True
63 | close_prs: True
64 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/templates/account/login.html:
--------------------------------------------------------------------------------
1 | {% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account socialaccount %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% trans "Sign In" %}{% endblock %}
8 |
9 | {% block inner %}
10 |
11 |
{% trans "Sign In" %}
12 |
13 | {% get_providers as socialaccount_providers %}
14 |
15 | {% if socialaccount_providers %}
16 |
{% blocktrans with site.name as site_name %}Please sign in with one
17 | of your existing third party accounts. Or, sign up
18 | for a {{ site_name }} account and sign in below:{% endblocktrans %}
19 |
20 |
21 |
22 |
23 | {% include "socialaccount/snippets/provider_list.html" with process="login" %}
24 |
{% trans 'The following e-mail addresses are associated with your account:' %}
14 |
15 |
44 |
45 | {% else %}
46 |
{% trans 'Warning:'%} {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}
47 |
48 | {% endif %}
49 |
50 |
51 |
{% trans "Add E-mail Address" %}
52 |
53 |
58 |
59 | {% endblock %}
60 |
61 |
62 | {% block javascript %}
63 | {{ block.super }}
64 |
79 | {% endblock %}
80 |
81 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/trading_bot/api/market.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import List
4 |
5 | from ccxt.base.exchange import Exchange
6 |
7 | from django_crypto_trading_bot.trading_bot.models import Currency, Market
8 |
9 | from .client import get_client
10 |
11 |
12 | def get_or_create_market(response: dict, exchange_id: str) -> Market:
13 | """
14 | update or create a market based on the api json
15 | """
16 | base, create = Currency.objects.get_or_create(short=response["base"].upper())
17 | quote, create = Currency.objects.get_or_create(short=response["quote"].upper())
18 |
19 | try:
20 | market: Market = Market.objects.get(
21 | base=base, quote=quote, exchange=exchange_id
22 | )
23 | market.active = response["active"]
24 | market.precision_amount = response["precision"]["amount"]
25 | market.precision_price = response["precision"]["price"]
26 | market.limits_amount_min = response["limits"]["amount"]["min"]
27 | market.limits_amount_max = response["limits"]["amount"]["max"]
28 | market.limits_price_min = response["limits"]["price"]["min"]
29 | market.limits_price_max = response["limits"]["price"]["max"]
30 | market.save()
31 | return market
32 |
33 | except Market.DoesNotExist:
34 | return Market.objects.create(
35 | base=base,
36 | quote=quote,
37 | exchange=exchange_id,
38 | active=response["active"],
39 | precision_amount=response["precision"]["amount"],
40 | precision_price=response["precision"]["price"],
41 | limits_amount_min=response["limits"]["amount"]["min"],
42 | limits_amount_max=response["limits"]["amount"]["max"],
43 | limits_price_min=response["limits"]["price"]["min"],
44 | limits_price_max=response["limits"]["price"]["max"],
45 | )
46 |
47 |
48 | def update_market(market: Market, exchange: Exchange = None) -> Market:
49 | """
50 | Update Market Order
51 |
52 | Keyword arguments:
53 | market -- market model
54 | exchange -- exchange client, preload all markets to reduce requests
55 |
56 | return -> Market, updated market
57 | """
58 | if not exchange:
59 | exchange = get_client(exchange_id=market.exchange)
60 |
61 | market_exchange: dict = exchange.market(market.symbol)
62 | return get_or_create_market(response=market_exchange, exchange_id=market.exchange)
63 |
64 |
65 | def update_all_markets(exchange: Exchange):
66 | """
67 | Update all markets
68 | """
69 | for market in Market.objects.all():
70 | update_market(market, exchange)
71 |
72 |
73 | def get_all_markets_from_exchange(exchange_id: str) -> List[Market]:
74 | """
75 | Load all markets from an exchange into the database
76 |
77 | Arguments:
78 | exchange_id {str} -- exchange name like "binance"
79 |
80 | Returns:
81 | List[Market] -- All Markets from the exchange as model
82 | """
83 |
84 | exchange = get_client(exchange_id=exchange_id)
85 | exchange.load_markets()
86 |
87 | markets: List[Market] = []
88 |
89 | for market in exchange.markets.values():
90 | markets.append(get_or_create_market(response=market, exchange_id=exchange_id))
91 |
92 | return markets
93 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/trading_bot/tests/api_client/test_api_market.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from ccxt.base.exchange import Exchange
3 | from django_crypto_trading_bot.trading_bot.api.client import get_client
4 | from django_crypto_trading_bot.trading_bot.api.market import (
5 | get_or_create_market,
6 | update_market,
7 | update_all_markets,
8 | )
9 | from django_crypto_trading_bot.trading_bot.models import Market, Currency
10 | from django_crypto_trading_bot.trading_bot.tests.factories import OutOfDataMarketFactory
11 | from django_crypto_trading_bot.trading_bot.tests.api_client.api_data_example import (
12 | market_structure,
13 | market_structure_eth_btc,
14 | )
15 |
16 |
17 | @pytest.mark.django_db()
18 | def test_get_or_create_market():
19 | default_market: dict = market_structure()
20 |
21 | # assert model
22 | market: Market = get_or_create_market(
23 | response=default_market, exchange_id="binance"
24 | )
25 | assert isinstance(market, Market)
26 | assert market.active == default_market["active"]
27 | assert isinstance(market.base, Currency)
28 | assert isinstance(market.quote, Currency)
29 | assert market.precision_amount == default_market["precision"]["amount"]
30 | assert market.precision_price == default_market["precision"]["price"]
31 |
32 | # test if new market works on binance
33 | exchange: Exchange = get_client(exchange_id="binance")
34 | exchange.load_markets()
35 | market_exchange = exchange.market(default_market["symbol"])
36 | assert default_market["symbol"] == market_exchange["symbol"]
37 |
38 |
39 | @pytest.mark.django_db()
40 | def test_update_market():
41 | exchange: Exchange = get_client(exchange_id="binance")
42 | exchange.load_markets()
43 |
44 | # load outdated market
45 | out_of_data_market: Market = OutOfDataMarketFactory()
46 |
47 | # update market
48 | updated_market: Market = update_market(market=out_of_data_market, exchange=exchange)
49 |
50 | # get market from binance
51 | market_exchange: dict = exchange.market(out_of_data_market.symbol)
52 |
53 | assert isinstance(updated_market, Market)
54 |
55 | assert out_of_data_market.active != updated_market.active
56 | assert out_of_data_market.precision_amount != updated_market.precision_amount
57 | assert out_of_data_market.precision_price != updated_market.precision_price
58 |
59 | assert updated_market.active == market_exchange["active"]
60 | assert updated_market.precision_amount == market_exchange["precision"]["amount"]
61 | assert updated_market.precision_price == market_exchange["precision"]["price"]
62 |
63 |
64 | @pytest.mark.django_db()
65 | def test_update_all_markets():
66 | exchange: Exchange = get_client(exchange_id="binance")
67 | exchange.load_markets()
68 |
69 | out_of_data_market: Market = OutOfDataMarketFactory()
70 | get_or_create_market(response=market_structure_eth_btc(), exchange_id="binance")
71 |
72 | # update market
73 | update_all_markets(exchange)
74 |
75 | # get updatet market from the database
76 | updated_market: Market = Market.objects.get(pk=out_of_data_market.pk)
77 |
78 | # get market from binance
79 | market_exchange: dict = exchange.market(out_of_data_market.symbol)
80 |
81 | # assert if values changed
82 | assert out_of_data_market.active != updated_market.active
83 | assert out_of_data_market.precision_amount != updated_market.precision_amount
84 | assert out_of_data_market.precision_price != updated_market.precision_price
85 |
86 | # assert if value are set to market_exchange
87 | assert updated_market.active == market_exchange["active"]
88 | assert updated_market.precision_amount == market_exchange["precision"]["amount"]
89 | assert updated_market.precision_price == market_exchange["precision"]["price"]
90 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/trading_bot/tests/api_client/test_api_order.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from decimal import Decimal
3 | from typing import List
4 |
5 | import pytest
6 | import pytz
7 | from ccxt import Exchange
8 |
9 | from django_crypto_trading_bot.trading_bot.api.client import get_client
10 | from django_crypto_trading_bot.trading_bot.api.market import (
11 | get_all_markets_from_exchange,
12 | )
13 | from django_crypto_trading_bot.trading_bot.api.order import (
14 | create_order,
15 | update_order_from_api_response,
16 | )
17 | from django_crypto_trading_bot.trading_bot.models import Market, Order, Trade
18 | from django_crypto_trading_bot.trading_bot.tests.api_client.api_data_example import (
19 | order_structure,
20 | )
21 | from django_crypto_trading_bot.trading_bot.tests.factories import (
22 | BotFactory,
23 | BtcCurrencyFactory,
24 | BuyOrderFactory,
25 | EthCurrencyFactory,
26 | )
27 |
28 |
29 | @pytest.mark.django_db()
30 | def test_create_buy_order():
31 | exchange: Exchange = get_client(exchange_id="binance")
32 | exchange.load_markets()
33 |
34 | bot = BotFactory()
35 |
36 | order: Order = create_order(
37 | amount=Decimal(1),
38 | price=Decimal(0.01),
39 | side="buy",
40 | bot=bot,
41 | isTestOrder=True,
42 | market=bot.market,
43 | )
44 | order2: Order = create_order(
45 | amount=Decimal(1),
46 | price=Decimal(0.01),
47 | side="buy",
48 | bot=bot,
49 | isTestOrder=True,
50 | market=bot.market,
51 | )
52 |
53 | assert isinstance(order, Order)
54 | assert isinstance(order2, Order)
55 | assert order.side == "buy"
56 | assert len(Order.objects.all()) == 2
57 |
58 |
59 | @pytest.mark.django_db()
60 | def test_create_sell_order():
61 | exchange: Exchange = get_client(exchange_id="binance")
62 | exchange.load_markets()
63 |
64 | bot = BotFactory()
65 | order: Order = create_order(
66 | amount=Decimal(1),
67 | price=Decimal(0.01),
68 | side="sell",
69 | bot=bot,
70 | isTestOrder=True,
71 | market=bot.market,
72 | )
73 | order2: Order = create_order(
74 | amount=Decimal(1),
75 | price=Decimal(0.01),
76 | side="sell",
77 | bot=bot,
78 | isTestOrder=True,
79 | market=bot.market,
80 | )
81 |
82 | assert isinstance(order, Order)
83 | assert isinstance(order2, Order)
84 | assert order.side == "sell"
85 | assert len(Order.objects.all()) == 2
86 |
87 |
88 | @pytest.mark.django_db()
89 | def test_get_all_markets_from_exchange():
90 | # load all markets
91 | markets: List[Market] = get_all_markets_from_exchange(exchange_id="binance")
92 |
93 | # load markets from binance
94 | exchange: Exchange = get_client(exchange_id="binance")
95 | exchange.load_markets()
96 |
97 | # compare loaded markets with binance
98 | assert len(markets) == len(exchange.markets.values())
99 |
100 |
101 | @pytest.mark.django_db()
102 | def test_update_order_from_api_response():
103 | order: Order = BuyOrderFactory()
104 |
105 | order_dict: dict = order_structure(add_trades=True)
106 |
107 | order = update_order_from_api_response(cctx_order=order_dict, order=order)
108 |
109 | trade: Trade = Trade.objects.get(trade_id=order_dict["trades"][0]["id"])
110 |
111 | # assert trade
112 | assert trade.order == order
113 | assert trade.trade_id == order_dict["trades"][0]["id"]
114 | assert trade.timestamp == datetime.fromtimestamp(
115 | order_dict["trades"][0]["timestamp"] / 1000, tz=pytz.timezone("UTC")
116 | )
117 | assert trade.taker_or_maker == order_dict["trades"][0]["takerOrMaker"]
118 | assert trade.amount == Decimal(order_dict["trades"][0]["amount"])
119 | assert trade.fee_currency == EthCurrencyFactory()
120 | assert "{:.4f}".format(trade.fee_cost) == "{:.4f}".format(
121 | order_dict["trades"][0]["fee"]["cost"]
122 | )
123 | assert "{:.4f}".format(trade.fee_rate) == "{:.4f}".format(
124 | order_dict["trades"][0]["fee"]["rate"]
125 | )
126 |
127 | # assert order
128 | assert order.status == order_dict["status"]
129 | assert order.filled == order_dict["filled"]
130 | assert order.fee_currency == BtcCurrencyFactory()
131 | assert "{:.4f}".format(order.fee_cost) == "{:.4f}".format(order_dict["fee"]["cost"])
132 | assert "{:.4f}".format(order.fee_rate) == "{:.4f}".format(order_dict["fee"]["rate"])
133 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/trading_bot/tests/test_trade.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from decimal import Decimal
3 |
4 | import pytest
5 | from django.core.cache import cache
6 | from django.utils import timezone
7 |
8 | from django_crypto_trading_bot.trading_bot.models import (
9 | Bot,
10 | OHLCV,
11 | Order,
12 | Saving,
13 | Timeframes,
14 | )
15 | from django_crypto_trading_bot.trading_bot.tests.factories import (
16 | BnbEurMarketFactory,
17 | BtcBnbMarketFactory,
18 | BuyOrderFactory,
19 | EndOrderFactory,
20 | EthBnbMarketFactory,
21 | MarketFactory,
22 | OpenBuyOrderFactory,
23 | SellOrderFactory,
24 | RisingChartOrderFactory,
25 | RisingChartBotFactory,
26 | )
27 | from django_crypto_trading_bot.trading_bot.trade import run_rising_chart, run_wave_rider
28 |
29 |
30 | @pytest.mark.django_db()
31 | class TestWaveRider(unittest.TestCase):
32 | def test_no_update(self):
33 | open_order: Order = OpenBuyOrderFactory()
34 | end_order: Order = EndOrderFactory()
35 |
36 | run_wave_rider(test=True)
37 |
38 | open_order_update: Order = Order.objects.get(pk=open_order.pk)
39 | end_order_update: Order = Order.objects.get(pk=end_order.pk)
40 |
41 | assert open_order_update.status == open_order.status
42 | assert open_order_update.next_order == open_order.next_order
43 |
44 | assert end_order_update.status == end_order.status
45 | assert end_order_update.next_order == end_order.next_order
46 |
47 | def test_normal_update(self):
48 | buy_order: Order = BuyOrderFactory()
49 | sell_order: Order = SellOrderFactory()
50 |
51 | candle: OHLCV = OHLCV(
52 | market=buy_order.bot.market,
53 | timeframe=Timeframes.MONTH_1,
54 | timestamp=timezone.now(),
55 | open_price=Decimal(8.3),
56 | highest_price=Decimal(9.4),
57 | lowest_price=Decimal(7.5),
58 | closing_price=Decimal(8),
59 | volume=Decimal(100),
60 | )
61 |
62 | run_wave_rider(candle=candle, test=True)
63 |
64 | buy_order_reload: Order = Order.objects.get(pk=buy_order.pk)
65 | sell_order_reload: Order = Order.objects.get(pk=sell_order.pk)
66 |
67 | assert buy_order_reload.next_order is not None
68 | assert sell_order_reload.next_order is not None
69 |
70 | Saving.objects.get(order=sell_order)
71 |
72 | assert Saving.objects.all().count() == 2
73 |
74 |
75 | @pytest.mark.django_db()
76 | class TestRisingChart(unittest.TestCase):
77 | def set_tickers_cache(self, exchange: str):
78 | MarketFactory().save()
79 | BnbEurMarketFactory().save()
80 | EthBnbMarketFactory().save()
81 | BtcBnbMarketFactory().save()
82 |
83 | tickers: dict = {
84 | "BNB/EUR": {
85 | "symbol": "BNB/EUR",
86 | "percentage": 2.0,
87 | "last": 1.0,
88 | "bid": 1.0,
89 | "ask": 1.0,
90 | },
91 | "TRX/BNB": {
92 | "symbol": "TRX/BNB",
93 | "percentage": 6.0,
94 | "last": 1.0,
95 | "bid": 1.0,
96 | "ask": 1.0,
97 | },
98 | "ETH/BNB": {
99 | "symbol": "ETH/BNB",
100 | "percentage": 10.0,
101 | "last": 1.0,
102 | "bid": 1.0,
103 | "ask": 1.0,
104 | },
105 | "BTC/BNB": {
106 | "symbol": "BTC/BNB",
107 | "percentage": -2.0,
108 | "last": 1.0,
109 | "bid": 1.0,
110 | "ask": 1.0,
111 | },
112 | }
113 |
114 | cache.set("tickers-{}".format(exchange), tickers, 60)
115 |
116 | def test_order_stop_loss(self):
117 | order: Order = RisingChartOrderFactory()
118 | order.last_price_tick = Decimal(10)
119 | order.save()
120 |
121 | self.set_tickers_cache(order.bot.account.exchange)
122 |
123 | run_rising_chart(test=True)
124 |
125 | order_buy: Order = Order.objects.get(pk=order.pk)
126 |
127 | assert order_buy.next_order is not None
128 |
129 | def test_order_update_order(self):
130 | order: Order = RisingChartOrderFactory()
131 | order.last_price_tick = Decimal(0.5)
132 | order.save()
133 |
134 | self.set_tickers_cache(order.bot.account.exchange)
135 |
136 | run_rising_chart(test=True)
137 |
138 | order_buy: Order = Order.objects.get(pk=order.pk)
139 |
140 | assert order_buy.last_price_tick is not None
141 | assert "{:0.1f}".format(order_buy.last_price_tick) == "1.0"
142 | assert order_buy.next_order is None
143 |
144 | def test_init_buy_order(self):
145 | bot: Bot = RisingChartBotFactory()
146 | bot.save()
147 |
148 | self.set_tickers_cache(bot.account.exchange)
149 |
150 | run_rising_chart(test=True)
151 |
152 | order_buy: Order = Order.objects.get(bot=bot)
153 |
154 | assert order_buy.next_order is None
155 | assert order_buy.market is not None
156 | assert order_buy.market.symbol.upper() == "ETH/BNB"
157 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Django Crypto Trading Bot
2 | =========================
3 |
4 | Auto crypto trading bot for various exchanges.
5 |
6 | .. image:: https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg
7 | :target: https://github.com/pydanny/cookiecutter-django/
8 | :alt: Built with Cookiecutter Django
9 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
10 | :target: https://github.com/ambv/black
11 | :alt: Black code style
12 | .. image:: https://travis-ci.com/linuxluigi/django-crypto-trading-bot.svg?branch=master
13 | :target: https://travis-ci.com/linuxluigi/django-crypto-trading-bot
14 | :alt: Travis CI tests
15 | .. image:: https://readthedocs.org/projects/django-crypto-trading-bot/badge/?version=latest
16 | :target: https://django-crypto-trading-bot.readthedocs.io/en/latest/?badge=latest
17 | :alt: Documentation Status
18 | .. image:: https://coveralls.io/repos/github/linuxluigi/django-crypto-trading-bot/badge.svg?branch=master
19 | :target: https://coveralls.io/github/linuxluigi/django-crypto-trading-bot?branch=master
20 | :alt: Coverage
21 | .. image:: https://api.codacy.com/project/badge/Grade/c6bd668a8e61448b86a15fdb2648cd38?isInternal=true
22 | :target: https://www.codacy.com/manual/linuxluigi/django-crypto-trading-bot?utm_source=github.com&utm_medium=referral&utm_content=linuxluigi/django-crypto-trading-bot&utm_campaign=Badge_Grade_Dashboard
23 | :alt: Codacy quality
24 | .. image:: https://static.deepsource.io/deepsource-badge-light.svg
25 | :target: https://deepsource.io/gh/linuxluigi/django-crypto-trading-bot/?ref=repository-badge
26 | :alt: DeepSource
27 |
28 |
29 | :License: MIT
30 | Rewrite in progress
31 | =========================
32 |
33 | The current state of the project is very experimental and there is a complete rewrite_ in progress. Soon it will be possible to create a trading strategy through a django app.
34 |
35 | .. _rewrite: https://github.com/linuxluigi/django-crypto-trading-bot/tree/v0.2
36 |
37 | Settings
38 | --------
39 |
40 | Moved to settings_.
41 |
42 | .. _settings: http://cookiecutter-django.readthedocs.io/en/latest/settings.html
43 |
44 | Basic Commands
45 | --------------
46 |
47 | Setting Up Your Users
48 | ^^^^^^^^^^^^^^^^^^^^^
49 |
50 | * To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go.
51 |
52 | * To create an **superuser account**, use this command::
53 |
54 | $ python manage.py createsuperuser
55 |
56 | For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users.
57 |
58 | Type checks
59 | ^^^^^^^^^^^
60 |
61 | Running type checks with mypy:
62 |
63 | ::
64 |
65 | $ mypy django_crypto_trading_bot
66 |
67 | Test coverage
68 | ^^^^^^^^^^^^^
69 |
70 | To run the tests, check your test coverage, and generate an HTML coverage report::
71 |
72 | $ coverage run -m pytest
73 | $ coverage html
74 | $ open htmlcov/index.html
75 |
76 | Running tests with py.test
77 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
78 |
79 | ::
80 |
81 | $ pytest
82 |
83 | Live reloading and Sass CSS compilation
84 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
85 |
86 | Moved to `Live reloading and SASS compilation`_.
87 |
88 | .. _`Live reloading and SASS compilation`: http://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html
89 |
90 |
91 |
92 |
93 | Email Server
94 | ^^^^^^^^^^^^
95 |
96 | In development, it is often nice to be able to see emails that are being sent from your application. For that reason local SMTP server `MailHog`_ with a web interface is available as docker container.
97 |
98 | Container mailhog will start automatically when you will run all docker containers.
99 | Please check `cookiecutter-django Docker documentation`_ for more details how to start all containers.
100 |
101 | With MailHog running, to view messages that are sent by your application, open your browser and go to ``http://127.0.0.1:8025``
102 |
103 | .. _mailhog: https://github.com/mailhog/MailHog
104 |
105 |
106 |
107 | Sentry
108 | ^^^^^^
109 |
110 | Sentry is an error logging aggregator service. You can sign up for a free account at https://sentry.io/signup/?code=cookiecutter or download and host it yourself.
111 | The system is setup with reasonable defaults, including 404 logging and integration with the WSGI application.
112 |
113 | You must set the DSN url in production.
114 |
115 |
116 | Deployment
117 | ----------
118 |
119 | The following details how to deploy this application.
120 |
121 |
122 | Heroku
123 | ^^^^^^
124 |
125 | See detailed `cookiecutter-django Heroku documentation`_.
126 |
127 | .. _`cookiecutter-django Heroku documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-on-heroku.html
128 |
129 |
130 |
131 | Docker
132 | ^^^^^^
133 |
134 | See detailed `cookiecutter-django Docker documentation`_.
135 |
136 | .. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html
137 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/django_crypto_trading_bot/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load static i18n %}
2 |
3 |
4 |
5 |
6 | {% block title %}Django Crypto Trading Bot{% endblock title %}
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 | {% block css %}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {% endblock %}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
74 |
75 |
76 |
77 |
78 |
79 | {% if messages %}
80 | {% for message in messages %}
81 |