├── .coveragerc ├── .deepsource.toml ├── .dockerignore ├── .editorconfig ├── .envs └── .local │ ├── .django │ └── .postgres ├── .gitattributes ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── .pyup.yml ├── .readthedocs.yml ├── .travis.yml ├── .vscode └── settings.json ├── CONTRIBUTORS.txt ├── LICENSE ├── Procfile ├── README.rst ├── compose ├── local │ ├── django │ │ ├── Dockerfile │ │ └── start │ └── docs │ │ └── Dockerfile └── production │ ├── aws │ ├── Dockerfile │ └── maintenance │ │ ├── download │ │ └── upload │ ├── django │ ├── Dockerfile │ ├── cron │ ├── entrypoint │ └── start │ ├── postgres │ ├── Dockerfile │ └── maintenance │ │ ├── _sourced │ │ ├── constants.sh │ │ ├── countdown.sh │ │ ├── messages.sh │ │ └── yes_no.sh │ │ ├── backup │ │ ├── backups │ │ └── restore │ └── traefik │ ├── Dockerfile │ └── traefik.yml ├── config ├── __init__.py ├── api_router.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── local.py │ ├── production.py │ └── test.py ├── urls.py └── wsgi.py ├── django_crypto_trading_bot ├── __init__.py ├── conftest.py ├── contrib │ ├── __init__.py │ └── sites │ │ ├── __init__.py │ │ └── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_domain_unique.py │ │ ├── 0003_set_site_domain_and_name.py │ │ └── __init__.py ├── static │ ├── css │ │ └── project.css │ ├── fonts │ │ └── .gitkeep │ ├── images │ │ └── favicons │ │ │ └── favicon.ico │ ├── js │ │ └── project.js │ └── sass │ │ ├── custom_bootstrap_vars.scss │ │ └── project.scss ├── templates │ ├── 403.html │ ├── 404.html │ ├── 500.html │ ├── account │ │ ├── account_inactive.html │ │ ├── base.html │ │ ├── email.html │ │ ├── email_confirm.html │ │ ├── login.html │ │ ├── logout.html │ │ ├── password_change.html │ │ ├── password_reset.html │ │ ├── password_reset_done.html │ │ ├── password_reset_from_key.html │ │ ├── password_reset_from_key_done.html │ │ ├── password_set.html │ │ ├── signup.html │ │ ├── signup_closed.html │ │ ├── verification_sent.html │ │ └── verified_email_required.html │ ├── base.html │ ├── pages │ │ ├── about.html │ │ └── home.html │ └── users │ │ ├── user_detail.html │ │ └── user_form.html ├── trading_bot │ ├── __init__.py │ ├── admin.py │ ├── api │ │ ├── __init__.py │ │ ├── client.py │ │ ├── market.py │ │ └── order.py │ ├── apps.py │ ├── exceptions.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── add_markets.py │ │ │ ├── cron.py │ │ │ ├── init_trade.py │ │ │ ├── trade.py │ │ │ └── update_OHLCV.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20200708_1557.py │ │ ├── 0003_auto_20200708_1941.py │ │ ├── 0004_auto_20200709_1344.py │ │ ├── 0005_bot_lock_time.py │ │ └── __init__.py │ ├── models.py │ ├── tests │ │ ├── __init__.py │ │ ├── api_client │ │ │ ├── api_data_example.py │ │ │ ├── test_api_client.py │ │ │ ├── test_api_market.py │ │ │ └── test_api_order.py │ │ ├── conftest.py │ │ ├── factories.py │ │ ├── test_models.py │ │ └── test_trade.py │ ├── trade.py │ └── views.py ├── users │ ├── __init__.py │ ├── adapters.py │ ├── admin.py │ ├── api │ │ ├── serializers.py │ │ └── views.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests │ │ ├── __init__.py │ │ ├── factories.py │ │ ├── test_drf_urls.py │ │ ├── test_drf_views.py │ │ ├── test_forms.py │ │ ├── test_models.py │ │ ├── test_urls.py │ │ └── test_views.py │ ├── urls.py │ └── views.py └── utils │ ├── __init__.py │ ├── context_processors.py │ └── storages.py ├── docs ├── Makefile ├── __init__.py ├── _source │ ├── api │ │ ├── config.rst │ │ ├── config.settings.rst │ │ ├── django_crypto_trading_bot.contrib.rst │ │ ├── django_crypto_trading_bot.contrib.sites.migrations.rst │ │ ├── django_crypto_trading_bot.contrib.sites.rst │ │ ├── django_crypto_trading_bot.rst │ │ ├── django_crypto_trading_bot.trading_bot.api.rst │ │ ├── django_crypto_trading_bot.trading_bot.management.commands.rst │ │ ├── django_crypto_trading_bot.trading_bot.management.rst │ │ ├── django_crypto_trading_bot.trading_bot.migrations.rst │ │ ├── django_crypto_trading_bot.trading_bot.rst │ │ ├── django_crypto_trading_bot.trading_bot.tests.rst │ │ ├── django_crypto_trading_bot.users.migrations.rst │ │ ├── django_crypto_trading_bot.users.rst │ │ ├── django_crypto_trading_bot.users.tests.rst │ │ ├── django_crypto_trading_bot.utils.rst │ │ └── modules.rst │ ├── howto.rst │ ├── index.rst │ ├── trading_bot.rst │ └── users.rst ├── conf.py └── make.bat ├── local.yml ├── locale └── README.rst ├── manage.py ├── merge_production_dotenvs_in_dotenv.py ├── production.yml ├── pytest.ini ├── renovate.json ├── requirements.txt ├── requirements ├── base.txt ├── local.txt └── production.txt ├── runtime.txt └── setup.cfg /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = django_crypto_trading_bot/* 3 | omit = *migrations*, *tests* 4 | plugins = 5 | django_coverage_plugin 6 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | exclude_patterns = [ 4 | "bin/**", 5 | "**/node_modules/", 6 | "js/*/*.min.js" 7 | ] 8 | 9 | test_patterns = [ 10 | "tests/**", 11 | "test_*.py" 12 | ] 13 | 14 | 15 | [[analyzers]] 16 | name = "python" 17 | enabled = true 18 | dependency_file_paths = [ 19 | "requirements/base.txt", 20 | "requirements/local.txt", 21 | "requirements/production.txt", 22 | "Pipfile" 23 | ] 24 | 25 | [analyzers.meta] 26 | runtime_version = "3.x.x" 27 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.coveragerc 3 | !.env 4 | !.pylintrc 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.py] 16 | line_length = 88 17 | known_first_party = django_crypto_trading_bot,config 18 | multi_line_output = 3 19 | default_section = THIRDPARTY 20 | recursive = true 21 | skip = venv/ 22 | skip_glob = **/migrations/*.py 23 | include_trailing_comma = true 24 | force_grid_wrap = 0 25 | use_parentheses = true 26 | 27 | [*.{html,css,scss,json,yml}] 28 | indent_style = space 29 | indent_size = 2 30 | 31 | [*.md] 32 | trim_trailing_whitespace = false 33 | 34 | [Makefile] 35 | indent_style = tab 36 | 37 | [nginx.conf] 38 | indent_style = space 39 | indent_size = 2 40 | -------------------------------------------------------------------------------- /.envs/.local/.django: -------------------------------------------------------------------------------- 1 | # General 2 | # ------------------------------------------------------------------------------ 3 | USE_DOCKER=yes 4 | IPYTHONDIR=/app/.ipython 5 | -------------------------------------------------------------------------------- /.envs/.local/.postgres: -------------------------------------------------------------------------------- 1 | # PostgreSQL 2 | # ------------------------------------------------------------------------------ 3 | POSTGRES_HOST=postgres 4 | POSTGRES_PORT=5432 5 | POSTGRES_DB=django_crypto_trading_bot 6 | POSTGRES_USER=hhfgKNnBooRDOLRZtJEyUZlAuudolGOi 7 | POSTGRES_PASSWORD=hRfbc6Bsx3MMMel3HiQpcRY0JQcU0XM9ht8WMRd6eoq5KALPsJrXBywwfIVcjzrv 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | staticfiles/ 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | # pyenv 63 | .python-version 64 | 65 | 66 | 67 | # Environments 68 | .venv 69 | venv/ 70 | ENV/ 71 | 72 | # Rope project settings 73 | .ropeproject 74 | 75 | # mkdocs documentation 76 | /site 77 | 78 | # mypy 79 | .mypy_cache/ 80 | 81 | 82 | ### Node template 83 | # Logs 84 | logs 85 | *.log 86 | npm-debug.log* 87 | yarn-debug.log* 88 | yarn-error.log* 89 | 90 | # Runtime data 91 | pids 92 | *.pid 93 | *.seed 94 | *.pid.lock 95 | 96 | # Directory for instrumented libs generated by jscoverage/JSCover 97 | lib-cov 98 | 99 | # Coverage directory used by tools like istanbul 100 | coverage 101 | 102 | # nyc test coverage 103 | .nyc_output 104 | 105 | # Bower dependency directory (https://bower.io/) 106 | bower_components 107 | 108 | # node-waf configuration 109 | .lock-wscript 110 | 111 | # Compiled binary addons (http://nodejs.org/api/addons.html) 112 | build/Release 113 | 114 | # Dependency directories 115 | node_modules/ 116 | jspm_packages/ 117 | 118 | # Typescript v1 declaration files 119 | typings/ 120 | 121 | # Optional npm cache directory 122 | .npm 123 | 124 | # Optional eslint cache 125 | .eslintcache 126 | 127 | # Optional REPL history 128 | .node_repl_history 129 | 130 | # Output of 'npm pack' 131 | *.tgz 132 | 133 | # Yarn Integrity file 134 | .yarn-integrity 135 | 136 | 137 | ### Linux template 138 | *~ 139 | 140 | # temporary files which can be created if a process still has a handle open of a deleted file 141 | .fuse_hidden* 142 | 143 | # KDE directory preferences 144 | .directory 145 | 146 | # Linux trash folder which might appear on any partition or disk 147 | .Trash-* 148 | 149 | # .nfs files are created when an open file is removed but is still being accessed 150 | .nfs* 151 | 152 | 153 | ### VisualStudioCode template 154 | .vscode/* 155 | !.vscode/settings.json 156 | !.vscode/tasks.json 157 | !.vscode/launch.json 158 | !.vscode/extensions.json 159 | 160 | 161 | 162 | 163 | 164 | ### Windows template 165 | # Windows thumbnail cache files 166 | Thumbs.db 167 | ehthumbs.db 168 | ehthumbs_vista.db 169 | 170 | # Dump file 171 | *.stackdump 172 | 173 | # Folder config file 174 | Desktop.ini 175 | 176 | # Recycle Bin used on file shares 177 | $RECYCLE.BIN/ 178 | 179 | # Windows Installer files 180 | *.cab 181 | *.msi 182 | *.msm 183 | *.msp 184 | 185 | # Windows shortcuts 186 | *.lnk 187 | 188 | 189 | ### macOS template 190 | # General 191 | *.DS_Store 192 | .AppleDouble 193 | .LSOverride 194 | 195 | # Icon must end with two \r 196 | Icon 197 | 198 | # Thumbnails 199 | ._* 200 | 201 | # Files that might appear in the root of a volume 202 | .DocumentRevisions-V100 203 | .fseventsd 204 | .Spotlight-V100 205 | .TemporaryItems 206 | .Trashes 207 | .VolumeIcon.icns 208 | .com.apple.timemachine.donotpresent 209 | 210 | # Directories potentially created on remote AFP share 211 | .AppleDB 212 | .AppleDesktop 213 | Network Trash Folder 214 | Temporary Items 215 | .apdisk 216 | 217 | 218 | ### SublimeText template 219 | # Cache files for Sublime Text 220 | *.tmlanguage.cache 221 | *.tmPreferences.cache 222 | *.stTheme.cache 223 | 224 | # Workspace files are user-specific 225 | *.sublime-workspace 226 | 227 | # Project files should be checked into the repository, unless a significant 228 | # proportion of contributors will probably not be using Sublime Text 229 | # *.sublime-project 230 | 231 | # SFTP configuration file 232 | sftp-config.json 233 | 234 | # Package control specific files 235 | Package Control.last-run 236 | Package Control.ca-list 237 | Package Control.ca-bundle 238 | Package Control.system-ca-bundle 239 | Package Control.cache/ 240 | Package Control.ca-certs/ 241 | Package Control.merged-ca-bundle 242 | Package Control.user-ca-bundle 243 | oscrypto-ca-bundle.crt 244 | bh_unicode_properties.cache 245 | 246 | # Sublime-github package stores a github token in this file 247 | # https://packagecontrol.io/packages/sublime-github 248 | GitHub.sublime-settings 249 | 250 | 251 | ### Vim template 252 | # Swap 253 | [._]*.s[a-v][a-z] 254 | [._]*.sw[a-p] 255 | [._]s[a-v][a-z] 256 | [._]sw[a-p] 257 | 258 | # Session 259 | Session.vim 260 | 261 | # Temporary 262 | .netrwhist 263 | 264 | # Auto-generated tag files 265 | tags 266 | 267 | 268 | ### Project template 269 | 270 | django_crypto_trading_bot/media/ 271 | 272 | .pytest_cache/ 273 | 274 | 275 | .ipython/ 276 | .env 277 | .envs/* 278 | !.envs/.local/ 279 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: "docs|node_modules|migrations|.git|.tox" 2 | default_stages: [commit] 3 | fail_fast: true 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v3.2.0 8 | hooks: 9 | - id: trailing-whitespace 10 | - id: end-of-file-fixer 11 | - id: check-yaml 12 | 13 | - repo: https://github.com/psf/black 14 | rev: 20.8b1 15 | hooks: 16 | - id: black 17 | 18 | - repo: https://github.com/timothycrosley/isort 19 | rev: 5.5.0 20 | hooks: 21 | - id: isort 22 | 23 | - repo: https://gitlab.com/pycqa/flake8 24 | rev: 3.8.3 25 | hooks: 26 | - id: flake8 27 | args: ["--config=setup.cfg"] 28 | additional_dependencies: [flake8-isort] 29 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | load-plugins=pylint_django 3 | 4 | [FORMAT] 5 | max-line-length=120 6 | 7 | [MESSAGES CONTROL] 8 | disable=missing-docstring,invalid-name 9 | 10 | [DESIGN] 11 | max-parents=13 12 | 13 | [TYPECHECK] 14 | generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete 15 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | formats: all 17 | 18 | python: 19 | version: 3.8 20 | install: 21 | - requirements: requirements/local.txt 22 | - method: pip 23 | path: docs 24 | system_packages: true 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | 3 | language: python 4 | python: 5 | - "3.8" 6 | 7 | services: 8 | - docker 9 | jobs: 10 | include: 11 | - name: "Linter" 12 | before_script: 13 | - pip install -q flake8 14 | script: 15 | - "flake8" 16 | 17 | - name: "Django Test" 18 | before_script: 19 | - echo BINANCE_SANDBOX_API_KEY=$BINANCE_SANDBOX_API_KEY >> .env 20 | - echo BINANCE_SANDBOX_SECRET_KEY=$BINANCE_SANDBOX_SECRET_KEY >> .env 21 | - docker-compose -v 22 | - docker -v 23 | - docker-compose -f local.yml build 24 | # Ensure celerybeat does not crash due to non-existent tables 25 | - docker-compose -f local.yml run --rm django python manage.py migrate 26 | - docker-compose -f local.yml up -d 27 | script: 28 | - "docker-compose -f local.yml run django pytest" 29 | after_failure: 30 | - docker-compose -f local.yml logs 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/bin/python", 3 | "python.linting.pylintEnabled": true, 4 | "python.formatting.provider": "black", 5 | "cSpell.words": [ 6 | "BINANCE", 7 | "OHLCV", 8 | "USDT", 9 | "cctx", 10 | "ccxt", 11 | "nargs", 12 | "ohlcvs", 13 | "prec", 14 | "pytest", 15 | "pytz", 16 | "reorderd", 17 | "retrade", 18 | "tzinfo" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | Steffen Exler 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | Copyright (c) 2020, Steffen Exler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: python manage.py migrate 2 | 3 | web: gunicorn config.wsgi:application 4 | 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /compose/local/django/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-buster 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | ENV PYTHONDONTWRITEBYTECODE 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 | # Requirements are installed here to ensure they will be cached. 18 | COPY ./requirements /requirements 19 | RUN pip install -r /requirements/local.txt 20 | 21 | COPY ./compose/production/django/entrypoint /entrypoint 22 | RUN sed -i 's/\r$//g' /entrypoint 23 | RUN chmod +x /entrypoint 24 | 25 | COPY ./compose/local/django/start /start 26 | RUN sed -i 's/\r$//g' /start 27 | RUN chmod +x /start 28 | 29 | WORKDIR /app 30 | 31 | ENTRYPOINT ["/entrypoint"] 32 | -------------------------------------------------------------------------------- /compose/local/django/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | python manage.py migrate 9 | python manage.py runserver_plus 0.0.0.0:8000 10 | -------------------------------------------------------------------------------- /compose/local/docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-buster 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | ENV PYTHONDONTWRITEBYTECODE 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 | # Uncomment below lines to enable Sphinx output to latex and pdf 14 | # && apt-get install -y texlive-latex-recommended \ 15 | # && apt-get install -y texlive-fonts-recommended \ 16 | # && apt-get install -y texlive-latex-extra \ 17 | # && apt-get install -y latexmk \ 18 | # cleaning up unused files 19 | && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ 20 | && rm -rf /var/lib/apt/lists/* 21 | 22 | # Requirements are installed here to ensure they will be cached. 23 | COPY ./requirements /requirements 24 | # All imports needed for autodoc. 25 | RUN pip install -r /requirements/local.txt -r /requirements/production.txt 26 | 27 | WORKDIR /docs 28 | 29 | CMD make livehtml -------------------------------------------------------------------------------- /compose/production/aws/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM garland/aws-cli-docker:1.16.140 2 | 3 | COPY ./compose/production/aws/maintenance /usr/local/bin/maintenance 4 | COPY ./compose/production/postgres/maintenance/_sourced /usr/local/bin/maintenance/_sourced 5 | 6 | RUN chmod +x /usr/local/bin/maintenance/* 7 | 8 | RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ 9 | && rmdir /usr/local/bin/maintenance 10 | -------------------------------------------------------------------------------- /compose/production/aws/maintenance/download: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### Download a file from your Amazon S3 bucket to the postgres /backups folder 4 | ### 5 | ### Usage: 6 | ### $ docker-compose -f production.yml run --rm awscli <1> 7 | 8 | set -o errexit 9 | set -o pipefail 10 | set -o nounset 11 | 12 | working_dir="$(dirname ${0})" 13 | source "${working_dir}/_sourced/constants.sh" 14 | source "${working_dir}/_sourced/messages.sh" 15 | 16 | export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}" 17 | export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}" 18 | export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}" 19 | 20 | 21 | aws s3 cp s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH}/${1} ${BACKUP_DIR_PATH}/${1} 22 | 23 | message_success "Finished downloading ${1}." 24 | 25 | -------------------------------------------------------------------------------- /compose/production/aws/maintenance/upload: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### Upload the /backups folder to Amazon S3 4 | ### 5 | ### Usage: 6 | ### $ docker-compose -f production.yml run --rm awscli upload 7 | 8 | set -o errexit 9 | set -o pipefail 10 | set -o nounset 11 | 12 | working_dir="$(dirname ${0})" 13 | source "${working_dir}/_sourced/constants.sh" 14 | source "${working_dir}/_sourced/messages.sh" 15 | 16 | export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}" 17 | export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}" 18 | export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}" 19 | 20 | 21 | message_info "Upload the backups directory to S3 bucket {$AWS_STORAGE_BUCKET_NAME}" 22 | 23 | aws s3 cp ${BACKUP_DIR_PATH} s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH} --recursive 24 | 25 | message_info "Cleaning the directory ${BACKUP_DIR_PATH}" 26 | 27 | rm -rf ${BACKUP_DIR_PATH}/* 28 | 29 | message_success "Finished uploading and cleaning." 30 | 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /compose/production/django/cron: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | python /app/manage.py cron 9 | 10 | -------------------------------------------------------------------------------- /compose/production/django/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | 9 | 10 | if [ -z "${POSTGRES_USER}" ]; then 11 | base_postgres_image_default_user='postgres' 12 | export POSTGRES_USER="${base_postgres_image_default_user}" 13 | fi 14 | export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" 15 | 16 | postgres_ready() { 17 | python << END 18 | import sys 19 | 20 | import psycopg2 21 | 22 | try: 23 | psycopg2.connect( 24 | dbname="${POSTGRES_DB}", 25 | user="${POSTGRES_USER}", 26 | password="${POSTGRES_PASSWORD}", 27 | host="${POSTGRES_HOST}", 28 | port="${POSTGRES_PORT}", 29 | ) 30 | except psycopg2.OperationalError: 31 | sys.exit(-1) 32 | sys.exit(0) 33 | 34 | END 35 | } 36 | until postgres_ready; do 37 | >&2 echo 'Waiting for PostgreSQL to become available...' 38 | sleep 1 39 | done 40 | >&2 echo 'PostgreSQL is available' 41 | 42 | exec "$@" 43 | -------------------------------------------------------------------------------- /compose/production/django/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | 8 | python /app/manage.py collectstatic --noinput 9 | 10 | 11 | /usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app 12 | -------------------------------------------------------------------------------- /compose/production/postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:13.8 2 | 3 | COPY ./compose/production/postgres/maintenance /usr/local/bin/maintenance 4 | RUN chmod +x /usr/local/bin/maintenance/* 5 | RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ 6 | && rmdir /usr/local/bin/maintenance 7 | -------------------------------------------------------------------------------- /compose/production/postgres/maintenance/_sourced/constants.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | BACKUP_DIR_PATH='/backups' 5 | BACKUP_FILE_PREFIX='backup' 6 | -------------------------------------------------------------------------------- /compose/production/postgres/maintenance/_sourced/countdown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | countdown() { 5 | declare desc="A simple countdown. Source: https://superuser.com/a/611582" 6 | local seconds="${1}" 7 | local d=$(($(date +%s) + "${seconds}")) 8 | while [ "$d" -ge `date +%s` ]; do 9 | echo -ne "$(date -u --date @$(($d - `date +%s`)) +%H:%M:%S)\r"; 10 | sleep 0.1 11 | done 12 | } 13 | -------------------------------------------------------------------------------- /compose/production/postgres/maintenance/_sourced/messages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | message_newline() { 5 | echo 6 | } 7 | 8 | message_debug() 9 | { 10 | echo -e "DEBUG: ${@}" 11 | } 12 | 13 | message_welcome() 14 | { 15 | echo -e "\e[1m${@}\e[0m" 16 | } 17 | 18 | message_warning() 19 | { 20 | echo -e "\e[33mWARNING\e[0m: ${@}" 21 | } 22 | 23 | message_error() 24 | { 25 | echo -e "\e[31mERROR\e[0m: ${@}" 26 | } 27 | 28 | message_info() 29 | { 30 | echo -e "\e[37mINFO\e[0m: ${@}" 31 | } 32 | 33 | message_suggestion() 34 | { 35 | echo -e "\e[33mSUGGESTION\e[0m: ${@}" 36 | } 37 | 38 | message_success() 39 | { 40 | echo -e "\e[32mSUCCESS\e[0m: ${@}" 41 | } 42 | -------------------------------------------------------------------------------- /compose/production/postgres/maintenance/_sourced/yes_no.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | yes_no() { 5 | declare desc="Prompt for confirmation. \$\"\{1\}\": confirmation message." 6 | local arg1="${1}" 7 | 8 | local response= 9 | read -r -p "${arg1} (y/[n])? " response 10 | if [[ "${response}" =~ ^[Yy]$ ]] 11 | then 12 | exit 0 13 | else 14 | exit 1 15 | fi 16 | } 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /compose/production/postgres/maintenance/backups: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | ### View backups. 5 | ### 6 | ### Usage: 7 | ### $ docker-compose -f .yml (exec |run --rm) postgres backups 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 "These are the backups you have got:" 21 | 22 | ls -lht "${BACKUP_DIR_PATH}" 23 | -------------------------------------------------------------------------------- /compose/production/postgres/maintenance/restore: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | ### Restore database from a backup. 5 | ### 6 | ### Parameters: 7 | ### <1> filename of an existing backup. 8 | ### 9 | ### Usage: 10 | ### $ docker-compose -f .yml (exec |run --rm) postgres restore <1> 11 | 12 | 13 | set -o errexit 14 | set -o pipefail 15 | set -o nounset 16 | 17 | 18 | working_dir="$(dirname ${0})" 19 | source "${working_dir}/_sourced/constants.sh" 20 | source "${working_dir}/_sourced/messages.sh" 21 | 22 | 23 | if [[ -z ${1+x} ]]; then 24 | message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again." 25 | exit 1 26 | fi 27 | backup_filename="${BACKUP_DIR_PATH}/${1}" 28 | if [[ ! -f "${backup_filename}" ]]; then 29 | message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again." 30 | exit 1 31 | fi 32 | 33 | message_welcome "Restoring the '${POSTGRES_DB}' database from the '${backup_filename}' backup..." 34 | 35 | if [[ "${POSTGRES_USER}" == "postgres" ]]; then 36 | message_error "Restoring as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again." 37 | exit 1 38 | fi 39 | 40 | export PGHOST="${POSTGRES_HOST}" 41 | export PGPORT="${POSTGRES_PORT}" 42 | export PGUSER="${POSTGRES_USER}" 43 | export PGPASSWORD="${POSTGRES_PASSWORD}" 44 | export PGDATABASE="${POSTGRES_DB}" 45 | 46 | message_info "Dropping the database..." 47 | dropdb "${PGDATABASE}" 48 | 49 | message_info "Creating a new database..." 50 | createdb --owner="${POSTGRES_USER}" 51 | 52 | message_info "Applying the backup to the new database..." 53 | gunzip -c "${backup_filename}" | psql "${POSTGRES_DB}" 54 | 55 | message_success "The '${POSTGRES_DB}' database has been restored from the '${backup_filename}' backup." 56 | -------------------------------------------------------------------------------- /compose/production/traefik/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM traefik:v2.9 2 | RUN mkdir -p /etc/traefik/acme 3 | RUN touch /etc/traefik/acme/acme.json 4 | RUN chmod 600 /etc/traefik/acme/acme.json 5 | COPY ./compose/production/traefik/traefik.yml /etc/traefik 6 | -------------------------------------------------------------------------------- /compose/production/traefik/traefik.yml: -------------------------------------------------------------------------------- 1 | log: 2 | level: INFO 3 | 4 | entryPoints: 5 | web: 6 | # http 7 | address: ":80" 8 | 9 | web-secure: 10 | # https 11 | address: ":443" 12 | 13 | certificatesResolvers: 14 | letsencrypt: 15 | # https://docs.traefik.io/master/https/acme/#lets-encrypt 16 | acme: 17 | email: "Steffen.Exler@pm.me" 18 | storage: /etc/traefik/acme/acme.json 19 | # https://docs.traefik.io/master/https/acme/#httpchallenge 20 | httpChallenge: 21 | entryPoint: web 22 | 23 | http: 24 | routers: 25 | web-router: 26 | rule: "Host(`django-crypto-trading-bot.org`) || Host(`www.django-crypto-trading-bot.org`)" 27 | 28 | entryPoints: 29 | - web 30 | middlewares: 31 | - redirect 32 | - csrf 33 | service: django 34 | 35 | web-secure-router: 36 | rule: "Host(`django-crypto-trading-bot.org`) || Host(`www.django-crypto-trading-bot.org`)" 37 | 38 | entryPoints: 39 | - web-secure 40 | middlewares: 41 | - csrf 42 | service: django 43 | tls: 44 | # https://docs.traefik.io/master/routing/routers/#certresolver 45 | certResolver: letsencrypt 46 | 47 | middlewares: 48 | redirect: 49 | # https://docs.traefik.io/master/middlewares/redirectscheme/ 50 | redirectScheme: 51 | scheme: https 52 | permanent: true 53 | csrf: 54 | # https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders 55 | # https://docs.djangoproject.com/en/dev/ref/csrf/#ajax 56 | headers: 57 | hostsProxyHeaders: ["X-CSRFToken"] 58 | 59 | services: 60 | django: 61 | loadBalancer: 62 | servers: 63 | - url: http://django:5000 64 | 65 | providers: 66 | # https://docs.traefik.io/master/providers/file/ 67 | file: 68 | filename: /etc/traefik/traefik.yml 69 | watch: true 70 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/config/__init__.py -------------------------------------------------------------------------------- /config/api_router.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from rest_framework.routers import DefaultRouter, SimpleRouter 3 | 4 | from django_crypto_trading_bot.users.api.views import UserViewSet 5 | 6 | if settings.DEBUG: 7 | router = DefaultRouter() 8 | else: 9 | router = SimpleRouter() 10 | 11 | router.register("users", UserViewSet) 12 | 13 | 14 | app_name = "api" 15 | urlpatterns = router.urls 16 | -------------------------------------------------------------------------------- /config/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/config/settings/__init__.py -------------------------------------------------------------------------------- /config/settings/local.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | from .base import env 3 | 4 | # GENERAL 5 | # ------------------------------------------------------------------------------ 6 | # https://docs.djangoproject.com/en/dev/ref/settings/#debug 7 | DEBUG = True 8 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 9 | SECRET_KEY = env( 10 | "DJANGO_SECRET_KEY", 11 | default="fSW2XAQR31cORL5kLFnsflJcaoz5A7lR9G77MWpoXqukkMnHMlAfdXNyTSjoqntI", 12 | ) 13 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts 14 | ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] 15 | 16 | # CACHES 17 | # ------------------------------------------------------------------------------ 18 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches 19 | CACHES = { 20 | "default": { 21 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache", 22 | "LOCATION": "", 23 | } 24 | } 25 | 26 | # EMAIL 27 | # ------------------------------------------------------------------------------ 28 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-host 29 | EMAIL_HOST = env("EMAIL_HOST", default="mailhog") 30 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-port 31 | EMAIL_PORT = 1025 32 | 33 | # WhiteNoise 34 | # ------------------------------------------------------------------------------ 35 | # http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development 36 | INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405 37 | 38 | 39 | # django-debug-toolbar 40 | # ------------------------------------------------------------------------------ 41 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites 42 | INSTALLED_APPS += ["debug_toolbar"] # noqa F405 43 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware 44 | MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa F405 45 | # https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config 46 | DEBUG_TOOLBAR_CONFIG = { 47 | "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"], 48 | "SHOW_TEMPLATE_CONTEXT": True, 49 | } 50 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips 51 | INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"] 52 | if env("USE_DOCKER") == "yes": 53 | import socket 54 | 55 | hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) 56 | INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] 57 | 58 | # django-extensions 59 | # ------------------------------------------------------------------------------ 60 | # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration 61 | INSTALLED_APPS += ["django_extensions"] # noqa F405 62 | 63 | # Your stuff... 64 | # ------------------------------------------------------------------------------ 65 | -------------------------------------------------------------------------------- /config/settings/production.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import sentry_sdk 4 | from sentry_sdk.integrations.django import DjangoIntegration 5 | from sentry_sdk.integrations.logging import LoggingIntegration 6 | from sentry_sdk.integrations.aiohttp import AioHttpIntegration 7 | from sentry_sdk.integrations.redis import RedisIntegration 8 | 9 | from .base import * # noqa 10 | from .base import env 11 | 12 | # GENERAL 13 | # ------------------------------------------------------------------------------ 14 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 15 | SECRET_KEY = env("DJANGO_SECRET_KEY") 16 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts 17 | ALLOWED_HOSTS = env.list( 18 | "DJANGO_ALLOWED_HOSTS", default=["django-crypto-trading-bot.org"] 19 | ) 20 | 21 | # DATABASES 22 | # ------------------------------------------------------------------------------ 23 | DATABASES["default"] = env.db("DATABASE_URL") # noqa F405 24 | DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405 25 | DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405 26 | 27 | # CACHES 28 | # ------------------------------------------------------------------------------ 29 | CACHES = { 30 | "default": { 31 | "BACKEND": "django_redis.cache.RedisCache", 32 | "LOCATION": env("REDIS_URL"), 33 | "OPTIONS": { 34 | "CLIENT_CLASS": "django_redis.client.DefaultClient", 35 | # Mimicing memcache behavior. 36 | # http://jazzband.github.io/django-redis/latest/#_memcached_exceptions_behavior 37 | "IGNORE_EXCEPTIONS": True, 38 | }, 39 | } 40 | } 41 | 42 | # SECURITY 43 | # ------------------------------------------------------------------------------ 44 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header 45 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") 46 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect 47 | SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True) 48 | # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure 49 | SESSION_COOKIE_SECURE = True 50 | # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure 51 | CSRF_COOKIE_SECURE = True 52 | # https://docs.djangoproject.com/en/dev/topics/security/#ssl-https 53 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds 54 | # TODO: set this to 60 seconds first and then to 518400 once you prove the former works 55 | SECURE_HSTS_SECONDS = 60 56 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains 57 | SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( 58 | "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True 59 | ) 60 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload 61 | SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) 62 | # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff 63 | SECURE_CONTENT_TYPE_NOSNIFF = env.bool( 64 | "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True 65 | ) 66 | 67 | # STORAGES 68 | # ------------------------------------------------------------------------------ 69 | # https://django-storages.readthedocs.io/en/latest/#installation 70 | INSTALLED_APPS += ["storages"] # noqa F405 71 | # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings 72 | AWS_ACCESS_KEY_ID = env("DJANGO_AWS_ACCESS_KEY_ID") 73 | # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings 74 | AWS_SECRET_ACCESS_KEY = env("DJANGO_AWS_SECRET_ACCESS_KEY") 75 | # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings 76 | AWS_STORAGE_BUCKET_NAME = env("DJANGO_AWS_STORAGE_BUCKET_NAME") 77 | # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings 78 | AWS_QUERYSTRING_AUTH = False 79 | # DO NOT change these unless you know what you're doing. 80 | _AWS_EXPIRY = 60 * 60 * 24 * 7 81 | # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings 82 | AWS_S3_OBJECT_PARAMETERS = { 83 | "CacheControl": f"max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate" 84 | } 85 | # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings 86 | AWS_DEFAULT_ACL = None 87 | # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings 88 | AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) 89 | # STATIC 90 | # ------------------------ 91 | STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" 92 | # MEDIA 93 | # ------------------------------------------------------------------------------ 94 | DEFAULT_FILE_STORAGE = ( 95 | "django_crypto_trading_bot.utils.storages.MediaRootS3Boto3Storage" 96 | ) 97 | MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/" 98 | 99 | # TEMPLATES 100 | # ------------------------------------------------------------------------------ 101 | # https://docs.djangoproject.com/en/dev/ref/settings/#templates 102 | TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405 103 | ( 104 | "django.template.loaders.cached.Loader", 105 | [ 106 | "django.template.loaders.filesystem.Loader", 107 | "django.template.loaders.app_directories.Loader", 108 | ], 109 | ) 110 | ] 111 | 112 | # EMAIL 113 | # ------------------------------------------------------------------------------ 114 | # https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email 115 | DEFAULT_FROM_EMAIL = env( 116 | "DJANGO_DEFAULT_FROM_EMAIL", 117 | default="Django Crypto Trading Bot ", 118 | ) 119 | # https://docs.djangoproject.com/en/dev/ref/settings/#server-email 120 | SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) 121 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix 122 | EMAIL_SUBJECT_PREFIX = env( 123 | "DJANGO_EMAIL_SUBJECT_PREFIX", default="[Django Crypto Trading Bot]" 124 | ) 125 | 126 | # ADMIN 127 | # ------------------------------------------------------------------------------ 128 | # Django Admin URL regex. 129 | ADMIN_URL = env("DJANGO_ADMIN_URL") 130 | 131 | # # Anymail 132 | # # ------------------------------------------------------------------------------ 133 | # # https://anymail.readthedocs.io/en/stable/installation/#installing-anymail 134 | # INSTALLED_APPS += ["anymail"] # noqa F405 135 | # # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 136 | # # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference 137 | # # https://anymail.readthedocs.io/en/stable/esps/mailgun/ 138 | # EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" 139 | # ANYMAIL = { 140 | # "MAILGUN_API_KEY": env("MAILGUN_API_KEY"), 141 | # "MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"), 142 | # "MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"), 143 | # } 144 | 145 | 146 | # LOGGING 147 | # ------------------------------------------------------------------------------ 148 | # https://docs.djangoproject.com/en/dev/ref/settings/#logging 149 | # See https://docs.djangoproject.com/en/dev/topics/logging for 150 | # more details on how to customize your logging configuration. 151 | 152 | LOGGING = { 153 | "version": 1, 154 | "disable_existing_loggers": True, 155 | "formatters": { 156 | "verbose": { 157 | "format": "%(levelname)s %(asctime)s %(module)s " 158 | "%(process)d %(thread)d %(message)s" 159 | } 160 | }, 161 | "handlers": { 162 | "console": { 163 | "level": "DEBUG", 164 | "class": "logging.StreamHandler", 165 | "formatter": "verbose", 166 | } 167 | }, 168 | "root": {"level": "INFO", "handlers": ["console"]}, 169 | "loggers": { 170 | "django.db.backends": { 171 | "level": "ERROR", 172 | "handlers": ["console"], 173 | "propagate": False, 174 | }, 175 | # Errors logged by the SDK itself 176 | "sentry_sdk": {"level": "ERROR", "handlers": ["console"], "propagate": False}, 177 | "django.security.DisallowedHost": { 178 | "level": "ERROR", 179 | "handlers": ["console"], 180 | "propagate": False, 181 | }, 182 | }, 183 | } 184 | 185 | # Sentry 186 | # ------------------------------------------------------------------------------ 187 | SENTRY_DSN = env("SENTRY_DSN") 188 | SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO) 189 | 190 | sentry_logging = LoggingIntegration( 191 | level=SENTRY_LOG_LEVEL, # Capture info and above as breadcrumbs 192 | event_level=logging.ERROR, # Send errors as events 193 | ) 194 | sentry_sdk.init( 195 | dsn=SENTRY_DSN, 196 | integrations=[ 197 | sentry_logging, 198 | DjangoIntegration(), 199 | AioHttpIntegration(), 200 | RedisIntegration(), 201 | ], 202 | ) 203 | 204 | # Your stuff... 205 | # ------------------------------------------------------------------------------ 206 | -------------------------------------------------------------------------------- /config/settings/test.py: -------------------------------------------------------------------------------- 1 | """ 2 | With these settings, tests run faster. 3 | """ 4 | 5 | from .base import * # noqa 6 | from .base import env 7 | 8 | # GENERAL 9 | # ------------------------------------------------------------------------------ 10 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 11 | SECRET_KEY = env( 12 | "DJANGO_SECRET_KEY", 13 | default="Nm6r6iMjUNIWxqikwMBKiCVOTPN9mvTKUVYGYbjkTq20XnRNRRGljDjP9RZvGBzc", 14 | ) 15 | # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner 16 | TEST_RUNNER = "django.test.runner.DiscoverRunner" 17 | 18 | # CACHES 19 | # ------------------------------------------------------------------------------ 20 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches 21 | CACHES = { 22 | "default": { 23 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache", 24 | "LOCATION": "", 25 | } 26 | } 27 | 28 | # PASSWORDS 29 | # ------------------------------------------------------------------------------ 30 | # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers 31 | PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] 32 | 33 | # TEMPLATES 34 | # ------------------------------------------------------------------------------ 35 | TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405 36 | ( 37 | "django.template.loaders.cached.Loader", 38 | [ 39 | "django.template.loaders.filesystem.Loader", 40 | "django.template.loaders.app_directories.Loader", 41 | ], 42 | ) 43 | ] 44 | 45 | # EMAIL 46 | # ------------------------------------------------------------------------------ 47 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 48 | EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" 49 | 50 | # Your stuff... 51 | # ------------------------------------------------------------------------------ 52 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.urls import include, path 5 | from django.views import defaults as default_views 6 | from django.views.generic import TemplateView 7 | from rest_framework.authtoken.views import obtain_auth_token 8 | 9 | urlpatterns = [ 10 | path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), 11 | path( 12 | "about/", TemplateView.as_view(template_name="pages/about.html"), name="about" 13 | ), 14 | # Django Admin, use {% url 'admin:index' %} 15 | path(settings.ADMIN_URL, admin.site.urls), 16 | # User management 17 | path("users/", include("django_crypto_trading_bot.users.urls", namespace="users")), 18 | path("accounts/", include("allauth.urls")), 19 | # Your stuff: custom urls includes go here 20 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 21 | 22 | # API URLS 23 | urlpatterns += [ 24 | # API base url 25 | path("api/", include("config.api_router")), 26 | # DRF auth token 27 | path("auth-token/", obtain_auth_token), 28 | ] 29 | 30 | if settings.DEBUG: 31 | # This allows the error pages to be debugged during development, just visit 32 | # these url in browser to see how these error pages look like. 33 | urlpatterns += [ 34 | path( 35 | "400/", 36 | default_views.bad_request, 37 | kwargs={"exception": Exception("Bad Request!")}, 38 | ), 39 | path( 40 | "403/", 41 | default_views.permission_denied, 42 | kwargs={"exception": Exception("Permission Denied")}, 43 | ), 44 | path( 45 | "404/", 46 | default_views.page_not_found, 47 | kwargs={"exception": Exception("Page not Found")}, 48 | ), 49 | path("500/", default_views.server_error), 50 | ] 51 | if "debug_toolbar" in settings.INSTALLED_APPS: 52 | import debug_toolbar 53 | 54 | urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns 55 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for Django Crypto Trading Bot project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | import sys 18 | from pathlib import Path 19 | 20 | from django.core.wsgi import get_wsgi_application 21 | 22 | # This allows easy placement of apps within the interior 23 | # django_crypto_trading_bot directory. 24 | ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent 25 | sys.path.append(str(ROOT_DIR / "django_crypto_trading_bot")) 26 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 27 | # if running multiple sites in the same mod_wsgi process. To fix this, use 28 | # mod_wsgi daemon mode with each site in its own daemon process, or use 29 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" 30 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") 31 | 32 | # This application object is used by any WSGI server configured to use this 33 | # file. This includes Django's development server, if the WSGI_APPLICATION 34 | # setting points here. 35 | application = get_wsgi_application() 36 | # Apply WSGI middleware here. 37 | # from helloworld.wsgi import HelloWorldApplication 38 | # application = HelloWorldApplication(application) 39 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | __version_info__ = tuple( 3 | [ 4 | int(num) if num.isdigit() else num 5 | for num in __version__.replace("-", ".", 1).split(".") 6 | ] 7 | ) 8 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from django_crypto_trading_bot.users.models import User 4 | from django_crypto_trading_bot.users.tests.factories import UserFactory 5 | 6 | 7 | @pytest.fixture(autouse=True) 8 | def media_storage(settings, tmpdir): 9 | settings.MEDIA_ROOT = tmpdir.strpath 10 | 11 | 12 | @pytest.fixture 13 | def user() -> User: 14 | return UserFactory() 15 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/contrib/__init__.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 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/contrib/sites/__init__.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/contrib/sites/migrations/0002_alter_domain_unique.py: -------------------------------------------------------------------------------- 1 | import django.contrib.sites.models 2 | from django.db import migrations, models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [("sites", "0001_initial")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="site", 12 | name="domain", 13 | field=models.CharField( 14 | max_length=100, 15 | unique=True, 16 | validators=[django.contrib.sites.models._simple_domain_name_validator], 17 | verbose_name="domain name", 18 | ), 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /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/contrib/sites/migrations/__init__.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 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/static/css/project.css: -------------------------------------------------------------------------------- 1 | /* These styles are generated from project.scss. */ 2 | 3 | .alert-debug { 4 | color: black; 5 | background-color: white; 6 | border-color: #d6e9c6; 7 | } 8 | 9 | .alert-error { 10 | color: #b94a48; 11 | background-color: #f2dede; 12 | border-color: #eed3d7; 13 | } 14 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/static/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/static/fonts/.gitkeep -------------------------------------------------------------------------------- /django_crypto_trading_bot/static/images/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/static/images/favicons/favicon.ico -------------------------------------------------------------------------------- /django_crypto_trading_bot/static/js/project.js: -------------------------------------------------------------------------------- 1 | /* Project specific Javascript goes here. */ 2 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/static/sass/custom_bootstrap_vars.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/static/sass/custom_bootstrap_vars.scss -------------------------------------------------------------------------------- /django_crypto_trading_bot/static/sass/project.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | // project specific CSS goes here 6 | 7 | //////////////////////////////// 8 | //Variables// 9 | //////////////////////////////// 10 | 11 | // Alert colors 12 | 13 | $white: #fff; 14 | $mint-green: #d6e9c6; 15 | $black: #000; 16 | $pink: #f2dede; 17 | $dark-pink: #eed3d7; 18 | $red: #b94a48; 19 | 20 | //////////////////////////////// 21 | //Alerts// 22 | //////////////////////////////// 23 | 24 | // bootstrap alert CSS, translated to the django-standard levels of 25 | // debug, info, success, warning, error 26 | 27 | .alert-debug { 28 | background-color: $white; 29 | border-color: $mint-green; 30 | color: $black; 31 | } 32 | 33 | .alert-error { 34 | background-color: $pink; 35 | border-color: $dark-pink; 36 | color: $red; 37 | } 38 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Forbidden (403){% endblock %} 4 | 5 | {% block content %} 6 |

Forbidden (403)

7 | 8 |

CSRF verification failed. Request aborted.

9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page not found{% endblock %} 4 | 5 | {% block content %} 6 |

Page not found

7 | 8 |

This is not the page you were looking for.

9 | {% endblock content %} 10 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Server Error{% endblock %} 4 | 5 | {% block content %} 6 |

Ooops!!! 500

7 | 8 |

Looks like something went wrong!

9 | 10 |

We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.

11 | {% endblock content %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/account_inactive.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Account Inactive" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Account Inactive" %}

9 | 10 |

{% trans "This account is inactive." %}

11 | {% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} 3 | 4 | {% block content %} 5 |
6 |
7 | {% block inner %}{% endblock %} 8 |
9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/email.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "account/base.html" %} 3 | 4 | {% load i18n %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Account" %}{% endblock %} 8 | 9 | {% block inner %} 10 |

{% trans "E-mail Addresses" %}

11 | 12 | {% if user.emailaddress_set.all %} 13 |

{% 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 |
54 | {% csrf_token %} 55 | {{ form|crispy }} 56 | 57 |
58 | 59 | {% endblock %} 60 | 61 | 62 | {% block javascript %} 63 | {{ block.super }} 64 | 79 | {% endblock %} 80 | 81 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} 7 | 8 | 9 | {% block inner %} 10 |

{% trans "Confirm E-mail Address" %}

11 | 12 | {% if confirmation %} 13 | 14 | {% user_display confirmation.email_address.user as user_display %} 15 | 16 |

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

17 | 18 |
19 | {% csrf_token %} 20 | 21 |
22 | 23 | {% else %} 24 | 25 | {% url 'account_email' as email_url %} 26 | 27 |

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

28 | 29 | {% endif %} 30 | 31 | {% endblock %} 32 | 33 | -------------------------------------------------------------------------------- /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 |
25 | 26 | 27 | 28 |
29 | 30 | {% include "socialaccount/snippets/login_extra.html" %} 31 | 32 | {% else %} 33 |

{% blocktrans %}If you have not created an account yet, then please 34 | sign up first.{% endblocktrans %}

35 | {% endif %} 36 | 37 | 46 | 47 | {% endblock %} 48 | 49 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Out" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Sign Out" %}

9 | 10 |

{% trans 'Are you sure you want to sign out?' %}

11 | 12 |
13 | {% csrf_token %} 14 | {% if redirect_field_value %} 15 | 16 | {% endif %} 17 | 18 |
19 | 20 | 21 | {% endblock %} 22 | 23 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Change Password" %}

10 | 11 |
12 | {% csrf_token %} 13 | {{ form|crispy }} 14 | 15 |
16 | {% endblock %} 17 | 18 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 8 | 9 | {% block inner %} 10 | 11 |

{% trans "Password Reset" %}

12 | {% if user.is_authenticated %} 13 | {% include "account/snippets/already_logged_in.html" %} 14 | {% endif %} 15 | 16 |

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

17 | 18 |
19 | {% csrf_token %} 20 | {{ form|crispy }} 21 | 22 |
23 | 24 |

{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}

25 | {% endblock %} 26 | 27 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Password Reset" %}

10 | 11 | {% if user.is_authenticated %} 12 | {% include "account/snippets/already_logged_in.html" %} 13 | {% endif %} 14 | 15 |

{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

16 | {% endblock %} 17 | 18 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% 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 |
16 | {% csrf_token %} 17 | {{ form|crispy }} 18 | 19 |
20 | {% else %} 21 |

{% trans 'Your password is now changed.' %}

22 | {% endif %} 23 | {% endif %} 24 | {% endblock %} 25 | 26 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 5 | 6 | {% block inner %} 7 |

{% trans "Change Password" %}

8 |

{% trans 'Your password is now changed.' %}

9 | {% endblock %} 10 | 11 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Set Password" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Set Password" %}

10 | 11 |
12 | {% csrf_token %} 13 | {{ form|crispy }} 14 | 15 |
16 | {% endblock %} 17 | 18 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Signup" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Sign Up" %}

10 | 11 |

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

12 | 13 | 21 | 22 | {% endblock %} 23 | 24 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Sign Up Closed" %}

9 | 10 |

{% trans "We are sorry, but the sign up is currently closed." %}

11 | {% endblock %} 12 | 13 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Verify Your E-mail Address" %}

9 | 10 |

{% 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/templates/account/verified_email_required.html: -------------------------------------------------------------------------------- 1 | {% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Verify Your E-mail Address" %}

9 | 10 | {% url 'account_email' as email_url %} 11 | 12 |

{% 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 %}

19 | 20 |

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

21 | 22 | 23 | {% endblock %} 24 | 25 | -------------------------------------------------------------------------------- /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 |
{{ message }}
82 | {% endfor %} 83 | {% endif %} 84 | 85 | {% block content %} 86 |

Use this document as a way to quick start any new project.

87 | {% endblock content %} 88 | 89 |
90 | 91 | {% block modal %}{% endblock modal %} 92 | 93 | 95 | 96 | {% block javascript %} 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | {% endblock javascript %} 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/pages/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/pages/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} -------------------------------------------------------------------------------- /django_crypto_trading_bot/templates/users/user_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block title %}User: {{ object.username }}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | 9 |
10 |
11 | 12 |

{{ object.username }}

13 | {% if object.name %} 14 |

{{ object.name }}

15 | {% endif %} 16 |
17 |
18 | 19 | {% if object == request.user %} 20 | 21 |
22 | 23 |
24 | My Info 25 | E-Mail 26 | 27 |
28 | 29 |
30 | 31 | {% endif %} 32 | 33 | 34 |
35 | {% endblock content %} 36 | 37 | -------------------------------------------------------------------------------- /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 |

{{ user.username }}

8 |
9 | {% csrf_token %} 10 | {{ form|crispy }} 11 |
12 |
13 | 14 |
15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/trading_bot/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import ( 4 | OHLCV, 5 | Account, 6 | Bot, 7 | Currency, 8 | Market, 9 | Order, 10 | OrderErrorLog, 11 | Saving, 12 | Trade, 13 | ) 14 | 15 | 16 | class BotInline(admin.TabularInline): 17 | model = Bot 18 | extra = 0 19 | 20 | 21 | class OrderInline(admin.TabularInline): 22 | model = Order 23 | extra = 0 24 | 25 | 26 | class TradeInline(admin.TabularInline): 27 | model = Trade 28 | extra = 0 29 | 30 | 31 | class ErrorInline(admin.TabularInline): 32 | model = OrderErrorLog 33 | extra = 0 34 | 35 | 36 | class SavingInline(admin.TabularInline): 37 | model = Saving 38 | extra = 0 39 | 40 | 41 | class AccountAdmin(admin.ModelAdmin): 42 | fieldsets = [ 43 | ("User", {"fields": ["user"]}), 44 | ( 45 | "Exchange information", 46 | { 47 | "fields": [ 48 | "exchange", 49 | "api_key", 50 | "secret", 51 | "password", 52 | "default_fee_rate", 53 | ] 54 | }, 55 | ), 56 | ] 57 | 58 | list_display = ("user", "exchange") 59 | list_filter = ["user", "exchange"] 60 | search_fields = ["user"] 61 | 62 | inlines = [BotInline] 63 | 64 | 65 | class CurrencyAdmin(admin.ModelAdmin): 66 | fields = ["short", "name"] 67 | 68 | list_display = ("short", "name") 69 | search_fields = ["short", "name"] 70 | 71 | 72 | class MarketAdmin(admin.ModelAdmin): 73 | fieldsets = [ 74 | ("Base", {"fields": ["exchange", "active"]}), 75 | ("Currencies", {"fields": ["base", "quote"]}), 76 | ( 77 | "Market information", 78 | { 79 | "classes": ("collapse",), 80 | "fields": [ 81 | "precision_amount", 82 | "precision_price", 83 | "limits_amount_min", 84 | "limits_amount_max", 85 | "limits_price_min", 86 | "limits_price_max", 87 | ], 88 | }, 89 | ), 90 | ] 91 | 92 | list_display = ("symbol", "exchange", "active") 93 | list_filter = ["exchange", "active"] 94 | search_fields = ["base", "quote"] 95 | 96 | 97 | class BotAdmin(admin.ModelAdmin): 98 | readonly_fields = ( 99 | "created", 100 | "start_amount", 101 | "current_amount", 102 | "estimate_current_amount", 103 | "roi", 104 | "estimate_roi", 105 | "orders_count", 106 | ) 107 | 108 | fieldsets = [ 109 | ("Base", {"fields": ["account", "active"]}), 110 | ("Trade Mode: Wave Rider", {"fields": ["market", "timeframe"]}), 111 | ( 112 | "Trade Mode: Rising Chart", 113 | {"fields": ["quote", "max_amount", "min_rise", "stop_loss", "lock_time"]}, 114 | ), 115 | ( 116 | "Stats", 117 | { 118 | "fields": [ 119 | "start_amount", 120 | "current_amount", 121 | "estimate_current_amount", 122 | "roi", 123 | "estimate_roi", 124 | "orders_count", 125 | ] 126 | }, 127 | ), 128 | ] 129 | 130 | list_display = ( 131 | "account", 132 | "market", 133 | "created", 134 | "timeframe", 135 | "active", 136 | "roi", 137 | "estimate_roi", 138 | "orders_count", 139 | ) 140 | list_filter = ["account", "timeframe", "active"] 141 | search_fields = ["account", "market", "created"] 142 | 143 | inlines = [OrderInline, SavingInline] 144 | 145 | 146 | class OrderAdmin(admin.ModelAdmin): 147 | readonly_fields = ("errors",) 148 | 149 | fieldsets = [ 150 | ("Base", {"fields": ["bot", "errors"]}), 151 | ( 152 | "Order", 153 | { 154 | "fields": [ 155 | "next_order", 156 | "order_id", 157 | "timestamp", 158 | "status", 159 | "order_type", 160 | "side", 161 | "price", 162 | "amount", 163 | "filled", 164 | ] 165 | }, 166 | ), 167 | ("Fee", {"fields": ["fee_currency", "fee_cost", "fee_rate"]}), 168 | ("Rising Chart", {"fields": ["last_price_tick", "market"]}), 169 | ] 170 | 171 | list_display = ( 172 | "next_order", 173 | "order_id", 174 | "bot", 175 | "timestamp", 176 | "status", 177 | "side", 178 | "price", 179 | "errors", 180 | ) 181 | list_filter = ["bot", "timestamp", "status", "side"] 182 | search_fields = [ 183 | "bot", 184 | "order_id", 185 | "timestamp", 186 | "status", 187 | "order_type", 188 | "side", 189 | "price", 190 | "amount", 191 | "filled", 192 | ] 193 | 194 | # inlines = [TradeInline] 195 | inlines = [ErrorInline] 196 | 197 | 198 | class TradeAdmin(admin.ModelAdmin): 199 | fieldsets = [ 200 | ( 201 | None, 202 | { 203 | "fields": [ 204 | "order", 205 | "trade_id", 206 | "timestamp", 207 | "taker_or_maker", 208 | "amount", 209 | "fee_currency", 210 | "fee_cost", 211 | "fee_rate", 212 | ] 213 | }, 214 | ), 215 | ] 216 | 217 | list_display = ( 218 | "order", 219 | "trade_id", 220 | "timestamp", 221 | "taker_or_maker", 222 | "amount", 223 | "fee_rate", 224 | ) 225 | list_filter = ["taker_or_maker", "timestamp"] 226 | search_fields = [ 227 | "order", 228 | "trade_id", 229 | "timestamp", 230 | "taker_or_maker", 231 | "amount", 232 | "fee_currency", 233 | "fee_cost", 234 | "fee_rate", 235 | ] 236 | 237 | 238 | class OHLCVAdmin(admin.ModelAdmin): 239 | fieldsets = [ 240 | ( 241 | None, 242 | { 243 | "fields": [ 244 | "market", 245 | "timeframe", 246 | "timestamp", 247 | "open_price", 248 | "highest_price", 249 | "lowest_price", 250 | "closing_price", 251 | "volume", 252 | ] 253 | }, 254 | ), 255 | ] 256 | 257 | list_display = ( 258 | "market", 259 | "timeframe", 260 | "timestamp", 261 | "open_price", 262 | "highest_price", 263 | "lowest_price", 264 | "closing_price", 265 | "volume", 266 | ) 267 | list_filter = ["market", "timeframe", "timestamp"] 268 | search_fields = [ 269 | "market", 270 | "timeframe", 271 | "timestamp", 272 | "open_price", 273 | "highest_price", 274 | "lowest_price", 275 | "closing_price", 276 | "volume", 277 | ] 278 | 279 | 280 | admin.site.register(Account, AccountAdmin) 281 | admin.site.register(Bot, BotAdmin) 282 | admin.site.register(Currency, CurrencyAdmin) 283 | admin.site.register(Market, MarketAdmin) 284 | admin.site.register(Order, OrderAdmin) 285 | admin.site.register(Trade, TradeAdmin) 286 | admin.site.register(OHLCV, OHLCVAdmin) 287 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/trading_bot/api/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/api/client.py: -------------------------------------------------------------------------------- 1 | from ccxt.base.exchange import Exchange 2 | import ccxt 3 | 4 | 5 | def get_client(exchange_id: str, api_key: str = None, secret: str = None) -> Exchange: 6 | exchange_class = getattr(ccxt, exchange_id) 7 | return exchange_class( 8 | {"apiKey": api_key, "secret": secret, "timeout": 30000, "enableRateLimit": True} 9 | ) 10 | -------------------------------------------------------------------------------- /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/api/order.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from datetime import datetime 4 | from decimal import Decimal 5 | from typing import Optional 6 | 7 | import pytz 8 | from ccxt import Exchange 9 | from django.utils import timezone 10 | from django_crypto_trading_bot.trading_bot.models import ( 11 | Bot, 12 | Currency, 13 | Market, 14 | Order, 15 | Trade, 16 | ) 17 | 18 | from ..exceptions import NoMarket 19 | 20 | 21 | def create_order( 22 | amount: Decimal, 23 | side: Order.Side, 24 | bot: Bot, 25 | market: Market, 26 | price: Optional[Decimal] = None, 27 | isTestOrder: bool = False, 28 | ) -> Order: 29 | """ 30 | Create an order 31 | :param amount: amount you want to buy 32 | :param side: sell or buy order? 33 | :param botId: id of the bot which has placed the order 34 | :param price: the price you want to spend 35 | :param isTestOrder: is this a test order? 36 | :return: Order object 37 | """ 38 | exchange: Exchange = bot.account.get_account_client() 39 | 40 | params = {"test": isTestOrder} # test if it's valid, but don't actually place it 41 | 42 | order_type: str = "LIMIT" 43 | if not price: 44 | order_type = "MARKET" 45 | 46 | if isTestOrder: 47 | 48 | if not price: 49 | price = Decimal(1) 50 | 51 | return Order.objects.create( 52 | bot=bot, 53 | status=Order.Status.OPEN, 54 | order_id=len(Order.objects.all()) + 1, 55 | order_type=order_type, 56 | side=side, 57 | timestamp=timezone.now(), 58 | price=price, 59 | amount=amount, 60 | filled=Decimal(0), 61 | ) 62 | else: 63 | cctx_order: dict 64 | if not price: 65 | cctx_order = exchange.create_order( 66 | market.symbol, order_type, side, amount, params 67 | ) 68 | 69 | return create_order_from_api_response(cctx_order, bot) 70 | else: 71 | cctx_order = exchange.create_order( 72 | market.symbol, order_type, side, amount, price, params 73 | ) 74 | 75 | return create_order_from_api_response(cctx_order, bot) 76 | 77 | 78 | def create_order_from_api_response(cctx_order: dict, bot: Bot) -> Order: 79 | """ 80 | Parse response api response into object 81 | :param cctx_order: the api response object 82 | :param botId: the id of the bot which has fired the request 83 | :return: Order object 84 | """ 85 | side: Order.Side 86 | if cctx_order["side"] == "buy": 87 | side = Order.Side.SIDE_BUY 88 | else: 89 | side = Order.Side.SIDE_SELL 90 | 91 | return Order.objects.create( 92 | bot=bot, 93 | status=cctx_order["status"], 94 | order_id=cctx_order["id"], 95 | order_type=cctx_order["type"], 96 | side=side, 97 | timestamp=datetime.fromtimestamp( 98 | cctx_order["timestamp"] / 1000, tz=pytz.timezone("UTC") 99 | ), 100 | price=Decimal(cctx_order["price"]), 101 | amount=Decimal(cctx_order["amount"]), 102 | filled=Decimal(cctx_order["filled"]), 103 | ) 104 | 105 | 106 | def update_order_from_api_response(cctx_order: dict, order: Order) -> Order: 107 | """ 108 | Parse API response to update a order object 109 | """ 110 | order.status = cctx_order["status"] 111 | order.filled = Decimal(cctx_order["filled"]) 112 | 113 | if "fee" in cctx_order: 114 | if cctx_order["fee"]: 115 | currency, c_created = Currency.objects.get_or_create( 116 | short=cctx_order["fee"]["currency"], 117 | ) 118 | order.fee_currency = currency 119 | order.fee_cost = Decimal(cctx_order["fee"]["cost"]) 120 | order.fee_rate = Decimal(cctx_order["fee"]["rate"]) 121 | 122 | if order.filled: 123 | if cctx_order["trades"]: 124 | for order_trade in cctx_order["trades"]: 125 | currency, c_created = Currency.objects.get_or_create( 126 | short=order_trade["fee"]["currency"], 127 | ) 128 | 129 | trade, created = Trade.objects.get_or_create( 130 | order=order, 131 | trade_id=order_trade["id"], 132 | timestamp=datetime.fromtimestamp( 133 | order_trade["timestamp"] / 1000, tz=pytz.timezone("UTC") 134 | ), 135 | taker_or_maker=order_trade["takerOrMaker"], 136 | amount=Decimal(order_trade["amount"]), 137 | fee_currency=currency, 138 | fee_cost=Decimal(order_trade["fee"]["cost"]), 139 | fee_rate=Decimal(order_trade["fee"]["rate"]), 140 | ) 141 | 142 | order.save() 143 | return order 144 | 145 | 146 | def get_order_from_exchange(order: Order): 147 | """ 148 | get order from api repsonse & update the order model 149 | """ 150 | exchange: Exchange = order.bot.account.get_account_client() 151 | 152 | cctx_order: dict 153 | if order.market: 154 | cctx_order = exchange.fetch_order(id=order.order_id, symbol=order.market.symbol) 155 | elif order.bot.market: 156 | cctx_order = exchange.fetch_order( 157 | id=order.order_id, symbol=order.bot.market.symbol 158 | ) 159 | else: 160 | # todo test exception 161 | raise NoMarket("Bot & order has no market!") 162 | 163 | return update_order_from_api_response(cctx_order=cctx_order, order=order) 164 | 165 | 166 | def update_all_open_orders(): 167 | """ 168 | update all open orders 169 | """ 170 | for order in Order.objects.filter(status=Order.Status.OPEN): 171 | get_order_from_exchange(order=order) 172 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TradingBotConfig(AppConfig): 5 | name = "trading_bot" 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/trading_bot/management/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/trading_bot/management/commands/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/management/commands/add_markets.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django_crypto_trading_bot.trading_bot.api.market import ( 3 | get_all_markets_from_exchange, 4 | ) 5 | 6 | 7 | class Command(BaseCommand): 8 | help = "Add & update all markets from exchange in db" 9 | 10 | def add_arguments(self, parser): 11 | parser.add_argument( 12 | "exchange", 13 | nargs="?", 14 | type=str, 15 | help="Exchange like binance", 16 | default="binance", 17 | ) 18 | 19 | def handle(self, *args, **options): 20 | 21 | # todo check if exchange exists 22 | exchange: str = options["exchange"].lower() 23 | 24 | # add & update all markets from exchange in db 25 | get_all_markets_from_exchange(exchange) 26 | 27 | print("All markets for {} added & updated in the database!".format(exchange)) 28 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/management/commands/cron.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import threading 3 | import time 4 | from datetime import datetime 5 | from time import sleep 6 | 7 | from crontab import CronTab 8 | from django.core.management.base import BaseCommand 9 | 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 update_all_open_orders 14 | from django_crypto_trading_bot.trading_bot.trade import run_rising_chart, run_wave_rider 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class Trade(threading.Thread): 20 | def run(self): 21 | ct: CronTab = CronTab("30 * * * * * *") 22 | 23 | while True: 24 | # get how long to wait for next cron 25 | now: datetime = datetime.utcnow() 26 | delay: float = ct.next(now, default_utc=True) 27 | 28 | sleep(delay) 29 | 30 | # run trade cron 31 | logger.info("Update all orders.") 32 | update_all_open_orders() 33 | logger.info("Run trading mode rising chart.") 34 | run_rising_chart() 35 | logger.info("Run trading mode wave rider.") 36 | run_wave_rider() 37 | 38 | 39 | class UpdateMarket(threading.Thread): 40 | def run(self): 41 | ct: CronTab = CronTab("@hourly") 42 | 43 | while True: 44 | # get how long to wait for next cron 45 | now: datetime = datetime.utcnow() 46 | delay: float = ct.next(now, default_utc=True) 47 | 48 | sleep(delay) 49 | 50 | # run trade cron 51 | logger.info("update markets") 52 | get_all_markets_from_exchange("binance") 53 | 54 | 55 | class Command(BaseCommand): 56 | help = "Run Cronjobs" 57 | 58 | def handle(self, *args, **options): 59 | 60 | trade_thread: Trade = Trade() 61 | update_market_thread: UpdateMarket = UpdateMarket() 62 | 63 | trade_thread.start() 64 | update_market_thread.start() 65 | 66 | trade_thread.join() 67 | update_market_thread.join() 68 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/management/commands/init_trade.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import List 3 | 4 | from ccxt.base.exchange import Exchange 5 | from django.core.management.base import BaseCommand 6 | 7 | from django_crypto_trading_bot.trading_bot.api.order import create_order 8 | from django_crypto_trading_bot.trading_bot.models import OHLCV, Bot, Order 9 | 10 | from ...exceptions import NoMarket, NoTimeFrame 11 | 12 | 13 | class Command(BaseCommand): 14 | help = "Start the simulation" 15 | 16 | def add_arguments(self, parser): 17 | 18 | parser.add_argument( 19 | "--amount", nargs="?", type=float, help="Init Trade Amount", default=1, 20 | ) 21 | 22 | parser.add_argument( 23 | "--bot_id", nargs="?", type=int, help="bot_id", 24 | ) 25 | 26 | parser.add_argument( 27 | "--sell_order", 28 | action="store_true", 29 | help="Create a sell order instead of a buy order.", 30 | ) 31 | 32 | def handle(self, *args, **options): 33 | bot: Bot = Bot.objects.get(pk=options["bot_id"]) 34 | 35 | exchange: Exchange = bot.account.get_account_client() 36 | 37 | if not bot.market: 38 | raise NoMarket("Bot has no market to trade!") 39 | if not bot.timeframe: 40 | raise NoTimeFrame("Bot has no time frame to trade!") 41 | 42 | candles: List[List[float]] = exchange.fetch_ohlcv( 43 | symbol=bot.market.symbol, timeframe=bot.timeframe, limit=1, 44 | ) 45 | 46 | candle: OHLCV = OHLCV.get_OHLCV( 47 | candle=candles[0], timeframe=bot.timeframe, market=bot.market, 48 | ) 49 | 50 | order: Order 51 | if options["sell_order"]: 52 | order = create_order( 53 | amount=Decimal(options["amount"]), 54 | price=candle.highest_price, 55 | side=Order.Side.SIDE_SELL, 56 | market=bot.market, 57 | bot=bot, 58 | ) 59 | else: 60 | order = create_order( 61 | amount=Decimal(options["amount"]), 62 | price=candle.lowest_price, 63 | side=Order.Side.SIDE_BUY, 64 | market=bot.market, 65 | bot=bot, 66 | ) 67 | 68 | order.save() 69 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/management/commands/trade.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.core.management.base import BaseCommand 4 | 5 | from django_crypto_trading_bot.trading_bot.api.order import update_all_open_orders 6 | from django_crypto_trading_bot.trading_bot.trade import run_rising_chart, run_wave_rider 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class Command(BaseCommand): 12 | help = "Update trades & create re orders." 13 | 14 | def handle(self, *args, **options): 15 | logger.info("Update all orders.") 16 | update_all_open_orders() 17 | logger.info("Run trading mode rising chart.") 18 | run_rising_chart() 19 | logger.info("Run trading mode wave rider.") 20 | run_wave_rider() 21 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/management/commands/update_OHLCV.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from django_crypto_trading_bot.trading_bot.models import OHLCV, Timeframes 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Download the chart history of all markets for a timeframe" 8 | 9 | def add_arguments(self, parser): 10 | 11 | parser.add_argument( 12 | "--timeframe", 13 | nargs="?", 14 | type=Timeframes, 15 | help="Timeframe like 1m, 1h, 1d, 1w, 1M, ...", 16 | default=Timeframes.DAY_1, 17 | ) 18 | 19 | def handle(self, *args, **options): 20 | OHLCV.update_new_candles_all_markets(timeframe=options["timeframe"]) 21 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/migrations/0002_auto_20200708_1557.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-07-08 13:57 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("trading_bot", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="bot", 16 | name="quote", 17 | field=models.ForeignKey( 18 | blank=True, 19 | null=True, 20 | on_delete=django.db.models.deletion.PROTECT, 21 | to="trading_bot.Currency", 22 | ), 23 | ), 24 | migrations.AddField( 25 | model_name="bot", 26 | name="trade_mode", 27 | field=models.CharField( 28 | choices=[ 29 | ("wave rider", "Wave Rider"), 30 | ("rising chart", "Rising Chart"), 31 | ], 32 | default="wave rider", 33 | max_length=20, 34 | ), 35 | ), 36 | migrations.AlterField( 37 | model_name="bot", 38 | name="market", 39 | field=models.ForeignKey( 40 | blank=True, 41 | null=True, 42 | on_delete=django.db.models.deletion.PROTECT, 43 | to="trading_bot.Market", 44 | ), 45 | ), 46 | migrations.AlterField( 47 | model_name="bot", 48 | name="timeframe", 49 | field=models.CharField( 50 | blank=True, 51 | choices=[ 52 | ("1m", "Minute 1"), 53 | ("3m", "Minute 3"), 54 | ("5m", "Minute 5"), 55 | ("15m", "Minute 15"), 56 | ("30m", "Minute 30"), 57 | ("1h", "Hour 1"), 58 | ("2h", "Hour 2"), 59 | ("4h", "Hour 4"), 60 | ("6h", "Hour 6"), 61 | ("8h", "Hour 8"), 62 | ("12h", "Hour 12"), 63 | ("1d", "Day 1"), 64 | ("3d", "Day 3"), 65 | ("1w", "Week 1"), 66 | ("1M", "Month 1"), 67 | ], 68 | default="1M", 69 | max_length=10, 70 | null=True, 71 | ), 72 | ), 73 | ] 74 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/migrations/0003_auto_20200708_1941.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-07-08 17:41 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("trading_bot", "0002_auto_20200708_1557"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="bot", 16 | name="max_amount", 17 | field=models.DecimalField( 18 | blank=True, decimal_places=8, max_digits=30, null=True 19 | ), 20 | ), 21 | migrations.AddField( 22 | model_name="bot", 23 | name="min_rise", 24 | field=models.DecimalField( 25 | blank=True, decimal_places=2, max_digits=30, null=True 26 | ), 27 | ), 28 | migrations.AddField( 29 | model_name="bot", 30 | name="stop_loss", 31 | field=models.DecimalField( 32 | blank=True, decimal_places=2, max_digits=30, null=True 33 | ), 34 | ), 35 | migrations.AddField( 36 | model_name="order", 37 | name="last_price_tick", 38 | field=models.DecimalField( 39 | blank=True, decimal_places=8, max_digits=30, null=True 40 | ), 41 | ), 42 | migrations.AddField( 43 | model_name="order", 44 | name="market", 45 | field=models.ForeignKey( 46 | blank=True, 47 | null=True, 48 | on_delete=django.db.models.deletion.PROTECT, 49 | to="trading_bot.Market", 50 | ), 51 | ), 52 | ] 53 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/migrations/0004_auto_20200709_1344.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-07-09 11:44 2 | 3 | from decimal import Decimal 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("trading_bot", "0003_auto_20200708_1941"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="account", 16 | name="default_fee_rate", 17 | field=models.DecimalField( 18 | decimal_places=4, 19 | default=Decimal( 20 | "0.1000000000000000055511151231257827021181583404541015625" 21 | ), 22 | max_digits=30, 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/migrations/0005_bot_lock_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-07-09 18:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("trading_bot", "0004_auto_20200709_1344"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="bot", name="lock_time", field=models.IntegerField(default=12), 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/trading_bot/migrations/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/trading_bot/tests/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/tests/api_client/api_data_example.py: -------------------------------------------------------------------------------- 1 | # example objects from https://github.com/ccxt/ccxt/wiki/Manual 2 | 3 | 4 | def trade_structure() -> dict: 5 | return { 6 | "id": "12345-67890:09876/54321", 7 | "timestamp": 1502962946216, 8 | "datetime": "2017-08-17 12:42:48.000", 9 | "symbol": "ETH/BTC", 10 | "order": "12345-67890:09876/54321", 11 | "type": "limit", 12 | "side": "buy", 13 | "takerOrMaker": "taker", 14 | "price": 0.06917684, 15 | "amount": 1.5, 16 | "cost": 0.10376526, 17 | "fee": {"cost": 0.0015, "currency": "ETH", "rate": 0.002}, 18 | } 19 | 20 | 21 | def order_structure(add_trades: bool) -> dict: 22 | """ 23 | get dummy order 24 | 25 | add_trades -> add a trade to order 26 | """ 27 | order: dict = { 28 | "id": "12345-67890:09876/54321", 29 | "datetime": "2017-08-17 12:42:48.000", 30 | "timestamp": 1502962946216, 31 | "lastTradeTimestamp": 1502962956216, 32 | "status": "open", 33 | "symbol": "ETH/BTC", 34 | "type": "limit", 35 | "side": "buy", 36 | "price": 0.06917684, 37 | "amount": 1.5, 38 | "filled": 1.1, 39 | "remaining": 0.4, 40 | "cost": 0.076094524, 41 | "trades": [], 42 | "fee": {"currency": "BTC", "cost": 0.0008, "rate": 0.002}, 43 | } 44 | 45 | if add_trades: 46 | order["trades"] = [trade_structure()] 47 | 48 | return order 49 | 50 | 51 | def market_structure() -> dict: 52 | return { 53 | "id": " btcusd", 54 | "symbol": "BTC/USDT", 55 | "base": "BTC", 56 | "quote": "USDT", 57 | "baseId": "btc", 58 | "quoteId": "usdt", 59 | "active": True, 60 | "precision": {"price": 8, "amount": 8, "cost": 8}, 61 | "limits": { 62 | "amount": {"min": 0.01, "max": 1000}, 63 | "price": {"min": 0.01, "max": 1000000.0}, 64 | }, 65 | } 66 | 67 | 68 | def market_structure_eth_btc() -> dict: 69 | return { 70 | "id": " ETHBTC", 71 | "symbol": "ETH/BTC", 72 | "base": "ETH", 73 | "quote": "BTC", 74 | "baseId": "ETH", 75 | "quoteId": "BTC", 76 | "active": True, 77 | "precision": {"price": 8, "amount": 3, "cost": 3}, 78 | "limits": { 79 | "amount": {"min": 0.001, "max": 100000}, 80 | "price": {"min": 1e-06, "max": 100000.0}, 81 | }, 82 | } 83 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/tests/api_client/test_api_client.py: -------------------------------------------------------------------------------- 1 | from ccxt.base.exchange import Exchange 2 | from django_crypto_trading_bot.trading_bot.api.client import get_client 3 | 4 | 5 | def test_get_client(): 6 | # check if puplic api works 7 | exchange: Exchange = get_client(exchange_id="binance") 8 | exchange.load_markets() 9 | market_exchange = exchange.market("TRX/BNB") 10 | assert market_exchange["symbol"] == "TRX/BNB" 11 | -------------------------------------------------------------------------------- /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/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/trading_bot/tests/factories.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from decimal import Decimal 3 | 4 | import pytz 5 | from django.utils import timezone 6 | from factory import DjangoModelFactory, SubFactory 7 | 8 | from django_crypto_trading_bot.trading_bot.models import ( 9 | Exchanges, 10 | Order, 11 | Timeframes, 12 | Bot, 13 | ) 14 | from django_crypto_trading_bot.users.tests.factories import UserFactory 15 | 16 | 17 | class AccountFactory(DjangoModelFactory): 18 | class Meta: 19 | model = "trading_bot.Account" 20 | django_get_or_create = ["api_key"] 21 | 22 | exchange = Exchanges.BINANCE 23 | user = SubFactory(UserFactory) 24 | api_key = "*" 25 | secret = "*" 26 | 27 | 28 | class TrxCurrencyFactory(DjangoModelFactory): 29 | class Meta: 30 | model = "trading_bot.Currency" 31 | django_get_or_create = ["short"] 32 | 33 | name = "TRON" 34 | short = "TRX" 35 | 36 | 37 | class BnbCurrencyFactory(TrxCurrencyFactory): 38 | name = "Binance Coin" 39 | short = "BNB" 40 | 41 | 42 | class EurCurrencyFactory(TrxCurrencyFactory): 43 | name = "Euro" 44 | short = "EUR" 45 | 46 | 47 | class BtcCurrencyFactory(TrxCurrencyFactory): 48 | name = "Bitcoin" 49 | short = "BTC" 50 | 51 | 52 | class UsdtCurrencyFactory(TrxCurrencyFactory): 53 | name = "Tether" 54 | short = "USDT" 55 | 56 | 57 | class EthCurrencyFactory(TrxCurrencyFactory): 58 | name = "Ethereum" 59 | short = "ETH" 60 | 61 | 62 | class MarketFactory(DjangoModelFactory): 63 | class Meta: 64 | model = "trading_bot.Market" 65 | django_get_or_create = ["base", "quote"] 66 | 67 | base = SubFactory(TrxCurrencyFactory) 68 | quote = SubFactory(BnbCurrencyFactory) 69 | exchange = Exchanges.BINANCE 70 | active = True 71 | precision_amount = 3 72 | precision_price = 4 73 | limits_amount_min = Decimal(0.1) 74 | limits_amount_max = Decimal(1000) 75 | limits_price_min = Decimal(0.1) 76 | limits_price_max = Decimal(1000) 77 | 78 | 79 | class BnbEurMarketFactory(MarketFactory): 80 | base = SubFactory(BnbCurrencyFactory) 81 | quote = SubFactory(EurCurrencyFactory) 82 | 83 | 84 | class EthBnbMarketFactory(MarketFactory): 85 | base = SubFactory(EthCurrencyFactory) 86 | quote = SubFactory(BnbCurrencyFactory) 87 | 88 | 89 | class BtcBnbMarketFactory(MarketFactory): 90 | base = SubFactory(EthCurrencyFactory) 91 | quote = SubFactory(BnbCurrencyFactory) 92 | 93 | 94 | class OutOfDataMarketFactory(MarketFactory): 95 | base = SubFactory(BtcCurrencyFactory) 96 | quote = SubFactory(UsdtCurrencyFactory) 97 | active = False 98 | precision_amount = 10 99 | precision_price = 10 100 | limits_amount_min = Decimal(0.1) 101 | limits_amount_max = Decimal(1000) 102 | limits_price_min = Decimal(0.1) 103 | limits_price_max = Decimal(1000) 104 | 105 | 106 | class BotFactory(DjangoModelFactory): 107 | class Meta: 108 | model = "trading_bot.Bot" 109 | 110 | account = SubFactory(AccountFactory) 111 | market = SubFactory(MarketFactory) 112 | 113 | 114 | class RisingChartBotFactory(BotFactory): 115 | 116 | trade_mode = Bot.TradeMode.RISING_CHART 117 | quote = SubFactory(BnbCurrencyFactory) 118 | max_amount = Decimal(1) 119 | min_rise = Decimal(5) 120 | stop_loss = Decimal(2) 121 | 122 | 123 | class BuyOrderFactory(DjangoModelFactory): 124 | class Meta: 125 | model = "trading_bot.Order" 126 | django_get_or_create = ["order_id"] 127 | 128 | bot = SubFactory(BotFactory) 129 | order_id = "1" 130 | timestamp = timezone.now() 131 | status = Order.Status.CLOSED 132 | order_type = Order.OrderType.LIMIT 133 | side = Order.Side.SIDE_BUY 134 | price = Decimal(10) 135 | amount = Decimal(100) 136 | filled = Decimal(100) 137 | 138 | 139 | class SellOrderFactory(BuyOrderFactory): 140 | order_id = "2" 141 | side = Order.Side.SIDE_SELL 142 | 143 | 144 | class OpenBuyOrderFactory(BuyOrderFactory): 145 | order_id = "3" 146 | status = Order.Status.OPEN 147 | 148 | 149 | class EndOrderFactory(BuyOrderFactory): 150 | order_id = "4" 151 | status = Order.Status.NOT_MIN_NOTIONAL 152 | 153 | 154 | class BuyFeeOrderFactory(BuyOrderFactory): 155 | order_id = "5" 156 | fee_currency = SubFactory(BnbCurrencyFactory) 157 | fee_cost = Decimal(0.1) 158 | fee_rate = Decimal(0.01) 159 | 160 | 161 | class RisingChartOrderFactory(BuyOrderFactory): 162 | order_id = "6" 163 | side = Order.Side.SIDE_BUY 164 | order_type = Order.OrderType.MARKET 165 | bot = SubFactory(RisingChartBotFactory) 166 | price = Decimal(1) 167 | last_price_tick = Decimal(1) 168 | market = SubFactory(MarketFactory) 169 | 170 | 171 | class TradeFactory(DjangoModelFactory): 172 | class Meta: 173 | model = "trading_bot.Trade" 174 | django_get_or_create = ["trade_id"] 175 | 176 | order = SubFactory(BuyOrderFactory) 177 | trade_id = "123" 178 | timestamp = timezone.now() 179 | taker_or_maker = Order.OrderType.MARKET 180 | amount = Decimal(10) 181 | fee_currency = SubFactory(EurCurrencyFactory) 182 | fee_cost = Decimal(000.1) 183 | fee_rate = Decimal(00.1) 184 | 185 | 186 | class OHLCVBnbEurFactory(DjangoModelFactory): 187 | class Meta: 188 | model = "trading_bot.OHLCV" 189 | django_get_or_create = ["market", "timestamp", "timeframe"] 190 | 191 | market = SubFactory(BnbEurMarketFactory) 192 | timeframe = Timeframes.MINUTE_1 193 | timestamp = datetime( 194 | year=2020, month=4, day=30, hour=23, tzinfo=pytz.timezone("UTC") 195 | ) 196 | open_price = Decimal(0) 197 | highest_price = Decimal(0) 198 | lowest_price = Decimal(0) 199 | closing_price = Decimal(15.7987) 200 | volume_price = Decimal(0) 201 | 202 | 203 | class OHLCVTrxBnbFactory(OHLCVBnbEurFactory): 204 | market = SubFactory(MarketFactory) 205 | closing_price = Decimal(15.7987) 206 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/trading_bot/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/users/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/adapters.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from allauth.account.adapter import DefaultAccountAdapter 4 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter 5 | from django.conf import settings 6 | from django.http import HttpRequest 7 | 8 | 9 | class AccountAdapter(DefaultAccountAdapter): 10 | def is_open_for_signup(self, request: HttpRequest): 11 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) 12 | 13 | 14 | class SocialAccountAdapter(DefaultSocialAccountAdapter): 15 | def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): 16 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) 17 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth import admin as auth_admin 3 | from django.contrib.auth import get_user_model 4 | 5 | from django_crypto_trading_bot.users.forms import UserChangeForm, UserCreationForm 6 | 7 | User = get_user_model() 8 | 9 | 10 | @admin.register(User) 11 | class UserAdmin(auth_admin.UserAdmin): 12 | 13 | form = UserChangeForm 14 | add_form = UserCreationForm 15 | fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets 16 | list_display = ["username", "name", "is_superuser"] 17 | search_fields = ["name"] 18 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from django_crypto_trading_bot.users.models import User 4 | 5 | 6 | class UserSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = User 9 | fields = ["username", "email", "name", "url"] 10 | 11 | extra_kwargs = { 12 | "url": {"view_name": "api:user-detail", "lookup_field": "username"} 13 | } 14 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/api/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from rest_framework import status 3 | from rest_framework.decorators import action 4 | from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin 5 | from rest_framework.response import Response 6 | from rest_framework.viewsets import GenericViewSet 7 | 8 | from .serializers import UserSerializer 9 | 10 | User = get_user_model() 11 | 12 | 13 | class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet): 14 | serializer_class = UserSerializer 15 | queryset = User.objects.all() 16 | lookup_field = "username" 17 | 18 | def get_queryset(self, *args, **kwargs): 19 | return self.queryset.filter(id=self.request.user.id) 20 | 21 | @action(detail=False, methods=["GET"]) 22 | def me(self, request): 23 | serializer = UserSerializer(request.user, context={"request": request}) 24 | return Response(status=status.HTTP_200_OK, data=serializer.data) 25 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class UsersConfig(AppConfig): 6 | name = "django_crypto_trading_bot.users" 7 | verbose_name = _("Users") 8 | 9 | def ready(self): 10 | try: 11 | import django_crypto_trading_bot.users.signals # noqa F401 12 | except ImportError: 13 | pass 14 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import forms, get_user_model 2 | from django.core.exceptions import ValidationError 3 | from django.utils.translation import ugettext_lazy as _ 4 | 5 | User = get_user_model() 6 | 7 | 8 | class UserChangeForm(forms.UserChangeForm): 9 | class Meta(forms.UserChangeForm.Meta): 10 | model = User 11 | 12 | 13 | class UserCreationForm(forms.UserCreationForm): 14 | 15 | error_message = forms.UserCreationForm.error_messages.update( 16 | {"duplicate_username": _("This username has already been taken.")} 17 | ) 18 | 19 | class Meta(forms.UserCreationForm.Meta): 20 | model = User 21 | 22 | def clean_username(self): 23 | username = self.cleaned_data["username"] 24 | 25 | try: 26 | User.objects.get(username=username) 27 | except User.DoesNotExist: 28 | return username 29 | 30 | raise ValidationError(self.error_messages["duplicate_username"]) 31 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | import django.contrib.auth.models 2 | import django.contrib.auth.validators 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [("auth", "0008_alter_user_username_max_length")] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="User", 16 | fields=[ 17 | ( 18 | "id", 19 | models.AutoField( 20 | auto_created=True, 21 | primary_key=True, 22 | serialize=False, 23 | verbose_name="ID", 24 | ), 25 | ), 26 | ("password", models.CharField(max_length=128, verbose_name="password")), 27 | ( 28 | "last_login", 29 | models.DateTimeField( 30 | blank=True, null=True, verbose_name="last login" 31 | ), 32 | ), 33 | ( 34 | "is_superuser", 35 | models.BooleanField( 36 | default=False, 37 | help_text="Designates that this user has all permissions without explicitly assigning them.", 38 | verbose_name="superuser status", 39 | ), 40 | ), 41 | ( 42 | "username", 43 | models.CharField( 44 | error_messages={ 45 | "unique": "A user with that username already exists." 46 | }, 47 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", 48 | max_length=150, 49 | unique=True, 50 | validators=[ 51 | django.contrib.auth.validators.UnicodeUsernameValidator() 52 | ], 53 | verbose_name="username", 54 | ), 55 | ), 56 | ( 57 | "first_name", 58 | models.CharField( 59 | blank=True, max_length=30, verbose_name="first name" 60 | ), 61 | ), 62 | ( 63 | "last_name", 64 | models.CharField( 65 | blank=True, max_length=150, verbose_name="last name" 66 | ), 67 | ), 68 | ( 69 | "email", 70 | models.EmailField( 71 | blank=True, max_length=254, verbose_name="email address" 72 | ), 73 | ), 74 | ( 75 | "is_staff", 76 | models.BooleanField( 77 | default=False, 78 | help_text="Designates whether the user can log into this admin site.", 79 | verbose_name="staff status", 80 | ), 81 | ), 82 | ( 83 | "is_active", 84 | models.BooleanField( 85 | default=True, 86 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", 87 | verbose_name="active", 88 | ), 89 | ), 90 | ( 91 | "date_joined", 92 | models.DateTimeField( 93 | default=django.utils.timezone.now, verbose_name="date joined" 94 | ), 95 | ), 96 | ( 97 | "name", 98 | models.CharField( 99 | blank=True, max_length=255, verbose_name="Name of User" 100 | ), 101 | ), 102 | ( 103 | "groups", 104 | models.ManyToManyField( 105 | blank=True, 106 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 107 | related_name="user_set", 108 | related_query_name="user", 109 | to="auth.Group", 110 | verbose_name="groups", 111 | ), 112 | ), 113 | ( 114 | "user_permissions", 115 | models.ManyToManyField( 116 | blank=True, 117 | help_text="Specific permissions for this user.", 118 | related_name="user_set", 119 | related_query_name="user", 120 | to="auth.Permission", 121 | verbose_name="user permissions", 122 | ), 123 | ), 124 | ], 125 | options={ 126 | "verbose_name_plural": "users", 127 | "verbose_name": "user", 128 | "abstract": False, 129 | }, 130 | managers=[("objects", django.contrib.auth.models.UserManager())], 131 | ) 132 | ] 133 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/users/migrations/__init__.py -------------------------------------------------------------------------------- /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/users/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/users/tests/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/tests/factories.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Sequence 2 | 3 | from django.contrib.auth import get_user_model 4 | from factory import DjangoModelFactory, Faker, post_generation 5 | 6 | 7 | class UserFactory(DjangoModelFactory): 8 | 9 | username = Faker("user_name") 10 | email = Faker("email") 11 | name = Faker("name") 12 | 13 | @post_generation 14 | def password(self, create: bool, extracted: Sequence[Any], **kwargs): 15 | password = ( 16 | extracted 17 | if extracted 18 | else Faker( 19 | "password", 20 | length=42, 21 | special_chars=True, 22 | digits=True, 23 | upper_case=True, 24 | lower_case=True, 25 | ).generate(extra_kwargs={}) 26 | ) 27 | self.set_password(password) 28 | 29 | class Meta: 30 | model = get_user_model() 31 | django_get_or_create = ["username"] 32 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/tests/test_drf_urls.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.urls import resolve, reverse 3 | 4 | from django_crypto_trading_bot.users.models import User 5 | 6 | pytestmark = pytest.mark.django_db 7 | 8 | 9 | def test_user_detail(user: User): 10 | assert ( 11 | reverse("api:user-detail", kwargs={"username": user.username}) 12 | == f"/api/users/{user.username}/" 13 | ) 14 | assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail" 15 | 16 | 17 | def test_user_list(): 18 | assert reverse("api:user-list") == "/api/users/" 19 | assert resolve("/api/users/").view_name == "api:user-list" 20 | 21 | 22 | def test_user_me(): 23 | assert reverse("api:user-me") == "/api/users/me/" 24 | assert resolve("/api/users/me/").view_name == "api:user-me" 25 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/tests/test_drf_views.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.test import RequestFactory 3 | 4 | from django_crypto_trading_bot.users.api.views import UserViewSet 5 | from django_crypto_trading_bot.users.models import User 6 | 7 | pytestmark = pytest.mark.django_db 8 | 9 | 10 | class TestUserViewSet: 11 | def test_get_queryset(self, user: User, rf: RequestFactory): 12 | view = UserViewSet() 13 | request = rf.get("/fake-url/") 14 | request.user = user 15 | 16 | view.request = request 17 | 18 | assert user in view.get_queryset() 19 | 20 | def test_me(self, user: User, rf: RequestFactory): 21 | view = UserViewSet() 22 | request = rf.get("/fake-url/") 23 | request.user = user 24 | 25 | view.request = request 26 | 27 | response = view.me(request) 28 | 29 | assert response.data == { 30 | "username": user.username, 31 | "email": user.email, 32 | "name": user.name, 33 | "url": f"http://testserver/api/users/{user.username}/", 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/tests/test_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from django_crypto_trading_bot.users.models import User 4 | 5 | pytestmark = pytest.mark.django_db 6 | 7 | 8 | def test_user_get_absolute_url(user: User): 9 | assert user.get_absolute_url() == f"/users/{user.username}/" 10 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.urls import resolve, reverse 3 | 4 | from django_crypto_trading_bot.users.models import User 5 | 6 | pytestmark = pytest.mark.django_db 7 | 8 | 9 | def test_detail(user: User): 10 | assert ( 11 | reverse("users:detail", kwargs={"username": user.username}) 12 | == f"/users/{user.username}/" 13 | ) 14 | assert resolve(f"/users/{user.username}/").view_name == "users:detail" 15 | 16 | 17 | def test_update(): 18 | assert reverse("users:update") == "/users/~update/" 19 | assert resolve("/users/~update/").view_name == "users:update" 20 | 21 | 22 | def test_redirect(): 23 | assert reverse("users:redirect") == "/users/~redirect/" 24 | assert resolve("/users/~redirect/").view_name == "users:redirect" 25 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/tests/test_views.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.contrib.auth.models import AnonymousUser 3 | from django.http.response import Http404 4 | from django.test import RequestFactory 5 | 6 | from django_crypto_trading_bot.users.models import User 7 | from django_crypto_trading_bot.users.tests.factories import UserFactory 8 | from django_crypto_trading_bot.users.views import ( 9 | UserRedirectView, 10 | UserUpdateView, 11 | user_detail_view, 12 | ) 13 | 14 | pytestmark = pytest.mark.django_db 15 | 16 | 17 | class TestUserUpdateView: 18 | """ 19 | TODO: 20 | extracting view initialization code as class-scoped fixture 21 | would be great if only pytest-django supported non-function-scoped 22 | fixture db access -- this is a work-in-progress for now: 23 | https://github.com/pytest-dev/pytest-django/pull/258 24 | """ 25 | 26 | def test_get_success_url(self, user: User, rf: RequestFactory): 27 | view = UserUpdateView() 28 | request = rf.get("/fake-url/") 29 | request.user = user 30 | 31 | view.request = request 32 | 33 | assert view.get_success_url() == f"/users/{user.username}/" 34 | 35 | def test_get_object(self, user: User, rf: RequestFactory): 36 | view = UserUpdateView() 37 | request = rf.get("/fake-url/") 38 | request.user = user 39 | 40 | view.request = request 41 | 42 | assert view.get_object() == user 43 | 44 | 45 | class TestUserRedirectView: 46 | def test_get_redirect_url(self, user: User, rf: RequestFactory): 47 | view = UserRedirectView() 48 | request = rf.get("/fake-url") 49 | request.user = user 50 | 51 | view.request = request 52 | 53 | assert view.get_redirect_url() == f"/users/{user.username}/" 54 | 55 | 56 | class TestUserDetailView: 57 | def test_authenticated(self, user: User, rf: RequestFactory): 58 | request = rf.get("/fake-url/") 59 | request.user = UserFactory() 60 | 61 | response = user_detail_view(request, username=user.username) 62 | 63 | assert response.status_code == 200 64 | 65 | def test_not_authenticated(self, user: User, rf: RequestFactory): 66 | request = rf.get("/fake-url/") 67 | request.user = AnonymousUser() # type: ignore 68 | 69 | response = user_detail_view(request, username=user.username) 70 | 71 | assert response.status_code == 302 72 | # assert response.url == f"/accounts/login/?next=/fake-url/" 73 | 74 | def test_case_sensitivity(self, rf: RequestFactory): 75 | request = rf.get("/fake-url/") 76 | request.user = UserFactory(username="UserName") 77 | 78 | with pytest.raises(Http404): 79 | user_detail_view(request, username="username") 80 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from django_crypto_trading_bot.users.views import ( 4 | user_detail_view, 5 | user_redirect_view, 6 | user_update_view, 7 | ) 8 | 9 | app_name = "users" 10 | urlpatterns = [ 11 | path("~redirect/", view=user_redirect_view, name="redirect"), 12 | path("~update/", view=user_update_view, name="update"), 13 | path("/", view=user_detail_view, name="detail"), 14 | ] 15 | -------------------------------------------------------------------------------- /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/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxluigi/django-crypto-trading-bot/8ee78097bde6c934de4a9cc224032909ad6a88b6/django_crypto_trading_bot/utils/__init__.py -------------------------------------------------------------------------------- /django_crypto_trading_bot/utils/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def settings_context(_request): 5 | return {"settings": settings} 6 | -------------------------------------------------------------------------------- /django_crypto_trading_bot/utils/storages.py: -------------------------------------------------------------------------------- 1 | from storages.backends.s3boto3 import S3Boto3Storage 2 | 3 | 4 | class StaticRootS3Boto3Storage(S3Boto3Storage): 5 | location = "static" 6 | default_acl = "public-read" 7 | 8 | 9 | class MediaRootS3Boto3Storage(S3Boto3Storage): 10 | location = "media" 11 | file_overwrite = False 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build -c . 8 | SOURCEDIR = ./_source 9 | BUILDDIR = ./_build 10 | APP = /app 11 | 12 | .PHONY: help livehtml apidocs Makefile 13 | 14 | # Put it first so that "make" without argument is like "make help". 15 | help: 16 | @$(SPHINXBUILD) help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 17 | 18 | # Build, watch and serve docs with live reload 19 | livehtml: 20 | sphinx-autobuild -b html --host 0.0.0.0 --port 7000 --watch $(APP) -c . $(SOURCEDIR) $(BUILDDIR)/html 21 | 22 | # Outputs rst files from django application code 23 | apidocs: 24 | sphinx-apidoc -o $(SOURCEDIR)/api /app 25 | 26 | # Catch-all target: route all unknown targets to Sphinx using the new 27 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 28 | %: Makefile 29 | @$(SPHINXBUILD) -b $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 30 | -------------------------------------------------------------------------------- /docs/__init__.py: -------------------------------------------------------------------------------- 1 | # Included so that Django's startproject comment runs against the docs directory 2 | -------------------------------------------------------------------------------- /docs/_source/api/config.rst: -------------------------------------------------------------------------------- 1 | config package 2 | ============== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | config.settings 11 | 12 | Submodules 13 | ---------- 14 | 15 | config.api\_router module 16 | ------------------------- 17 | 18 | .. automodule:: config.api_router 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | config.urls module 24 | ------------------ 25 | 26 | .. automodule:: config.urls 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | config.wsgi module 32 | ------------------ 33 | 34 | .. automodule:: config.wsgi 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: config 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/_source/api/config.settings.rst: -------------------------------------------------------------------------------- 1 | config.settings package 2 | ======================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | config.settings.base module 8 | --------------------------- 9 | 10 | .. automodule:: config.settings.base 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | config.settings.local module 16 | ---------------------------- 17 | 18 | .. automodule:: config.settings.local 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | config.settings.production module 24 | --------------------------------- 25 | 26 | .. automodule:: config.settings.production 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | config.settings.test module 32 | --------------------------- 33 | 34 | .. automodule:: config.settings.test 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | Module contents 40 | --------------- 41 | 42 | .. automodule:: config.settings 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.contrib.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.contrib package 2 | ============================================ 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | django_crypto_trading_bot.contrib.sites 11 | 12 | Module contents 13 | --------------- 14 | 15 | .. automodule:: django_crypto_trading_bot.contrib 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.contrib.sites.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.contrib.sites package 2 | ================================================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | django_crypto_trading_bot.contrib.sites.migrations 11 | 12 | Module contents 13 | --------------- 14 | 15 | .. automodule:: django_crypto_trading_bot.contrib.sites 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot package 2 | ==================================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | django_crypto_trading_bot.contrib 11 | django_crypto_trading_bot.trading_bot 12 | django_crypto_trading_bot.users 13 | django_crypto_trading_bot.utils 14 | 15 | Submodules 16 | ---------- 17 | 18 | django\_crypto\_trading\_bot.conftest module 19 | -------------------------------------------- 20 | 21 | .. automodule:: django_crypto_trading_bot.conftest 22 | :members: 23 | :undoc-members: 24 | :show-inheritance: 25 | 26 | Module contents 27 | --------------- 28 | 29 | .. automodule:: django_crypto_trading_bot 30 | :members: 31 | :undoc-members: 32 | :show-inheritance: 33 | -------------------------------------------------------------------------------- /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/_source/api/django_crypto_trading_bot.trading_bot.management.commands.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.trading\_bot.management.commands package 2 | ===================================================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | django\_crypto\_trading\_bot.trading\_bot.management.commands.add\_markets module 8 | --------------------------------------------------------------------------------- 9 | 10 | .. automodule:: django_crypto_trading_bot.trading_bot.management.commands.add_markets 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | django\_crypto\_trading\_bot.trading\_bot.management.commands.cron module 16 | ------------------------------------------------------------------------- 17 | 18 | .. automodule:: django_crypto_trading_bot.trading_bot.management.commands.cron 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | django\_crypto\_trading\_bot.trading\_bot.management.commands.init\_trade module 24 | -------------------------------------------------------------------------------- 25 | 26 | .. automodule:: django_crypto_trading_bot.trading_bot.management.commands.init_trade 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | django\_crypto\_trading\_bot.trading\_bot.management.commands.trade module 32 | -------------------------------------------------------------------------- 33 | 34 | .. automodule:: django_crypto_trading_bot.trading_bot.management.commands.trade 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | django\_crypto\_trading\_bot.trading\_bot.management.commands.update\_OHLCV module 40 | ---------------------------------------------------------------------------------- 41 | 42 | .. automodule:: django_crypto_trading_bot.trading_bot.management.commands.update_OHLCV 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: django_crypto_trading_bot.trading_bot.management.commands 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.trading_bot.management.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.trading\_bot.management package 2 | ============================================================ 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | django_crypto_trading_bot.trading_bot.management.commands 11 | 12 | Module contents 13 | --------------- 14 | 15 | .. automodule:: django_crypto_trading_bot.trading_bot.management 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.trading_bot.migrations.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.trading\_bot.migrations package 2 | ============================================================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | django\_crypto\_trading\_bot.trading\_bot.migrations.0001\_initial module 8 | ------------------------------------------------------------------------- 9 | 10 | .. automodule:: django_crypto_trading_bot.trading_bot.migrations.0001_initial 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | django\_crypto\_trading\_bot.trading\_bot.migrations.0002\_auto\_20200708\_1557 module 16 | -------------------------------------------------------------------------------------- 17 | 18 | .. automodule:: django_crypto_trading_bot.trading_bot.migrations.0002_auto_20200708_1557 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | django\_crypto\_trading\_bot.trading\_bot.migrations.0003\_auto\_20200708\_1941 module 24 | -------------------------------------------------------------------------------------- 25 | 26 | .. automodule:: django_crypto_trading_bot.trading_bot.migrations.0003_auto_20200708_1941 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | django\_crypto\_trading\_bot.trading\_bot.migrations.0004\_auto\_20200709\_1344 module 32 | -------------------------------------------------------------------------------------- 33 | 34 | .. automodule:: django_crypto_trading_bot.trading_bot.migrations.0004_auto_20200709_1344 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | django\_crypto\_trading\_bot.trading\_bot.migrations.0005\_bot\_lock\_time module 40 | --------------------------------------------------------------------------------- 41 | 42 | .. automodule:: django_crypto_trading_bot.trading_bot.migrations.0005_bot_lock_time 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: django_crypto_trading_bot.trading_bot.migrations 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.trading_bot.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.trading\_bot package 2 | ================================================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | django_crypto_trading_bot.trading_bot.api 11 | django_crypto_trading_bot.trading_bot.management 12 | django_crypto_trading_bot.trading_bot.migrations 13 | django_crypto_trading_bot.trading_bot.tests 14 | 15 | Submodules 16 | ---------- 17 | 18 | django\_crypto\_trading\_bot.trading\_bot.admin module 19 | ------------------------------------------------------ 20 | 21 | .. automodule:: django_crypto_trading_bot.trading_bot.admin 22 | :members: 23 | :undoc-members: 24 | :show-inheritance: 25 | 26 | django\_crypto\_trading\_bot.trading\_bot.apps module 27 | ----------------------------------------------------- 28 | 29 | .. automodule:: django_crypto_trading_bot.trading_bot.apps 30 | :members: 31 | :undoc-members: 32 | :show-inheritance: 33 | 34 | django\_crypto\_trading\_bot.trading\_bot.exceptions module 35 | ----------------------------------------------------------- 36 | 37 | .. automodule:: django_crypto_trading_bot.trading_bot.exceptions 38 | :members: 39 | :undoc-members: 40 | :show-inheritance: 41 | 42 | django\_crypto\_trading\_bot.trading\_bot.models module 43 | ------------------------------------------------------- 44 | 45 | .. automodule:: django_crypto_trading_bot.trading_bot.models 46 | :members: 47 | :undoc-members: 48 | :show-inheritance: 49 | 50 | django\_crypto\_trading\_bot.trading\_bot.trade module 51 | ------------------------------------------------------ 52 | 53 | .. automodule:: django_crypto_trading_bot.trading_bot.trade 54 | :members: 55 | :undoc-members: 56 | :show-inheritance: 57 | 58 | django\_crypto\_trading\_bot.trading\_bot.views module 59 | ------------------------------------------------------ 60 | 61 | .. automodule:: django_crypto_trading_bot.trading_bot.views 62 | :members: 63 | :undoc-members: 64 | :show-inheritance: 65 | 66 | Module contents 67 | --------------- 68 | 69 | .. automodule:: django_crypto_trading_bot.trading_bot 70 | :members: 71 | :undoc-members: 72 | :show-inheritance: 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.users.migrations.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.users.migrations package 2 | ===================================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | django\_crypto\_trading\_bot.users.migrations.0001\_initial module 8 | ------------------------------------------------------------------ 9 | 10 | .. automodule:: django_crypto_trading_bot.users.migrations.0001_initial 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | Module contents 16 | --------------- 17 | 18 | .. automodule:: django_crypto_trading_bot.users.migrations 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.users.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.users package 2 | ========================================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | 10 | django_crypto_trading_bot.users.migrations 11 | django_crypto_trading_bot.users.tests 12 | 13 | Submodules 14 | ---------- 15 | 16 | django\_crypto\_trading\_bot.users.adapters module 17 | -------------------------------------------------- 18 | 19 | .. automodule:: django_crypto_trading_bot.users.adapters 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | django\_crypto\_trading\_bot.users.admin module 25 | ----------------------------------------------- 26 | 27 | .. automodule:: django_crypto_trading_bot.users.admin 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | django\_crypto\_trading\_bot.users.apps module 33 | ---------------------------------------------- 34 | 35 | .. automodule:: django_crypto_trading_bot.users.apps 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | 40 | django\_crypto\_trading\_bot.users.forms module 41 | ----------------------------------------------- 42 | 43 | .. automodule:: django_crypto_trading_bot.users.forms 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | 48 | django\_crypto\_trading\_bot.users.models module 49 | ------------------------------------------------ 50 | 51 | .. automodule:: django_crypto_trading_bot.users.models 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | 56 | django\_crypto\_trading\_bot.users.urls module 57 | ---------------------------------------------- 58 | 59 | .. automodule:: django_crypto_trading_bot.users.urls 60 | :members: 61 | :undoc-members: 62 | :show-inheritance: 63 | 64 | django\_crypto\_trading\_bot.users.views module 65 | ----------------------------------------------- 66 | 67 | .. automodule:: django_crypto_trading_bot.users.views 68 | :members: 69 | :undoc-members: 70 | :show-inheritance: 71 | 72 | Module contents 73 | --------------- 74 | 75 | .. automodule:: django_crypto_trading_bot.users 76 | :members: 77 | :undoc-members: 78 | :show-inheritance: 79 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.users.tests.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.users.tests package 2 | ================================================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | django\_crypto\_trading\_bot.users.tests.factories module 8 | --------------------------------------------------------- 9 | 10 | .. automodule:: django_crypto_trading_bot.users.tests.factories 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | django\_crypto\_trading\_bot.users.tests.test\_drf\_urls module 16 | --------------------------------------------------------------- 17 | 18 | .. automodule:: django_crypto_trading_bot.users.tests.test_drf_urls 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | django\_crypto\_trading\_bot.users.tests.test\_drf\_views module 24 | ---------------------------------------------------------------- 25 | 26 | .. automodule:: django_crypto_trading_bot.users.tests.test_drf_views 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | django\_crypto\_trading\_bot.users.tests.test\_forms module 32 | ----------------------------------------------------------- 33 | 34 | .. automodule:: django_crypto_trading_bot.users.tests.test_forms 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | django\_crypto\_trading\_bot.users.tests.test\_models module 40 | ------------------------------------------------------------ 41 | 42 | .. automodule:: django_crypto_trading_bot.users.tests.test_models 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | django\_crypto\_trading\_bot.users.tests.test\_urls module 48 | ---------------------------------------------------------- 49 | 50 | .. automodule:: django_crypto_trading_bot.users.tests.test_urls 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | django\_crypto\_trading\_bot.users.tests.test\_views module 56 | ----------------------------------------------------------- 57 | 58 | .. automodule:: django_crypto_trading_bot.users.tests.test_views 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | Module contents 64 | --------------- 65 | 66 | .. automodule:: django_crypto_trading_bot.users.tests 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | -------------------------------------------------------------------------------- /docs/_source/api/django_crypto_trading_bot.utils.rst: -------------------------------------------------------------------------------- 1 | django\_crypto\_trading\_bot.utils package 2 | ========================================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | django\_crypto\_trading\_bot.utils.context\_processors module 8 | ------------------------------------------------------------- 9 | 10 | .. automodule:: django_crypto_trading_bot.utils.context_processors 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | django\_crypto\_trading\_bot.utils.storages module 16 | -------------------------------------------------- 17 | 18 | .. automodule:: django_crypto_trading_bot.utils.storages 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | Module contents 24 | --------------- 25 | 26 | .. automodule:: django_crypto_trading_bot.utils 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | -------------------------------------------------------------------------------- /docs/_source/api/modules.rst: -------------------------------------------------------------------------------- 1 | app 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | config 8 | django_crypto_trading_bot 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/_source/index.rst: -------------------------------------------------------------------------------- 1 | .. Django Crypto Trading Bot documentation master file, created by 2 | sphinx-quickstart. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Django Crypto Trading Bot's documentation! 7 | ====================================================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | howto 14 | pycharm/configuration 15 | trading_bot 16 | users 17 | 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/_source/trading_bot.rst: -------------------------------------------------------------------------------- 1 | .. _trading_bot: 2 | 3 | Trading Bot 4 | ====================================================================== 5 | 6 | .. automodule:: django_crypto_trading_bot.trading_bot.models 7 | :members: 8 | :noindex: 9 | 10 | -------------------------------------------------------------------------------- /docs/_source/users.rst: -------------------------------------------------------------------------------- 1 | .. _users: 2 | 3 | Users 4 | ====================================================================== 5 | 6 | Starting a new project, it’s highly recommended to set up a custom user model, 7 | even if the default User model is sufficient for you. 8 | 9 | This model behaves identically to the default user model, 10 | but you’ll be able to customize it in the future if the need arises. 11 | 12 | .. automodule:: django_crypto_trading_bot.users.models 13 | :members: 14 | :noindex: 15 | 16 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import django 5 | 6 | # Configuration file for the Sphinx documentation builder. 7 | # 8 | # This file only contains a selection of the most common options. For a full 9 | # list see the documentation: 10 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 11 | 12 | # -- Path setup -------------------------------------------------------------- 13 | 14 | # If extensions (or modules to document with autodoc) are in another directory, 15 | # add these directories to sys.path here. If the directory is relative to the 16 | # documentation root, use os.path.abspath to make it absolute, like shown here. 17 | 18 | 19 | sys.path.insert(0, os.path.abspath("/app")) 20 | os.environ.setdefault("DATABASE_URL", "") 21 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") 22 | django.setup() 23 | 24 | # -- Project information ----------------------------------------------------- 25 | 26 | project = "Django Crypto Trading Bot" 27 | copyright = """2020, Steffen Exler""" 28 | author = "Steffen Exler" 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [ 36 | "sphinx.ext.autodoc", 37 | "sphinx.ext.napoleon", 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | # templates_path = ["_templates"] 42 | 43 | # List of patterns, relative to source directory, that match files and 44 | # directories to ignore when looking for source files. 45 | # This pattern also affects html_static_path and html_extra_path. 46 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 47 | 48 | # -- Options for HTML output ------------------------------------------------- 49 | 50 | # The theme to use for HTML and HTML Help pages. See the documentation for 51 | # a list of builtin themes. 52 | # 53 | html_theme = "sphinx_rtd_theme" 54 | 55 | # Add any paths that contain custom static files (such as style sheets) here, 56 | # relative to this directory. They are copied after the builtin static files, 57 | # so a file named "default.css" will overwrite the builtin "default.css". 58 | # html_static_path = ["_static"] 59 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /locale/README.rst: -------------------------------------------------------------------------------- 1 | Translations 2 | ============ 3 | 4 | Translations will be placed in this folder when running:: 5 | 6 | python manage.py makemessages 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /merge_production_dotenvs_in_dotenv.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | from typing import Sequence 4 | 5 | import pytest 6 | 7 | ROOT_DIR_PATH = Path(__file__).parent.resolve() 8 | PRODUCTION_DOTENVS_DIR_PATH = ROOT_DIR_PATH / ".envs" / ".production" 9 | PRODUCTION_DOTENV_FILE_PATHS = [ 10 | PRODUCTION_DOTENVS_DIR_PATH / ".django", 11 | PRODUCTION_DOTENVS_DIR_PATH / ".postgres", 12 | ] 13 | DOTENV_FILE_PATH = ROOT_DIR_PATH / ".env" 14 | 15 | 16 | def merge( 17 | output_file_path: str, merged_file_paths: Sequence[str], append_linesep: bool = True 18 | ) -> None: 19 | with open(output_file_path, "w") as output_file: 20 | for merged_file_path in merged_file_paths: 21 | with open(merged_file_path, "r") as merged_file: 22 | merged_file_content = merged_file.read() 23 | output_file.write(merged_file_content) 24 | if append_linesep: 25 | output_file.write(os.linesep) 26 | 27 | 28 | def main(): 29 | merge(str(DOTENV_FILE_PATH), str(PRODUCTION_DOTENV_FILE_PATHS)) 30 | 31 | 32 | @pytest.mark.parametrize("merged_file_count", range(3)) 33 | @pytest.mark.parametrize("append_linesep", [True, False]) 34 | def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool): 35 | tmp_dir_path = Path(str(tmpdir_factory.getbasetemp())) 36 | 37 | output_file_path = tmp_dir_path / ".env" 38 | 39 | expected_output_file_content = "" 40 | merged_file_paths = [] 41 | for i in range(merged_file_count): 42 | merged_file_ord = i + 1 43 | 44 | merged_filename = ".service{}".format(merged_file_ord) 45 | merged_file_path = tmp_dir_path / merged_filename 46 | 47 | merged_file_content = merged_filename * merged_file_ord 48 | 49 | with open(merged_file_path, "w+") as file: 50 | file.write(merged_file_content) 51 | 52 | expected_output_file_content += merged_file_content 53 | if append_linesep: 54 | expected_output_file_content += os.linesep 55 | 56 | merged_file_paths.append(merged_file_path) 57 | 58 | merge(str(output_file_path), str(merged_file_paths), append_linesep) 59 | 60 | with open(output_file_path, "r") as output_file: 61 | actual_output_file_content = output_file.read() 62 | 63 | assert actual_output_file_content == expected_output_file_content 64 | 65 | 66 | if __name__ == "__main__": 67 | main() 68 | -------------------------------------------------------------------------------- /production.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | volumes: 4 | production_postgres_data: {} 5 | production_postgres_data_backups: {} 6 | production_traefik: {} 7 | 8 | services: 9 | django: 10 | build: 11 | context: . 12 | dockerfile: ./compose/production/django/Dockerfile 13 | image: django_crypto_trading_bot_production_django 14 | depends_on: 15 | - postgres 16 | - redis 17 | env_file: 18 | - ./.envs/.production/.django 19 | - ./.envs/.production/.postgres 20 | command: /start 21 | 22 | cron: 23 | build: 24 | context: . 25 | dockerfile: ./compose/production/django/Dockerfile 26 | image: django_crypto_trading_bot_production_cron 27 | depends_on: 28 | - postgres 29 | - redis 30 | env_file: 31 | - ./.envs/.production/.django 32 | - ./.envs/.production/.postgres 33 | command: /cron 34 | 35 | postgres: 36 | build: 37 | context: . 38 | dockerfile: ./compose/production/postgres/Dockerfile 39 | image: django_crypto_trading_bot_production_postgres 40 | volumes: 41 | - production_postgres_data:/var/lib/postgresql/data 42 | - production_postgres_data_backups:/backups 43 | env_file: 44 | - ./.envs/.production/.postgres 45 | 46 | traefik: 47 | build: 48 | context: . 49 | dockerfile: ./compose/production/traefik/Dockerfile 50 | image: django_crypto_trading_bot_production_traefik 51 | depends_on: 52 | - django 53 | volumes: 54 | - production_traefik:/etc/traefik/acme 55 | ports: 56 | - "0.0.0.0:80:80" 57 | - "0.0.0.0:443:443" 58 | 59 | redis: 60 | image: redis:5.0 61 | 62 | awscli: 63 | build: 64 | context: . 65 | dockerfile: ./compose/production/aws/Dockerfile 66 | env_file: 67 | - ./.envs/.production/.django 68 | volumes: 69 | - production_postgres_data_backups:/backups 70 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --ds=config.settings.test --reuse-db 3 | python_files = tests.py test_*.py 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "packageRules": [ 6 | { 7 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 8 | "automerge": true 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is expected by Heroku. 2 | 3 | -r requirements/production.txt 4 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /requirements/local.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | 3 | Werkzeug==1.0.1 # https://github.com/pallets/werkzeug 4 | ipdb==0.13.2 # https://github.com/gotcha/ipdb 5 | psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 6 | 7 | # Testing 8 | # ------------------------------------------------------------------------------ 9 | mypy==0.770 # https://github.com/python/mypy 10 | django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs 11 | pytest==5.4.1 # https://github.com/pytest-dev/pytest 12 | pytest-sugar==0.9.3 # https://github.com/Frozenball/pytest-sugar 13 | 14 | # Code quality 15 | # ------------------------------------------------------------------------------ 16 | flake8==3.7.9 # https://github.com/PyCQA/flake8 17 | flake8-isort==3.0.0 # https://github.com/gforcada/flake8-isort 18 | coverage==5.1 # https://github.com/nedbat/coveragepy 19 | black==19.10b0 # https://github.com/ambv/black 20 | pylint-django==2.0.15 # https://github.com/PyCQA/pylint-django 21 | pre-commit==2.3.0 # https://github.com/pre-commit/pre-commit 22 | 23 | # Documentation 24 | # ------------------------------------------------------------------------------ 25 | sphinx==3.2.1 # https://github.com/sphinx-doc/sphinx 26 | sphinx-autobuild==2020.9.1 # https://github.com/GaretJax/sphinx-autobuild 27 | sphinx-rtd-theme==0.5.0 # https://github.com/readthedocs/sphinx_rtd_theme 28 | 29 | # Django 30 | # ------------------------------------------------------------------------------ 31 | factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy 32 | 33 | django-debug-toolbar==2.2 # https://github.com/jazzband/django-debug-toolbar 34 | django-extensions==2.2.9 # https://github.com/django-extensions/django-extensions 35 | django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin 36 | pytest-django==3.9.0 # https://github.com/pytest-dev/pytest-django 37 | -------------------------------------------------------------------------------- /requirements/production.txt: -------------------------------------------------------------------------------- 1 | # PRECAUTION: avoid production dependencies that aren't in development 2 | 3 | -r ./base.txt 4 | 5 | gunicorn==20.0.4 # https://github.com/benoitc/gunicorn 6 | psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 7 | sentry-sdk==0.16.0 # https://github.com/getsentry/sentry-python 8 | 9 | # Django 10 | # ------------------------------------------------------------------------------ 11 | django-storages[boto3]==1.9.1 # https://github.com/jschneier/django-storages 12 | django-anymail[mailgun]==7.1.0 # https://github.com/anymail/django-anymail 13 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.8.3 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 4 | 5 | [pycodestyle] 6 | max-line-length = 120 7 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 8 | 9 | [mypy] 10 | python_version = 3.8 11 | check_untyped_defs = True 12 | ignore_missing_imports = True 13 | warn_unused_ignores = True 14 | warn_redundant_casts = True 15 | warn_unused_configs = True 16 | plugins = mypy_django_plugin.main 17 | 18 | [mypy.plugins.django-stubs] 19 | django_settings_module = config.settings.test 20 | 21 | [mypy-*.migrations.*] 22 | # Django migrations should not produce any errors: 23 | ignore_errors = True 24 | 25 | [coverage:run] 26 | include = django_crypto_trading_bot/* 27 | omit = *migrations*, *tests* 28 | plugins = 29 | django_coverage_plugin 30 | --------------------------------------------------------------------------------