From 7b3284f9d8a24e60220a7431128748483885ae07 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sat, 29 Jul 2023 15:40:14 -0300 Subject: [PATCH] RC 2 / MVP --- .github/workflows/code-compliance.yml | 2 +- Pipfile | 15 +- Pipfile.lock | 1378 +++++++++-------- README.rst | 6 +- base/admin.py | 105 ++ base/client.py | 252 ++- base/consumers.py | 29 +- base/forms.py | 32 + base/management/commands/load_symbols.py | 1 + base/management/commands/scheduler.py | 28 - base/management/commands/warm_and_ready.py | 2 +- ...ainingdata_options_symbol_info_and_more.py | 608 ++++++++ base/models.py | 906 ++++++++++- base/routing.py | 1 + base/strategies.py | 153 ++ base/tasks.py | 67 +- .../admin/base/user/change_form.html | 7 + base/templates/base/_paginator.html | 17 + base/templates/base/base.html | 4 +- base/templates/base/bot_snippet.html | 42 + base/templates/base/botzinhos.html | 170 ++ base/templates/base/botzinhos_detail.html | 127 ++ base/templates/base/botzinhos_form.html | 21 + base/templates/base/instrucoes.html | 8 +- base/templates/base/login.html | 19 + base/templates/base/navbar.html | 14 +- base/templates/base/trade_snippet.html | 27 + base/templates/base/trade_summary.html | 7 + .../templates/base/trade_summary_snippet.html | 27 + base/templates/base/trades_section.html | 17 + base/templates/base/users_detail.html | 32 + base/templates/base/users_form.html | 21 + base/templatetags/symbols.py | 23 + base/tests.py | 505 +++++- base/urls.py | 52 +- base/utils.py | 12 + base/views.py | 196 ++- dockerfiles/supervisor.conf | 15 +- docs/commands.rst | 9 - docs/governance.rst | 10 + docs/index.rst | 13 +- docs/memorabilia.rst | 12 + docs/requirements_rtd.txt | 108 +- docs/running.rst | 8 +- docs/trading_bots.rst | 62 + tradero/__init__.py | 3 + tradero/celery.py | 39 + tradero/settings.py | 77 +- tradero/urls.py | 1 - 49 files changed, 4330 insertions(+), 960 deletions(-) create mode 100644 base/forms.py delete mode 100644 base/management/commands/scheduler.py create mode 100644 base/migrations/0003_traderobot_alter_trainingdata_options_symbol_info_and_more.py create mode 100644 base/strategies.py create mode 100644 base/templates/admin/base/user/change_form.html create mode 100644 base/templates/base/_paginator.html create mode 100644 base/templates/base/bot_snippet.html create mode 100644 base/templates/base/botzinhos.html create mode 100644 base/templates/base/botzinhos_detail.html create mode 100644 base/templates/base/botzinhos_form.html create mode 100644 base/templates/base/login.html create mode 100644 base/templates/base/trade_snippet.html create mode 100644 base/templates/base/trade_summary.html create mode 100644 base/templates/base/trade_summary_snippet.html create mode 100644 base/templates/base/trades_section.html create mode 100644 base/templates/base/users_detail.html create mode 100644 base/templates/base/users_form.html create mode 100644 docs/governance.rst create mode 100644 docs/memorabilia.rst create mode 100644 docs/trading_bots.rst create mode 100644 tradero/celery.py diff --git a/.github/workflows/code-compliance.yml b/.github/workflows/code-compliance.yml index 30f95d2..a6dd2cd 100644 --- a/.github/workflows/code-compliance.yml +++ b/.github/workflows/code-compliance.yml @@ -53,4 +53,4 @@ jobs: uses: pre-commit/action@v3.0.0 - name: Testing and Coverage - run: pipenv run pytest + run: pipenv run pytest -v diff --git a/Pipfile b/Pipfile index 743ff70..5609083 100644 --- a/Pipfile +++ b/Pipfile @@ -4,7 +4,7 @@ verify_ssl = true name = "pypi" [packages] -django = "~=4.2" +django = "*" django-environ = "*" pytest-django = "*" pytest = "*" @@ -15,29 +15,32 @@ defusedxml = "*" requests-mock = "*" djangorestframework = {git = "https://github.com/encode/django-rest-framework"} binance-connector = "*" -django-ai = {editable = true, ref = "tradero", git = "https://github.com/math-a3k/django-ai"} django-nested-admin = "*" pandas = "*" psycopg2 = "*" -django-rq = "~=2.6" django-redis = "*" django-mathfilters = "*" -rq-scheduler = "*" daphne = "*" channels = "*" channels-redis = "*" django-websocketclient = "*" psycopg = {extras = ["binary", "pool"], version = "*"} statsmodels = "*" -rq = "==1.13" supervisor = "*" uwsgi = "*" -time-machine = "*" encore = "*" pytest-asyncio = "*" pytest-xdist = "*" twisted = {extras = ["http2", "tls"], version = "*"} drf-spectacular = "*" +asgiref = "*" +asyncio = "*" +django-ai = {editable = true, ref = "tradero", git = "https://github.com/math-a3k/django-ai"} +celery = {extras = ["redis"], version = "*"} +django-celery-results = "*" +django-celery-beat = {file = "https://github.com/celery/django-celery-beat/zipball/master"} +python-crontab = "*" +pytest-celery = "*" [dev-packages] pre-commit = "*" diff --git a/Pipfile.lock b/Pipfile.lock index b2e37b5..4b038bd 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3725fe2ebff5406cb8076b1e200adc5d0f69c3d06a88136e5074d64e7cf4ebac" + "sha256": "37140b13e1bce74d331477773051f2a131d3fd713210234e0dd9450caf1f1022" }, "pipfile-spec": 6, "requires": {}, @@ -14,14 +14,32 @@ ] }, "default": { + "amqp": { + "hashes": [ + "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2", + "sha256:6f0956d2c23d8fa6e7691934d8c3930eadb44972cbbd1a7ae3a520f735d43359" + ], + "markers": "python_version >= '3.6'", + "version": "==5.1.1" + }, "asgiref": { "hashes": [ "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e", "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed" ], - "markers": "python_version >= '3.7'", + "index": "pypi", "version": "==3.7.2" }, + "asyncio": { + "hashes": [ + "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41", + "sha256:b62c9157d36187eca799c378e572c969f0da87cd5fc42ca372d92cdb06e7e1de", + "sha256:c46a87b48213d7464f22d9a497b9eef8c1928b68320a2fa94240f969f6fec08c", + "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d" + ], + "index": "pypi", + "version": "==3.4.3" + }, "attrs": { "hashes": [ "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", @@ -52,21 +70,37 @@ "markers": "python_full_version >= '3.6.0'", "version": "==4.12.2" }, + "billiard": { + "hashes": [ + "sha256:0f50d6be051c6b2b75bfbc8bfd85af195c5739c281d3f5b86a5640c65563614a", + "sha256:1ad2eeae8e28053d729ba3373d34d9d6e210f6e4d8bf0a9c64f92bd053f1edf5" + ], + "markers": "python_version >= '3.7'", + "version": "==4.1.0" + }, "binance-connector": { "hashes": [ - "sha256:527595893293af7f828d28d7e18d44a48630a75f7072f1c30265cc74d10edef9", - "sha256:f1018a13b2fc109fb8ff413e9459c997cd3451b58c91aad1a6eed463e7e98cb4" + "sha256:1cfb1bd37df591f1a55569571af09df8880e42cc8e5b1774fe43ec14b33cc4b8", + "sha256:a372e1333d732cad1622efee78b35a3a5f7ec54c739235f4661788df4873550a" ], "index": "pypi", - "version": "==3.1.0" + "version": "==3.1.1" + }, + "celery": { + "hashes": [ + "sha256:27f8f3f3b58de6e0ab4f174791383bbd7445aff0471a43e99cfd77727940753f", + "sha256:f84d1c21a1520c116c2b7d26593926581191435a03aa74b77c941b93ca1c6210" + ], + "index": "pypi", + "version": "==5.3.1" }, "certifi": { "hashes": [ - "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7", - "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" ], "markers": "python_version >= '3.6'", - "version": "==2023.5.7" + "version": "==2023.7.22" }, "cffi": { "hashes": [ @@ -155,92 +189,115 @@ }, "charset-normalizer": { "hashes": [ - "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", - "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", - "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", - "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", - "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", - "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", - "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", - "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", - "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", - "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", - "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", - "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", - "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", - "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", - "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", - "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", - "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", - "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", - "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", - "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", - "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", - "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", - "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", - "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", - "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", - "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", - "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", - "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", - "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", - "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", - "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", - "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", - "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", - "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", - "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", - "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", - "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", - "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", - "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", - "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", - "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", - "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", - "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", - "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", - "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", - "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", - "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", - "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", - "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", - "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", - "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", - "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", - "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", - "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", - "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", - "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", - "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", - "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", - "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", - "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", - "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", - "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", - "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", - "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", - "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", - "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", - "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", - "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", - "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", - "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", - "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", - "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", - "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", - "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", - "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" + "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", + "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", + "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", + "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", + "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", + "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", + "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", + "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", + "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", + "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", + "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", + "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", + "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", + "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", + "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", + "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", + "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", + "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", + "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", + "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", + "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", + "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", + "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", + "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", + "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", + "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", + "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", + "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", + "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", + "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", + "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", + "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", + "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", + "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", + "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", + "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", + "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", + "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", + "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", + "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", + "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", + "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", + "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", + "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", + "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", + "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", + "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", + "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", + "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", + "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", + "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", + "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", + "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", + "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", + "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", + "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", + "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", + "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", + "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", + "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", + "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", + "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", + "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", + "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", + "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", + "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", + "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", + "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", + "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", + "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", + "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", + "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", + "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", + "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", + "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.1.0" + "version": "==3.2.0" }, "click": { "hashes": [ - "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd", + "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5" ], "markers": "python_version >= '3.7'", - "version": "==8.1.3" + "version": "==8.1.6" + }, + "click-didyoumean": { + "hashes": [ + "sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667", + "sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035" + ], + "markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'", + "version": "==0.3.0" + }, + "click-plugins": { + "hashes": [ + "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b", + "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8" + ], + "version": "==1.1.1" + }, + "click-repl": { + "hashes": [ + "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", + "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812" + ], + "markers": "python_version >= '3.6'", + "version": "==0.3.0" }, "constantly": { "hashes": [ @@ -318,36 +375,40 @@ "markers": "python_version >= '3.7'", "version": "==7.2.7" }, - "crontab": { + "cron-descriptor": { "hashes": [ - "sha256:89477e3f93c81365e738d5ee2659509e6373bb2846de13922663e79aa74c6b91" + "sha256:b6ff4e3a988d7ca04a4ab150248e9f166fb7a5c828a85090e75bcc25aa93b4dd" ], - "version": "==1.0.1" + "version": "==1.4.0" }, "cryptography": { "hashes": [ - "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db", - "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a", - "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039", - "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c", - "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3", - "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485", - "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c", - "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca", - "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5", - "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5", - "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3", - "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb", - "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43", - "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31", - "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc", - "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b", - "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006", - "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a", - "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699" + "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711", + "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7", + "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd", + "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e", + "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58", + "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0", + "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d", + "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83", + "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831", + "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766", + "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b", + "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c", + "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182", + "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f", + "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa", + "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4", + "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a", + "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2", + "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76", + "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5", + "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee", + "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f", + "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14" ], "markers": "python_version >= '3.7'", - "version": "==41.0.1" + "version": "==41.0.2" }, "daphne": { "hashes": [ @@ -367,16 +428,16 @@ }, "django": { "hashes": [ - "sha256:2a6b6fbff5b59dd07bef10bcb019bee2ea97a30b2a656d51346596724324badf", - "sha256:672b3fa81e1f853bb58be1b51754108ab4ffa12a77c06db86aa8df9ed0c46fe5" + "sha256:45a747e1c5b3d6df1b141b1481e193b033fd1fdbda3ff52677dc81afdaacbaed", + "sha256:f7c7852a5ac5a3da5a8d5b35cc6168f31b605971441798dac845f17ca8028039" ], "index": "pypi", - "version": "==4.2.2" + "version": "==4.2.3" }, "django-ai": { "editable": true, "git": "https://github.com/math-a3k/django-ai", - "ref": "fff9da1e980d62c5a357ff152646aaffc9e55a78" + "ref": "495f675138e570e1d8a2160c07ad813c20631793" }, "django-bootstrap-v5": { "hashes": [ @@ -386,6 +447,23 @@ "index": "pypi", "version": "==1.0.11" }, + "django-celery-beat": { + "file": "https://github.com/celery/django-celery-beat/zipball/master", + "hashes": [ + "sha256:9b4f100f8d7d7f7325c03f0f453d816749a31e06fb5f432536694bfb7ffa2c16", + "sha256:ae460faa5ea142fba0875409095d22f6bd7bcc7377889b85e8cab5c0dfb781fe", + "sha256:cd0a47f5958402f51ac0c715bc942ae33d7b50b4e48cba91bc3f2712be505df1" + ], + "version": "==2.5.0" + }, + "django-celery-results": { + "hashes": [ + "sha256:0da4cd5ecc049333e4524a23fcfc3460dfae91aa0a60f1fae4b6b2889c254e01", + "sha256:3ecb7147f773f34d0381bac6246337ce4cf88a2ea7b82774ed48e518b67bb8fd" + ], + "index": "pypi", + "version": "==2.5.1" + }, "django-environ": { "hashes": [ "sha256:510f8c9c1d0a38b0815f91504270c29440a0cf44fab07f55942fa8d31bbb9be6", @@ -426,13 +504,13 @@ "index": "pypi", "version": "==5.3.0" }, - "django-rq": { + "django-timezone-field": { "hashes": [ - "sha256:161fcd037aa837e047400db909d2efa2867294c5efe1c45bcaf8d888ad1839b4", - "sha256:2820ecefed024a7f14fe42ebc46b22f53bcd84639a79c0a379491c10d82bfaa3" + "sha256:16ca9955a4e16064e32168b1a0d1cdb2839679c6cb56856c1f49f506e2ca4281", + "sha256:73fc49519273cd5da1c7f16abc04a4bcad87b00cc02968d0d384c0fecf9a8a86" ], - "index": "pypi", - "version": "==2.8.0" + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==5.1" }, "django-websocketclient": { "hashes": [ @@ -444,15 +522,15 @@ }, "djangorestframework": { "git": "https://github.com/encode/django-rest-framework", - "ref": "8dd4250d0234ddf6c6a19a806639678ef7786468" + "ref": "589b5dca9e7613f7742af8baed6ed870476dd23b" }, "drf-spectacular": { "hashes": [ - "sha256:1d84ac70522baaadd6d84a25ce5fe5ea50cfcba0387856689f98ac536f14aa32", - "sha256:b907a72a0244e5dcfeca625e9632cd8ebccdbe2cb528b7c1de1191708be6f31e" + "sha256:8f5a8f87353d1bb8dcb3f3909b7109b2dcbe1d91f3e069409cf322963e140bd6", + "sha256:afeccc6533dcdb4e78afbfcc49f3c5e9c369aeb62f965e4d1a43b165449c147a" ], "index": "pypi", - "version": "==0.26.3" + "version": "==0.26.4" }, "encore": { "hashes": [ @@ -464,19 +542,11 @@ }, "execnet": { "hashes": [ - "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5", - "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.9.0" - }, - "freezegun": { - "hashes": [ - "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446", - "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f" + "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41", + "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af" ], - "markers": "python_version >= '3.6'", - "version": "==1.2.2" + "markers": "python_version >= '3.7'", + "version": "==2.0.2" }, "h2": { "hashes": [ @@ -549,11 +619,27 @@ }, "jsonschema": { "hashes": [ - "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d", - "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6" + "sha256:971be834317c22daaa9132340a51c01b50910724082c2c1a2ac87eeec153a3fe", + "sha256:fb3642735399fa958c0d2aad7057901554596c63349f4f6b283c493cf692a25d" ], - "markers": "python_version >= '3.7'", - "version": "==4.17.3" + "markers": "python_version >= '3.8'", + "version": "==4.18.4" + }, + "jsonschema-specifications": { + "hashes": [ + "sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1", + "sha256:c91a50404e88a1f6ba40636778e2ee08f6e24c5613fe4c53ac24578a5a7f72bb" + ], + "markers": "python_version >= '3.8'", + "version": "==2023.7.1" + }, + "kombu": { + "hashes": [ + "sha256:48ee589e8833126fd01ceaa08f8a2041334e9f5894e5763c8486a550454551e9", + "sha256:fbd7572d92c0bf71c112a6b45163153dea5a7b6a701ec16b568c27d0fd2370f2" + ], + "markers": "python_version >= '3.8'", + "version": "==5.3.1" }, "msgpack": { "hashes": [ @@ -625,34 +711,34 @@ }, "numpy": { "hashes": [ - "sha256:0ac6edfb35d2a99aaf102b509c8e9319c499ebd4978df4971b94419a116d0790", - "sha256:26815c6c8498dc49d81faa76d61078c4f9f0859ce7817919021b9eba72b425e3", - "sha256:4aedd08f15d3045a4e9c648f1e04daca2ab1044256959f1f95aafeeb3d794c16", - "sha256:4c69fe5f05eea336b7a740e114dec995e2f927003c30702d896892403df6dbf0", - "sha256:5177310ac2e63d6603f659fadc1e7bab33dd5a8db4e0596df34214eeab0fee3b", - "sha256:5aa48bebfb41f93043a796128854b84407d4df730d3fb6e5dc36402f5cd594c0", - "sha256:5b1b90860bf7d8a8c313b372d4f27343a54f415b20fb69dd601b7efe1029c91e", - "sha256:6c284907e37f5e04d2412950960894b143a648dea3f79290757eb878b91acbd1", - "sha256:6d183b5c58513f74225c376643234c369468e02947b47942eacbb23c1671f25d", - "sha256:7412125b4f18aeddca2ecd7219ea2d2708f697943e6f624be41aa5f8a9852cc4", - "sha256:7cd981ccc0afe49b9883f14761bb57c964df71124dcd155b0cba2b591f0d64b9", - "sha256:85cdae87d8c136fd4da4dad1e48064d700f63e923d5af6c8c782ac0df8044542", - "sha256:8aa130c3042052d656751df5e81f6d61edff3e289b5994edcf77f54118a8d9f4", - "sha256:95367ccd88c07af21b379be1725b5322362bb83679d36691f124a16357390153", - "sha256:9c7211d7920b97aeca7b3773a6783492b5b93baba39e7c36054f6e749fc7490c", - "sha256:9e3f2b96e3b63c978bc29daaa3700c028fe3f049ea3031b58aa33fe2a5809d24", - "sha256:b76aa836a952059d70a2788a2d98cb2a533ccd46222558b6970348939e55fc24", - "sha256:b792164e539d99d93e4e5e09ae10f8cbe5466de7d759fc155e075237e0c274e4", - "sha256:c0dc071017bc00abb7d7201bac06fa80333c6314477b3d10b52b58fa6a6e38f6", - "sha256:cc3fda2b36482891db1060f00f881c77f9423eead4c3579629940a3e12095fe8", - "sha256:d6b267f349a99d3908b56645eebf340cb58f01bd1e773b4eea1a905b3f0e4208", - "sha256:d76a84998c51b8b68b40448ddd02bd1081bb33abcdc28beee6cd284fe11036c6", - "sha256:e559c6afbca484072a98a51b6fa466aae785cfe89b69e8b856c3191bc8872a82", - "sha256:ecc68f11404930e9c7ecfc937aa423e1e50158317bf67ca91736a9864eae0232", - "sha256:f1accae9a28dc3cda46a91de86acf69de0d1b5f4edd44a9b0c3ceb8036dfff19" + "sha256:012097b5b0d00a11070e8f2e261128c44157a8689f7dedcf35576e525893f4fe", + "sha256:0d3fe3dd0506a28493d82dc3cf254be8cd0d26f4008a417385cbf1ae95b54004", + "sha256:0def91f8af6ec4bb94c370e38c575855bf1d0be8a8fbfba42ef9c073faf2cf19", + "sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f", + "sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631", + "sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd", + "sha256:247d3ffdd7775bdf191f848be8d49100495114c82c2bd134e8d5d075fb386a1c", + "sha256:35a9527c977b924042170a0887de727cd84ff179e478481404c5dc66b4170009", + "sha256:38eb6548bb91c421261b4805dc44def9ca1a6eef6444ce35ad1669c0f1a3fc5d", + "sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105", + "sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09", + "sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf", + "sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e", + "sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588", + "sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa", + "sha256:791f409064d0a69dd20579345d852c59822c6aa087f23b07b1b4e28ff5880fcb", + "sha256:9a3a9f3a61480cc086117b426a8bd86869c213fc4072e606f01c4e4b66eb92bf", + "sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57", + "sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171", + "sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625", + "sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6", + "sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b", + "sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800", + "sha256:f76aebc3358ade9eacf9bc2bb8ae589863a4f911611694103af05346637df1b7", + "sha256:fd67b306320dcadea700a8f79b9e671e607f8696e98ec255915c0c6d6b818503" ], "markers": "python_version >= '3.10'", - "version": "==1.25.0" + "version": "==1.25.1" }, "packaging": { "hashes": [ @@ -702,75 +788,65 @@ }, "pillow": { "hashes": [ - "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1", - "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba", - "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a", - "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799", - "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51", - "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb", - "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5", - "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270", - "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6", - "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47", - "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf", - "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e", - "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b", - "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66", - "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865", - "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec", - "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c", - "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1", - "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38", - "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906", - "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705", - "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef", - "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc", - "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f", - "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf", - "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392", - "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d", - "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe", - "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32", - "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5", - "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7", - "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44", - "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d", - "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3", - "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625", - "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e", - "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829", - "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089", - "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3", - "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78", - "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96", - "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964", - "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597", - "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99", - "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a", - "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140", - "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7", - "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16", - "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903", - "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1", - "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296", - "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572", - "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115", - "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a", - "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd", - "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4", - "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1", - "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb", - "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa", - "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a", - "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569", - "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c", - "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf", - "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082", - "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062", - "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579" + "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5", + "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530", + "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d", + "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca", + "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891", + "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992", + "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7", + "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3", + "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba", + "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3", + "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3", + "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f", + "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538", + "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3", + "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d", + "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c", + "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017", + "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3", + "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223", + "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e", + "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3", + "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6", + "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640", + "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334", + "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1", + "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba", + "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa", + "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0", + "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396", + "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d", + "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485", + "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf", + "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43", + "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37", + "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2", + "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd", + "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86", + "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967", + "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629", + "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568", + "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed", + "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f", + "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551", + "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3", + "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614", + "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff", + "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d", + "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883", + "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684", + "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0", + "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de", + "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b", + "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3", + "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199", + "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51", + "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90" ], - "markers": "python_version >= '3.7'", - "version": "==9.5.0" + "markers": "python_version >= '3.8'", + "version": "==10.0.0" }, "pluggy": { "hashes": [ @@ -787,6 +863,14 @@ ], "version": "==1.3.0" }, + "prompt-toolkit": { + "hashes": [ + "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac", + "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.0.39" + }, "psycopg": { "extras": [ "binary", @@ -952,39 +1036,6 @@ ], "version": "==23.2.0" }, - "pyrsistent": { - "hashes": [ - "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8", - "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440", - "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a", - "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c", - "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3", - "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393", - "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9", - "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da", - "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf", - "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64", - "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a", - "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3", - "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98", - "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2", - "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8", - "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf", - "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc", - "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7", - "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28", - "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2", - "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b", - "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a", - "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64", - "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19", - "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1", - "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9", - "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c" - ], - "markers": "python_version >= '3.7'", - "version": "==0.19.3" - }, "pytest": { "hashes": [ "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32", @@ -995,11 +1046,19 @@ }, "pytest-asyncio": { "hashes": [ - "sha256:2b38a496aef56f56b0e87557ec313e11e1ab9276fc3863f6a7be0f1d0e415e1b", - "sha256:f2b3366b7cd501a4056858bd39349d5af19742aed2d81660b7998b6341c7eb9c" + "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d", + "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b" + ], + "index": "pypi", + "version": "==0.21.1" + }, + "pytest-celery": { + "hashes": [ + "sha256:63dec132df3a839226ecb003ffdbb0c2cb88dd328550957e979c942766578060", + "sha256:cfd060fc32676afa1e4f51b2938f903f7f75d952186b8c6cf631628c4088f406" ], "index": "pypi", - "version": "==0.21.0" + "version": "==0.0.0" }, "pytest-cov": { "hashes": [ @@ -1025,6 +1084,14 @@ "index": "pypi", "version": "==3.3.1" }, + "python-crontab": { + "hashes": [ + "sha256:6d5ba3c190ec76e4d252989a1644fcb233dbf53fbc8fceeb9febe1657b9fb1d4", + "sha256:79fb7465039ddfd4fb93d072d6ee0d45c1ac8bf1597f0686ea14fd4361dba379" + ], + "index": "pypi", + "version": "==3.0.0" + }, "python-dateutil": { "hashes": [ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", @@ -1049,49 +1116,49 @@ }, "pyyaml": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "markers": "python_version >= '3.6'", - "version": "==6.0" + "version": "==6.0.1" }, "redis": { "hashes": [ @@ -1101,6 +1168,14 @@ "markers": "python_version >= '3.7'", "version": "==4.6.0" }, + "referencing": { + "hashes": [ + "sha256:47237742e990457f7512c7d27486394a9aadaf876cbfaa4be65b27b4f4d47c6b", + "sha256:c257b08a399b6c2f5a3510a50d28ab5dbc7bbde049bcaf954d43c446f83ab548" + ], + "markers": "python_version >= '3.8'", + "version": "==0.30.0" + }, "requests": { "hashes": [ "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", @@ -1117,48 +1192,135 @@ "index": "pypi", "version": "==1.11.0" }, - "rq": { - "hashes": [ - "sha256:5bb0380a17597200520731686766bb72faf16ebffb602663560d91ea2c9e7103", - "sha256:621966d7cbf96d5609557a4bd3fd77f749d6d10997d2e353a3e89a14e08eea16" + "rpds-py": { + "hashes": [ + "sha256:0173c0444bec0a3d7d848eaeca2d8bd32a1b43f3d3fde6617aac3731fa4be05f", + "sha256:01899794b654e616c8625b194ddd1e5b51ef5b60ed61baa7a2d9c2ad7b2a4238", + "sha256:02938432352359805b6da099c9c95c8a0547fe4b274ce8f1a91677401bb9a45f", + "sha256:03421628f0dc10a4119d714a17f646e2837126a25ac7a256bdf7c3943400f67f", + "sha256:03975db5f103997904c37e804e5f340c8fdabbb5883f26ee50a255d664eed58c", + "sha256:0766babfcf941db8607bdaf82569ec38107dbb03c7f0b72604a0b346b6eb3298", + "sha256:07e2c54bef6838fa44c48dfbc8234e8e2466d851124b551fc4e07a1cfeb37260", + "sha256:0836d71ca19071090d524739420a61580f3f894618d10b666cf3d9a1688355b1", + "sha256:095b460e117685867d45548fbd8598a8d9999227e9061ee7f012d9d264e6048d", + "sha256:0e7521f5af0233e89939ad626b15278c71b69dc1dfccaa7b97bd4cdf96536bb7", + "sha256:0f2996fbac8e0b77fd67102becb9229986396e051f33dbceada3debaacc7033f", + "sha256:1054a08e818f8e18910f1bee731583fe8f899b0a0a5044c6e680ceea34f93876", + "sha256:13b602dc3e8dff3063734f02dcf05111e887f301fdda74151a93dbbc249930fe", + "sha256:141acb9d4ccc04e704e5992d35472f78c35af047fa0cfae2923835d153f091be", + "sha256:14c408e9d1a80dcb45c05a5149e5961aadb912fff42ca1dd9b68c0044904eb32", + "sha256:159fba751a1e6b1c69244e23ba6c28f879a8758a3e992ed056d86d74a194a0f3", + "sha256:190ca6f55042ea4649ed19c9093a9be9d63cd8a97880106747d7147f88a49d18", + "sha256:196cb208825a8b9c8fc360dc0f87993b8b260038615230242bf18ec84447c08d", + "sha256:1fcdee18fea97238ed17ab6478c66b2095e4ae7177e35fb71fbe561a27adf620", + "sha256:207f57c402d1f8712618f737356e4b6f35253b6d20a324d9a47cb9f38ee43a6b", + "sha256:24a81c177379300220e907e9b864107614b144f6c2a15ed5c3450e19cf536fae", + "sha256:29cd8bfb2d716366a035913ced99188a79b623a3512292963d84d3e06e63b496", + "sha256:2d8b3b3a2ce0eaa00c5bbbb60b6713e94e7e0becab7b3db6c5c77f979e8ed1f1", + "sha256:35da5cc5cb37c04c4ee03128ad59b8c3941a1e5cd398d78c37f716f32a9b7f67", + "sha256:44659b1f326214950a8204a248ca6199535e73a694be8d3e0e869f820767f12f", + "sha256:47c5f58a8e0c2c920cc7783113df2fc4ff12bf3a411d985012f145e9242a2764", + "sha256:4bd4dc3602370679c2dfb818d9c97b1137d4dd412230cfecd3c66a1bf388a196", + "sha256:4ea6b73c22d8182dff91155af018b11aac9ff7eca085750455c5990cb1cfae6e", + "sha256:50025635ba8b629a86d9d5474e650da304cb46bbb4d18690532dd79341467846", + "sha256:517cbf6e67ae3623c5127206489d69eb2bdb27239a3c3cc559350ef52a3bbf0b", + "sha256:5855c85eb8b8a968a74dc7fb014c9166a05e7e7a8377fb91d78512900aadd13d", + "sha256:5a46859d7f947061b4010e554ccd1791467d1b1759f2dc2ec9055fa239f1bc26", + "sha256:65a0583c43d9f22cb2130c7b110e695fff834fd5e832a776a107197e59a1898e", + "sha256:674c704605092e3ebbbd13687b09c9f78c362a4bc710343efe37a91457123044", + "sha256:682726178138ea45a0766907957b60f3a1bf3acdf212436be9733f28b6c5af3c", + "sha256:686ba516e02db6d6f8c279d1641f7067ebb5dc58b1d0536c4aaebb7bf01cdc5d", + "sha256:6a5d3fbd02efd9cf6a8ffc2f17b53a33542f6b154e88dd7b42ef4a4c0700fdad", + "sha256:6aa8326a4a608e1c28da191edd7c924dff445251b94653988efb059b16577a4d", + "sha256:700375326ed641f3d9d32060a91513ad668bcb7e2cffb18415c399acb25de2ab", + "sha256:71f2f7715935a61fa3e4ae91d91b67e571aeb5cb5d10331ab681256bda2ad920", + "sha256:745f5a43fdd7d6d25a53ab1a99979e7f8ea419dfefebcab0a5a1e9095490ee5e", + "sha256:79f594919d2c1a0cc17d1988a6adaf9a2f000d2e1048f71f298b056b1018e872", + "sha256:7d68dc8acded354c972116f59b5eb2e5864432948e098c19fe6994926d8e15c3", + "sha256:7f67da97f5b9eac838b6980fc6da268622e91f8960e083a34533ca710bec8611", + "sha256:83b32f0940adec65099f3b1c215ef7f1d025d13ff947975a055989cb7fd019a4", + "sha256:876bf9ed62323bc7dcfc261dbc5572c996ef26fe6406b0ff985cbcf460fc8a4c", + "sha256:890ba852c16ace6ed9f90e8670f2c1c178d96510a21b06d2fa12d8783a905193", + "sha256:8b08605d248b974eb02f40bdcd1a35d3924c83a2a5e8f5d0fa5af852c4d960af", + "sha256:8b2eb034c94b0b96d5eddb290b7b5198460e2d5d0c421751713953a9c4e47d10", + "sha256:8b9ec12ad5f0a4625db34db7e0005be2632c1013b253a4a60e8302ad4d462afd", + "sha256:8c8d7594e38cf98d8a7df25b440f684b510cf4627fe038c297a87496d10a174f", + "sha256:8d3335c03100a073883857e91db9f2e0ef8a1cf42dc0369cbb9151c149dbbc1b", + "sha256:8d70e8f14900f2657c249ea4def963bed86a29b81f81f5b76b5a9215680de945", + "sha256:9039a11bca3c41be5a58282ed81ae422fa680409022b996032a43badef2a3752", + "sha256:91378d9f4151adc223d584489591dbb79f78814c0734a7c3bfa9c9e09978121c", + "sha256:9251eb8aa82e6cf88510530b29eef4fac825a2b709baf5b94a6094894f252387", + "sha256:933a7d5cd4b84f959aedeb84f2030f0a01d63ae6cf256629af3081cf3e3426e8", + "sha256:978fa96dbb005d599ec4fd9ed301b1cc45f1a8f7982d4793faf20b404b56677d", + "sha256:987b06d1cdb28f88a42e4fb8a87f094e43f3c435ed8e486533aea0bf2e53d931", + "sha256:99b1c16f732b3a9971406fbfe18468592c5a3529585a45a35adbc1389a529a03", + "sha256:99e7c4bb27ff1aab90dcc3e9d37ee5af0231ed98d99cb6f5250de28889a3d502", + "sha256:9c439fd54b2b9053717cca3de9583be6584b384d88d045f97d409f0ca867d80f", + "sha256:9ea4d00850ef1e917815e59b078ecb338f6a8efda23369677c54a5825dbebb55", + "sha256:9f30d205755566a25f2ae0382944fcae2f350500ae4df4e795efa9e850821d82", + "sha256:a06418fe1155e72e16dddc68bb3780ae44cebb2912fbd8bb6ff9161de56e1798", + "sha256:a0805911caedfe2736935250be5008b261f10a729a303f676d3d5fea6900c96a", + "sha256:a1f044792e1adcea82468a72310c66a7f08728d72a244730d14880cd1dabe36b", + "sha256:a216b26e5af0a8e265d4efd65d3bcec5fba6b26909014effe20cd302fd1138fa", + "sha256:a987578ac5214f18b99d1f2a3851cba5b09f4a689818a106c23dbad0dfeb760f", + "sha256:aad51239bee6bff6823bbbdc8ad85136c6125542bbc609e035ab98ca1e32a192", + "sha256:ab2299e3f92aa5417d5e16bb45bb4586171c1327568f638e8453c9f8d9e0f020", + "sha256:ab6919a09c055c9b092798ce18c6c4adf49d24d4d9e43a92b257e3f2548231e7", + "sha256:b0c43f8ae8f6be1d605b0465671124aa8d6a0e40f1fb81dcea28b7e3d87ca1e1", + "sha256:b1440c291db3f98a914e1afd9d6541e8fc60b4c3aab1a9008d03da4651e67386", + "sha256:b52e7c5ae35b00566d244ffefba0f46bb6bec749a50412acf42b1c3f402e2c90", + "sha256:bf4151acb541b6e895354f6ff9ac06995ad9e4175cbc6d30aaed08856558201f", + "sha256:c27ee01a6c3223025f4badd533bea5e87c988cb0ba2811b690395dfe16088cfe", + "sha256:c545d9d14d47be716495076b659db179206e3fd997769bc01e2d550eeb685596", + "sha256:c5934e2833afeaf36bd1eadb57256239785f5af0220ed8d21c2896ec4d3a765f", + "sha256:c7671d45530fcb6d5e22fd40c97e1e1e01965fc298cbda523bb640f3d923b387", + "sha256:c861a7e4aef15ff91233751619ce3a3d2b9e5877e0fcd76f9ea4f6847183aa16", + "sha256:d25b1c1096ef0447355f7293fbe9ad740f7c47ae032c2884113f8e87660d8f6e", + "sha256:d55777a80f78dd09410bd84ff8c95ee05519f41113b2df90a69622f5540c4f8b", + "sha256:d576c3ef8c7b2d560e301eb33891d1944d965a4d7a2eacb6332eee8a71827db6", + "sha256:dd9da77c6ec1f258387957b754f0df60766ac23ed698b61941ba9acccd3284d1", + "sha256:de0b6eceb46141984671802d412568d22c6bacc9b230174f9e55fc72ef4f57de", + "sha256:e07e5dbf8a83c66783a9fe2d4566968ea8c161199680e8ad38d53e075df5f0d0", + "sha256:e564d2238512c5ef5e9d79338ab77f1cbbda6c2d541ad41b2af445fb200385e3", + "sha256:ed89861ee8c8c47d6beb742a602f912b1bb64f598b1e2f3d758948721d44d468", + "sha256:ef1f08f2a924837e112cba2953e15aacfccbbfcd773b4b9b4723f8f2ddded08e", + "sha256:f411330a6376fb50e5b7a3e66894e4a39e60ca2e17dce258d53768fea06a37bd", + "sha256:f68996a3b3dc9335037f82754f9cdbe3a95db42bde571d8c3be26cc6245f2324", + "sha256:f7fdf55283ad38c33e35e2855565361f4bf0abd02470b8ab28d499c663bc5d7c", + "sha256:f963c6b1218b96db85fc37a9f0851eaf8b9040aa46dec112611697a7023da535", + "sha256:fa2818759aba55df50592ecbc95ebcdc99917fa7b55cc6796235b04193eb3c55", + "sha256:fae5cb554b604b3f9e2c608241b5d8d303e410d7dfb6d397c335f983495ce7f6", + "sha256:fb39aca7a64ad0c9490adfa719dbeeb87d13be137ca189d2564e596f8ba32c07" ], - "index": "pypi", - "version": "==1.13" - }, - "rq-scheduler": { - "hashes": [ - "sha256:89d6a18f215536362b22c0548db7dbb8678bc520c18dc18a82fd0bb2b91695ce", - "sha256:c2b19c3aedfc7de4d405183c98aa327506e423bf4cdc556af55aaab9bbe5d1a1" - ], - "index": "pypi", - "version": "==0.13.1" + "markers": "python_version >= '3.8'", + "version": "==0.9.2" }, "scikit-learn": { "hashes": [ - "sha256:065e9673e24e0dc5113e2dd2b4ca30c9d8aa2fa90f4c0597241c93b63130d233", - "sha256:2dd3ffd3950e3d6c0c0ef9033a9b9b32d910c61bd06cb8206303fb4514b88a49", - "sha256:2e2642baa0ad1e8f8188917423dd73994bf25429f8893ddbe115be3ca3183584", - "sha256:44b47a305190c28dd8dd73fc9445f802b6ea716669cfc22ab1eb97b335d238b1", - "sha256:6477eed40dbce190f9f9e9d0d37e020815825b300121307942ec2110302b66a3", - "sha256:6fe83b676f407f00afa388dd1fdd49e5c6612e551ed84f3b1b182858f09e987d", - "sha256:7d5312d9674bed14f73773d2acf15a3272639b981e60b72c9b190a0cffed5bad", - "sha256:7f69313884e8eb311460cc2f28676d5e400bd929841a2c8eb8742ae78ebf7c20", - "sha256:8156db41e1c39c69aa2d8599ab7577af53e9e5e7a57b0504e116cc73c39138dd", - "sha256:8429aea30ec24e7a8c7ed8a3fa6213adf3814a6efbea09e16e0a0c71e1a1a3d7", - "sha256:8b0670d4224a3c2d596fd572fb4fa673b2a0ccfb07152688ebd2ea0b8c61025c", - "sha256:953236889928d104c2ef14027539f5f2609a47ebf716b8cbe4437e85dce42744", - "sha256:99cc01184e347de485bf253d19fcb3b1a3fb0ee4cea5ee3c43ec0cc429b6d29f", - "sha256:9c710ff9f9936ba8a3b74a455ccf0dcf59b230caa1e9ba0223773c490cab1e51", - "sha256:ad66c3848c0a1ec13464b2a95d0a484fd5b02ce74268eaa7e0c697b904f31d6c", - "sha256:bf036ea7ef66115e0d49655f16febfa547886deba20149555a41d28f56fd6d3c", - "sha256:dfeaf8be72117eb61a164ea6fc8afb6dfe08c6f90365bde2dc16456e4bc8e45f", - "sha256:e6e574db9914afcb4e11ade84fab084536a895ca60aadea3041e85b8ac963edb", - "sha256:ea061bf0283bf9a9f36ea3c5d3231ba2176221bbd430abd2603b1c3b2ed85c89", - "sha256:fe0aa1a7029ed3e1dcbf4a5bc675aa3b1bc468d9012ecf6c6f081251ca47f590", - "sha256:fe175ee1dab589d2e1033657c5b6bec92a8a3b69103e3dd361b58014729975c3" + "sha256:0e8102d5036e28d08ab47166b48c8d5e5810704daecf3a476a4282d562be9a28", + "sha256:151ac2bf65ccf363664a689b8beafc9e6aae36263db114b4ca06fbbbf827444a", + "sha256:1d54fb9e6038284548072df22fd34777e434153f7ffac72c8596f2d6987110dd", + "sha256:3a11936adbc379a6061ea32fa03338d4ca7248d86dd507c81e13af428a5bc1db", + "sha256:436aaaae2c916ad16631142488e4c82f4296af2404f480e031d866863425d2a2", + "sha256:552fd1b6ee22900cf1780d7386a554bb96949e9a359999177cf30211e6b20df6", + "sha256:6a885a9edc9c0a341cab27ec4f8a6c58b35f3d449c9d2503a6fd23e06bbd4f6a", + "sha256:7617164951c422747e7c32be4afa15d75ad8044f42e7d70d3e2e0429a50e6718", + "sha256:79970a6d759eb00a62266a31e2637d07d2d28446fca8079cf9afa7c07b0427f8", + "sha256:850a00b559e636b23901aabbe79b73dc604b4e4248ba9e2d6e72f95063765603", + "sha256:8be549886f5eda46436b6e555b0e4873b4f10aa21c07df45c4bc1735afbccd7a", + "sha256:981287869e576d42c682cf7ca96af0c6ac544ed9316328fd0d9292795c742cf5", + "sha256:9877af9c6d1b15486e18a94101b742e9d0d2f343d35a634e337411ddb57783f3", + "sha256:998d38fcec96584deee1e79cd127469b3ad6fefd1ea6c2dfc54e8db367eb396b", + "sha256:9d953531f5d9f00c90c34fa3b7d7cfb43ecff4c605dac9e4255a20b114a27369", + "sha256:ae80c08834a473d08a204d966982a62e11c976228d306a2648c575e3ead12111", + "sha256:c470f53cea065ff3d588050955c492793bb50c19a92923490d18fcb637f6383a", + "sha256:c7e28d8fa47a0b30ae1bd7a079519dd852764e31708a7804da6cb6f8b36e3630", + "sha256:ded35e810438a527e17623ac6deae3b360134345b7c598175ab7741720d7ffa7", + "sha256:ee04835fb016e8062ee9fe9074aef9b82e430504e420bff51e3e5fffe72750ca", + "sha256:fd6e2d7389542eae01077a1ee0318c4fec20c66c957f45c7aac0c6eb0fe3c612" ], "markers": "python_version >= '3.8'", - "version": "==1.2.2" + "version": "==1.3.0" }, "scipy": { "hashes": [ @@ -1261,71 +1423,11 @@ }, "threadpoolctl": { "hashes": [ - "sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b", - "sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380" - ], - "markers": "python_version >= '3.6'", - "version": "==3.1.0" - }, - "time-machine": { - "hashes": [ - "sha256:10c8b170920d3f83dad2268ae8d5e1d8bb431a85198e32d778e6f3a1f93b172d", - "sha256:1787887168e36f57d5ca1abf1b9d065a55eb67067df2fa23aaa4382da36f7098", - "sha256:185f7a4228e993ddae610e24fb3c7e7891130ebb6a40f42d58ea3be0bfafe1b1", - "sha256:1e9973091ad3272c719dafae35a5bb08fa5433c2902224d0f745657f9e3ac327", - "sha256:26a8cc1f8e9f4f69ea3f50b9b9e3a699e80e44ac9359a867208be6adac30fc60", - "sha256:2adc24cf25b7e8d08aea2b109cc42c5db76817b07ee709fae5c66afa4ec7bc6e", - "sha256:2d5e93c14b935d802a310c1d4694a9fe894b48a733ebd641c9a570d6f9e1f667", - "sha256:2e05306f63df3c7760170af6e77e1b37405b7c7c4a97cc9fdf0105f1094b1b1c", - "sha256:30881f263332245a665a49d0e30fda135597c4e18f2efa9c6759c224419c36a5", - "sha256:36f5be6f3042734fca043bedafbfbb6ad4809352e40b3283cb46b151a823674c", - "sha256:3d149a3fae8a06a3593361496ec036a27906fed478ade23ffc01dd402acd0b37", - "sha256:3d6d7b7680e34dbe60da34d75d6d5f31b6206c7149c0de8a7b0f0311d0ef7e3a", - "sha256:3ed92d2a6e2c2b7a0c8161ecca5d012041b7ba147cbdfb2b7f62f45c02615111", - "sha256:4083ec185ab9ece3e5a7ca7a7589114a555f04bcff31b29d4eb47a37e87d97fe", - "sha256:4252f4daef831556e6685853d7a61b02910d0465528c549f179ea4e36aaeb14c", - "sha256:458b52673ec83d10da279d989d7a6ad1e60c93e4ba986210d72e6c78e17102f4", - "sha256:4684308d749fdb0c22af173b081206d2a5a85d2154a683a7f4a60c4b667f7a65", - "sha256:48cce6dcb7118ba4a58537c6de4d1dd6e7ad6ea15d0257d6e0003b45c4a839c2", - "sha256:4c0dda6b132c0180941944ede357109016d161d840384c2fb1096a3a2ef619f4", - "sha256:51e36491bd4a43f8a937ca7c0d1a2287b8998f41306f47ebed250a02f93d2fe4", - "sha256:545a813b7407c33dee388aa380449e79f57f02613ea149c6e907fc9ca3d53e64", - "sha256:55bc6d666966fa2e6283d7433ebe875be37684a847eaa802075433c1ab3a377a", - "sha256:58c65bf4775fca62e1678cb234f1ca90254e811d978971c819d2cd24e1b7f136", - "sha256:5969f325c20bdcb7f8917a6ac2ef328ec41cc2e256320a99dfe38b4080eeae71", - "sha256:5efc4cc914d93138944c488fdebd6e4290273e3ac795d5c7a744af29eb04ce0f", - "sha256:5f451be286d50ec9b685198c7f76cea46538b8c57ec816f60edf5eb68d71c4f4", - "sha256:6241a1742657622ebdcd66cf6045c92e0ec6ca6365c55434cc7fea945008192c", - "sha256:648fec54917a7e67acca38ed8e736b206e8a9688730e13e1cf7a74bcce89dec7", - "sha256:64fd89678cf589fc5554c311417128b2782222dd65f703bf248ef41541761da0", - "sha256:6b3a529ecc819488783e371df5ad315e790b9558c6945a236b13d7cb9ab73b9a", - "sha256:6d2588581d3071d556f96954d084b7b99701e54120bb29dfadaab04791ef6ae4", - "sha256:6f3e5f263a623148a448756a332aad45e65a59876fcb2511f7f61213e6d3ec3e", - "sha256:7b5b60bc00ad2efa5fefee117e5611a28b26f563f1a64df118d1d2f2590a679a", - "sha256:7f03ac22440b00abd1027bfb7dd793dfeffb72dda26f336f4d561835e0ce6117", - "sha256:8527ac8fca7b92556c3c4c0f08e0bea995202db4be5b7d95b9b2ccbcb63649f2", - "sha256:860279c7f9413bc763b3d1aee622937c4538472e2e58ad668546b49a797cb9fb", - "sha256:8829ca7ed939419c2a23c360101edc51e3b57f40708d304b6aed16214d8b2a1f", - "sha256:8cb6285095efa0833fd0301e159748a06e950c7744dc3d38e92e7607e2232d5a", - "sha256:900517e4a4121bf88527343d6aea2b5c99df134815bb8271ef589ec792502a71", - "sha256:91b8b06e09e1dfd53dafe272d41b60690d6f8806d7194c62982b003a088dc423", - "sha256:99fc366cb4fa26d81f12fa36a929db0da89d99909e28231c045e0f1277e0db84", - "sha256:a1a5e283ab47b28205f33fa3c5a2df3fd9f07f09add63dbe76637c3633893a23", - "sha256:a2b3abcb48d7ca7ed95e5d99220317b7ce31378636bb020cabfa62f9099e7dad", - "sha256:a906bb338a6be978b83f09f09d8b24737239330f280c890ecbf1c13828e1838c", - "sha256:ab82ea5a59faa1faa7397465f2edd94789a13f543daa02d16244906339100080", - "sha256:acb2ca50d779d39eab1d0fab48697359e4ffc1aedfa58b79cd3a86ee13253834", - "sha256:b1491fb647568134d38b06e844783d3069f5811405e9a3906eff88d55403e327", - "sha256:b1b07f5da833b2d8ea170cdf15a322c6fa2c6f7e9097a1bea435adc597cdcb5d", - "sha256:c1775a949dd830579d1af5a271ec53d920dc01657035ad305f55c5a1ac9b9f1e", - "sha256:cbe45f88399b8af299136435a2363764d5fa6d16a936e4505081b6ea32ff3e18", - "sha256:e07e2c6c299c5509c72cc221a19f4bf680c87c793727a3127a29e18ddad3db13", - "sha256:e78f2759a63fcc7660d283e22054c7cfa7468fad1ad86d0846819b6ea958d63f", - "sha256:e93750309093275340e0e95bb270801ec9cbf2ee8702d71031f4ccd8cc91dd7f", - "sha256:f8225eb813ea9488de99e61569fc1b2d148d236473a84c6758cc436ffef4c043" + "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032", + "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355" ], - "index": "pypi", - "version": "==2.10.0" + "markers": "python_version >= '3.8'", + "version": "==3.2.0" }, "twisted": { "extras": [ @@ -1349,11 +1451,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771", - "sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82" + "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" ], "markers": "python_version >= '3.7'", - "version": "==4.7.0" + "version": "==4.7.1" }, "tzdata": { "hashes": [ @@ -1373,18 +1475,33 @@ }, "urllib3": { "hashes": [ - "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1", - "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825" + "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", + "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" ], "markers": "python_version >= '3.7'", - "version": "==2.0.3" + "version": "==2.0.4" }, "uwsgi": { "hashes": [ - "sha256:35a30d83791329429bc04fe44183ce4ab512fcf6968070a7bfba42fc5a0552a9" + "sha256:4cc4727258671ac5fa17ab422155e9aaef8a2008ebb86e4404b66deaae965db2" ], "index": "pypi", - "version": "==2.0.21" + "version": "==2.0.22" + }, + "vine": { + "hashes": [ + "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30", + "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e" + ], + "markers": "python_version >= '3.6'", + "version": "==5.0.0" + }, + "wcwidth": { + "hashes": [ + "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e", + "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0" + ], + "version": "==0.2.6" }, "websocket-client": { "hashes": [ @@ -1539,42 +1656,39 @@ }, "black": { "hashes": [ - "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5", - "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915", - "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326", - "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940", - "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b", - "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30", - "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c", - "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c", - "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab", - "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27", - "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2", - "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961", - "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9", - "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb", - "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70", - "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331", - "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2", - "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266", - "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d", - "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6", - "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b", - "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925", - "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8", - "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4", - "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3" + "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3", + "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb", + "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087", + "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320", + "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6", + "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3", + "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc", + "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f", + "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587", + "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91", + "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a", + "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad", + "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926", + "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9", + "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be", + "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd", + "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96", + "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491", + "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2", + "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a", + "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f", + "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995" ], "index": "pypi", - "version": "==23.3.0" + "version": "==23.7.0" }, "certifi": { "hashes": [ - "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7", - "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" ], "markers": "python_version >= '3.6'", - "version": "==2023.5.7" + "version": "==2023.7.22" }, "cfgv": { "hashes": [ @@ -1586,92 +1700,92 @@ }, "charset-normalizer": { "hashes": [ - "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", - "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", - "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", - "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", - "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", - "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", - "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", - "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", - "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", - "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", - "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", - "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", - "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", - "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", - "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", - "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", - "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", - "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", - "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", - "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", - "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", - "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", - "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", - "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", - "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", - "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", - "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", - "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", - "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", - "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", - "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", - "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", - "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", - "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", - "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", - "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", - "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", - "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", - "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", - "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", - "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", - "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", - "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", - "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", - "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", - "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", - "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", - "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", - "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", - "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", - "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", - "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", - "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", - "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", - "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", - "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", - "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", - "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", - "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", - "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", - "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", - "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", - "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", - "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", - "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", - "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", - "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", - "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", - "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", - "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", - "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", - "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", - "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", - "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", - "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" + "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", + "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", + "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", + "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", + "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", + "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", + "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", + "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", + "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", + "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", + "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", + "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", + "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", + "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", + "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", + "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", + "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", + "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", + "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", + "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", + "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", + "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", + "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", + "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", + "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", + "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", + "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", + "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", + "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", + "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", + "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", + "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", + "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", + "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", + "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", + "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", + "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", + "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", + "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", + "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", + "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", + "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", + "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", + "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", + "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", + "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", + "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", + "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", + "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", + "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", + "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", + "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", + "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", + "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", + "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", + "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", + "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", + "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", + "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", + "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", + "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", + "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", + "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", + "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", + "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", + "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", + "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", + "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", + "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", + "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", + "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", + "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", + "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", + "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", + "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.1.0" + "version": "==3.2.0" }, "click": { "hashes": [ - "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd", + "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5" ], "markers": "python_version >= '3.7'", - "version": "==8.1.3" + "version": "==8.1.6" }, "decorator": { "hashes": [ @@ -1683,10 +1797,10 @@ }, "distlib": { "hashes": [ - "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46", - "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" + "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057", + "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8" ], - "version": "==0.3.6" + "version": "==0.3.7" }, "docutils": { "hashes": [ @@ -1713,11 +1827,11 @@ }, "identify": { "hashes": [ - "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4", - "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d" + "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f", + "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54" ], - "markers": "python_version >= '3.7'", - "version": "==2.5.24" + "markers": "python_version >= '3.8'", + "version": "==2.5.26" }, "idna": { "hashes": [ @@ -1761,11 +1875,11 @@ }, "jedi": { "hashes": [ - "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e", - "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612" + "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4", + "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e" ], "markers": "python_version >= '3.6'", - "version": "==0.18.2" + "version": "==0.19.0" }, "jinja2": { "hashes": [ @@ -1777,39 +1891,39 @@ }, "libcst": { "hashes": [ - "sha256:1069b808a711db5cd47538f27eb2c73206317aa0d8b5a3500b23aab24f86eb2e", - "sha256:1312e293b864ef3cb4b09534ed5f104c2dc45b680233c68bf76237295041c781", - "sha256:158478e8f45578fb26621b3dc0fe275f9e004297e9afdcf08936ecda05681174", - "sha256:24582506da24e31f2644f862f11413a6b80fbad68d15194bfcc3f7dfebf2ec5e", - "sha256:349f2b4ee4b982fe254c65c78d941fc96299f3c422b79f95ef8c7bba2b7f0f90", - "sha256:3cb3b7821eac00713844cda079583230c546a589b22ed5f03f2ddc4f985c384b", - "sha256:50be085346a35812535c7f876319689e15a7bfd1bd8efae8fd70589281d944b6", - "sha256:5648aeae8c90a2abab1f7b1bf205769a0179ed2cfe1ea7f681f6885e87b8b193", - "sha256:58fe90458a26a55358207f74abf8a05dff51d662069f070b4bd308a000a80c09", - "sha256:5ed101fee1af7abea3684fcff7fab5b170ceea4040756f54c15c870539daec66", - "sha256:72dff8783ac79cd10f2bd2fde0b28f262e9a22718ae26990948ba6131b85ca8b", - "sha256:76884b1afe475e8e68e704bf26eb9f9a2867029643e58f2f26a0286e3b6e998e", - "sha256:76adc53660ef094ff83f77a2550a7e00d1cab8e5e63336e071c17c09b5a89fe2", - "sha256:7cfa4d4beb84d0d63247aca27f1a15c63984512274c5b23040f8b4ba511036d7", - "sha256:7e1b4cbaf7b1cdad5fa3eababe42d5b46c0d52afe13c5ba4eac2495fc57630ea", - "sha256:83ee7e7be4efac4c140a97d772e1f6b3553f98fa5f46ad78df5dfe51e5a4aa4d", - "sha256:8cdf2d0157438d3d52d310b0b6be31ff99bed19de489b2ebd3e2a4cd9946da45", - "sha256:8fa0ec646ed7bce984d0ee9dbf514af278050bdb16a4fb986e916ace534eebc6", - "sha256:999fbbe467f61cbce9e6e054f86cd1c5ffa3740fd3dc8ebdd600db379f699256", - "sha256:a10adc2e8ea2dda2b70eabec631ead2fc4a7a7ab633d6c2b690823c698b8431a", - "sha256:a144f20aff4643b00374facf8409d30c7935db8176e5b2a07e1fd44004db2c1f", - "sha256:a677103d2f1ab0e50bc3a7cc6c96c7d64bcbac826d785e4cbf5ee9aaa9fcfa25", - "sha256:a8fdfd4a7d301adb785aa4b98e4a7cca45c5ff8cfb460b485d081efcfaaeeab7", - "sha256:b1569d87536bed4e9c11dd5c94a137dc0bce2a2b05961489c6016bf4521bb7cf", - "sha256:b98a829d96e8b209fb761b00cd1bacc27c70eae77d00e57976e5ae2c718c3f81", - "sha256:bb9f10e5763e361e8bd8ff765fc0f1bcf744f242ff8b6d3e50ffec4dda3972ac", - "sha256:bcbd07cec3d7a7be6f0299b0c246e085e3d6cc8af367e2c96059183b97c2e2fe", - "sha256:cfeeabb528b5df7b4be1817b584ce79e9a1a66687bd72f6de9c22272462812f1", - "sha256:e7acfa747112ae40b032739661abd7c81aff37191294f7c2dab8bbd72372e78f", - "sha256:f3e9d9fdd9a9b9b8991936ff1c07527ce7ef396c8233280ba9a7137e72c2e48e" + "sha256:0138068baf09561268c7f079373bda45f0e2b606d2d19df1307ca8a5134fc465", + "sha256:119ba709f1dcb785a4458cf36cedb51d6f9cb2eec0acd7bb171f730eac7cb6ce", + "sha256:1adcfa7cafb6a0d39a1a0bec541355608038b45815e0c5019c95f91921d42884", + "sha256:37187337f979ba426d8bfefc08008c3c1b09b9e9f9387050804ed2da88107570", + "sha256:414350df5e334ddf0db1732d63da44e81b734d45abe1c597b5e5c0dd46aa4156", + "sha256:440887e5f82efb299f2e98d4bfa5663851a878cfc0efed652ab8c50205191436", + "sha256:47dba43855e9c7b06d8b256ee81f0ebec6a4f43605456519577e09dfe4b4288c", + "sha256:4840a3de701778f0a19582bb3085c61591329153f801dc25da84689a3733960b", + "sha256:4b4e336f6d68456017671cdda8ddebf9caebce8052cc21a3f494b03d7bd28386", + "sha256:5599166d5fec40e18601fb8868519dde99f77b6e4ad6074958018f9545da7abd", + "sha256:5e3293e77657ba62533553bb9f0c5fb173780e164c65db1ea2a3e0d03944a284", + "sha256:600c4d3a9a2f75d5a055fed713a5a4d812709947909610aa6527abe08a31896f", + "sha256:6caa33430c0c7a0fcad921b0deeec61ddb96796b6f88dca94966f6db62065f4f", + "sha256:80423311f09fc5fc3270ede44d30d9d8d3c2d3dd50dbf703a581ca7346949fa6", + "sha256:8420926791b0b6206cb831a7ec73d26ae820e65bdf07ce9813c7754c7722c07a", + "sha256:8c50541c3fd6b1d5a3765c4bb5ee8ecbba9d0e798e48f79fd5adf3b6752de4d0", + "sha256:8d31ce2790eab59c1bd8e33fe72d09cfc78635c145bdc3f08296b360abb5f443", + "sha256:967c66fabd52102954207bf1541312b467afc210fdf7033f32da992fb6c2372c", + "sha256:9a4931feceab171e6fce73de94e13880424367247dad6ff2b49cabfec733e144", + "sha256:9d6dec2a3c443792e6af7c36fadc256e4ea586214c76b52f0d18118811dbe351", + "sha256:a6b5aea04c35e13109edad3cf83bc6dcd74309b150a781d2189eecb288b73a87", + "sha256:ae49dcbfadefb82e830d41d9f0a1db0af3b771224768f431f1b7b3a9803ed7e3", + "sha256:ae7f4e71d714f256b5f2ff98b5a9effba0f9dff4d779d8f35d7eb157bef78f59", + "sha256:b0533de4e35396c61aeb3a6266ac30369a855910c2385aaa902ff4aabd60d409", + "sha256:b666a605f4205c8357696f3b6571a38f6a8537cdcbb8f357587d35168298af34", + "sha256:b97f652b15c50e91df411a9c8d5e6f75882b30743a49b387dcedd3f68ed94d75", + "sha256:c90c74a8a314f0774f045122323fb60bacba79cbf5f71883c0848ecd67179541", + "sha256:d237e9164a43caa7d6765ee560412264484e7620c546a2ee10a8d01bd56884e0", + "sha256:ddd4e0eeec499d1c824ab545e62e957dbbd69a16bc4273208817638eb7d6b3c6", + "sha256:f2cb687e1514625e91024e50a5d2e485c0ad3be24f199874ebf32b5de0346150" ], "markers": "python_version >= '3.7'", - "version": "==0.4.10" + "version": "==1.0.1" }, "markupsafe": { "hashes": [ @@ -1909,11 +2023,11 @@ }, "pathspec": { "hashes": [ - "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", - "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" + "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", + "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3" ], "markers": "python_version >= '3.7'", - "version": "==0.11.1" + "version": "==0.11.2" }, "pexpect": { "hashes": [ @@ -1932,11 +2046,11 @@ }, "platformdirs": { "hashes": [ - "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc", - "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e" + "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d", + "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d" ], "markers": "python_version >= '3.7'", - "version": "==3.8.0" + "version": "==3.10.0" }, "pre-commit": { "hashes": [ @@ -1948,11 +2062,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b", - "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f" + "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac", + "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.0.38" + "version": "==3.0.39" }, "ptyprocess": { "hashes": [ @@ -1970,11 +2084,11 @@ }, "pycln": { "hashes": [ - "sha256:1e1f2542aabc8942fd945bbecd39b55ed5f25cd9a70fa116a554cceaab4fdc3b", - "sha256:5029007881d00b87bfc8831ef7cf59c90cc214fbbcc8773f0a9560ddef8d150a" + "sha256:204be6151e305eecd87a282f2bb5555296e234067efc62a2b3d2d53fdb922553", + "sha256:9da51af133a406ce19b179646871cd96be6b04c323232d0d2cbb8d1ef4d0fa63" ], "index": "pypi", - "version": "==2.1.5" + "version": "==2.1.7" }, "pygments": { "hashes": [ @@ -1986,49 +2100,49 @@ }, "pyyaml": { "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" ], "markers": "python_version >= '3.6'", - "version": "==6.0" + "version": "==6.0.1" }, "requests": { "hashes": [ @@ -2063,11 +2177,11 @@ }, "sphinx": { "hashes": [ - "sha256:60c5e04756c1709a98845ed27a2eed7a556af3993afb66e77fec48189f742616", - "sha256:61e025f788c5977d9412587e733733a289e2b9fdc2fef8868ddfbfc4ccfe881d" + "sha256:4e6c5ea477afa0fb90815210fd1312012e1d7542589ab251ac9b53b7c0751bce", + "sha256:59b8e391f0768a96cd233e8300fe7f0a8dc2f64f83dc2a54336a9a84f428ff4e" ], "index": "pypi", - "version": "==7.0.1" + "version": "==7.1.1" }, "sphinxcontrib-applehelp": { "hashes": [ @@ -2150,11 +2264,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:5d8c9dac95c27d20df12fb1d97b9793ab8b2af8a3a525e68c80e21060c161771", - "sha256:935ccf31549830cda708b42289d44b6f74084d616a00be651601a4f968e77c82" + "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" ], "markers": "python_version >= '3.7'", - "version": "==4.7.0" + "version": "==4.7.1" }, "typing-inspect": { "hashes": [ @@ -2165,19 +2279,19 @@ }, "urllib3": { "hashes": [ - "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1", - "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825" + "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", + "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" ], "markers": "python_version >= '3.7'", - "version": "==2.0.3" + "version": "==2.0.4" }, "virtualenv": { "hashes": [ - "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419", - "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1" + "sha256:43a3052be36080548bdee0b42919c88072037d50d56c28bd3f853cbe92b953ff", + "sha256:fd8a78f46f6b99a67b7ec5cf73f92357891a7b3a40fd97637c27f854aae3b9e0" ], "markers": "python_version >= '3.7'", - "version": "==20.23.1" + "version": "==20.24.2" }, "wcwidth": { "hashes": [ diff --git a/README.rst b/README.rst index 2238618..db7e183 100644 --- a/README.rst +++ b/README.rst @@ -7,9 +7,9 @@ tradero ``tradero`` is a tool for achieving self-funding via trading. -Self-funding means generating the means independently to cover a budget in order to opt by the value it produces rather than out of economical neccessity. +Self-funding means generating the means independently to cover a budget in order to opt by the value it produces rather than out of economic necessity (financial freedom). -It tracks Symbols in a `Exchange`_ with a time resolution, calculates indicators and present them in an useful way so the user can perform trading more effectively. +It tracks Symbols in an `Exchange`_ with a time resolution, calculates indicators, presents them in an useful way and provides automatization through trading bots so the user can perform trading more effectively. The application is available at https://tradero.dev @@ -19,7 +19,7 @@ Community ========= * GitHub: https://github.com/math-a3k/tradero -* Discord: https://discord.gg/zACSJmtP +* Discord: https://discord.gg/6CdZmpjKab * Mailing list: https://groups.google.com/g/tradero | diff --git a/base/admin.py b/base/admin.py index 6182994..3ae4f92 100644 --- a/base/admin.py +++ b/base/admin.py @@ -11,6 +11,9 @@ Kline, OutliersSVC, Symbol, + TradeHistory, + TraderoBot, + TraderoBotLog, TrainingData, User, WSClient, @@ -34,6 +37,7 @@ class TraderoUserAdmin(UserAdmin): "trading_active", "date_joined", "is_staff", + "get_bots", ) list_display_links = ("username",) list_filter = ("trading_active",) + UserAdmin.list_filter @@ -45,11 +49,17 @@ class TraderoUserAdmin(UserAdmin): "trading_active", "api_key", "api_secret", + "checkpoint", ) }, ), ) + UserAdmin.fieldsets + def get_bots(self, obj): + return obj.bots.count() + + get_bots.short_description = "# Bots" + @admin.register(Symbol) class SymbolAdmin(admin.ModelAdmin): @@ -194,3 +204,98 @@ def get_is_open(self, obj): return obj.is_open get_is_open.short_description = "Is Open?" + + +class TradeHistoryInline(admin.StackedInline): + model = TradeHistory + min_num = 3 + max_num = 20 + extra = 0 + ordering = ["-timestamp_start"] + readonly_fields = [ + "receipt_buying", + "receipt_selling", + "user", + "symbol", + "timestamp_start", + "timestamp_buying", + "timestamp_selling", + ] + + +@admin.register(TraderoBot) +class TraderoBotAdmin(admin.ModelAdmin): + """ + Admin View for TraderoBot + """ + + list_display = ( + "id", + "user", + "status", + "symbol", + "should_reinvest", + "should_stop", + "is_dummy", + "strategy", + "strategy_params", + "fund_base_asset", + "fund_quote_asset", + "fund_quote_asset_initial", + "get_last_log_message", + ) + list_filter = ("status",) + inlines = [TradeHistoryInline] + readonly_fields = [ + "receipt_buying", + "receipt_selling", + "others", + ] + + def get_last_log_message(self, obj): + return obj.get_last_log_message() + + get_last_log_message.short_description = "Last Log Message" + + +@admin.register(TraderoBotLog) +class TraderoBotLogAdmin(admin.ModelAdmin): + """ + Admin View for TraderoBot + """ + + list_display = ( + "id", + "bot", + "is_dummy", + "get_action_display", + "message", + ) + list_filter = ("action",) + + def get_action_display(self, obj): + return obj.get_action_display() + + get_action_display.short_description = "Action" + + +@admin.register(TradeHistory) +class TradeHistoryAdmin(admin.ModelAdmin): + """ + Admin View for TraderoBot + """ + + list_display = ( + "id", + "user", + "bot", + "is_dummy", + "is_complete", + "variation", + "variation_quote_asset", + "duration_total", + ) + list_filter = ( + "user", + "bot", + ) diff --git a/base/client.py b/base/client.py index 7b56f59..49ffc23 100644 --- a/base/client.py +++ b/base/client.py @@ -1,88 +1,218 @@ -from binance.lib.utils import check_required_parameters +import logging +from decimal import Decimal + +import requests from binance.spot import Spot -from django.utils import timezone +from django.conf import settings + +from .utils import get_commission + +logger = logging.getLogger(__name__) class TraderoClient(Spot): # pragma: no cover """ Tradero's Client - a Spot overhauling... - "t_" methods are Tradero's specific + "tradero_" methods are Tradero's specific """ def __init__(self, user, *args, **kwargs): super().__init__( *args, + base_url=settings.EXCHANGE_API_URL, api_key=user.api_key, api_secret=user.api_secret, **kwargs, ) + self.session.mount( + "https://", requests.adapters.HTTPAdapter(pool_maxsize=36) + ) - def get_quote( + def tradero_market_order( self, - from_asset, - to_asset, - from_amount, - to_amount=None, - wallet_type="SPOT", - valid_time=None, - recv_window=None, - timestamp=None, - **kwargs, + side, + symbol, + amount, + dummy=False, ): - check_required_parameters( + """ + SELL Response: + { + "symbol": "BETABUSD", + "orderId": 204665291, + "orderListId": -1, + "clientOrderId": "cOcAYDp7dTEu9zd4sPoUs4", + "transactTime": 1688436028709, + "price": "0.00000000", + "origQty": "120.00000000", + "executedQty": "120.00000000", + "cummulativeQuoteQty": "10.02360000", + "status": "FILLED", + "timeInForce": "GTC", + "type": "MARKET", + "side": "SELL", + "workingTime": 1688436028709, + "fills": [ - [from_asset, "fromAsset"], - [to_asset, "toAsset"], - [from_amount, "fromAmount"], # TODO: see how to check EITHER - # [timestamp, "timestamp"], - ] - ) - - url_path = "/sapi/v1/convert/getQuote" - # TODO: See if nothing happens if keys have null values and complete - # the parameters in the payload - payload = { - "fromAsset": from_asset, - "toAsset": to_asset, - "fromAmount": from_amount, - "timestamp": timestamp or timezone.now().timestamp() * 1000, - **kwargs, + { + "price": "0.08353000", + "qty": "120.00000000", + "commission": "0.01002360", + "commissionAsset": "BUSD", + "tradeId": 10478698 + } + ], + "selfTradePreventionMode": "NONE" + } + BUY response: + { + "symbol": "ONTBUSD", + "orderId": 227918297, + "orderListId": -1, + "clientOrderId": "LUniYVTucWMYAeKc9QpSRt", + "transactTime": 1689381359633, + "price": "0.00000000", + "origQty": "72.00000000", + "executedQty": "72.00000000", + "cummulativeQuoteQty": "14.91840000", + "status": "FILLED", + "timeInForce": "GTC", + "type": "MARKET", + "side": "BUY", + "workingTime": 1689381359633, + "fills": + [ + { + "price": "0.20720000", + "qty": "72.00000000", + "commission": "0.07200000", + "commissionAsset": "ONT", + "tradeId": 6242788 + } + ], + "selfTradePreventionMode": "NONE" } - return self.sign_request("POST", url_path, payload) + """ + try: + market_price = None + # + if side == "BUY": + # Binance's Client Market's orders are expressed in BASE asset + market_price = Decimal( + self.ticker_price(symbol.symbol)["price"] + ) + amount = Decimal(amount) / market_price + # + step = Decimal(symbol.info["filters"][1]["stepSize"]) + qty = (Decimal(amount) // step) * step + # + if dummy: + # In dummy mode, price is the regular market price + market_price = market_price or Decimal( + self.ticker_price(symbol.symbol)["price"] + ) + exc_qty = qty + cum_exc_qty = qty * market_price + if side == "BUY": + commission = exc_qty * settings.EXCHANGE_FEE + quantity = exc_qty - commission + price = cum_exc_qty / quantity # Net price + else: + commission = cum_exc_qty * settings.EXCHANGE_FEE + quantity = cum_exc_qty - commission + price = quantity / exc_qty # Net price + # + quantity = quantity.quantize(Decimal("." + "0" * 8)) + price = price.quantize(Decimal("." + "0" * 8)) + # + return ( + True, # success + price, + quantity, + { # receipt + "orderId": "DUMMY", + "side": side, + "executedQty": str(exc_qty), + "cummulativeQuoteQty": str(cum_exc_qty), + "fills": [ + { + "commission": str(commission), + "commsssionAsset": ( + symbol.base_asset + if side == "BUY" + else symbol.quote_asset + ), + }, + ], + }, + None, # message + ) + else: + order_params = { + "symbol": symbol.symbol, + "side": side, + "type": "MARKET", + "quantity": qty, + } + if settings.TRADERO_DEBUG: + logger.debug(f"Symbol Info: {symbol.info}") + logger.debug(f"Order params: {order_params}") + asset = ( + symbol.quote_asset + if side == "BUY" + else symbol.base_asset + ) + logger.debug(f"User asset: {self.user_asset(asset=asset)}") + exc = None + for i in range(3): + try: + receipt = self.new_order(**order_params) + if settings.TRADERO_DEBUG: + logger.debug(f"Binance client result: f{receipt}") + commission = get_commission(receipt) + cum_exc_qty = Decimal(receipt["cummulativeQuoteQty"]) + exc_qty = Decimal(receipt["executedQty"]) + if side == "BUY": + quantity = exc_qty - commission + price = cum_exc_qty / quantity # Net price + else: + quantity = cum_exc_qty - commission + price = quantity / exc_qty # Net price + # + quantity = quantity.quantize(Decimal("." + "0" * 8)) + price = price.quantize(Decimal("." + "0" * 8)) + # + return ( + True, # success + price, + quantity, + receipt, + None, # message + ) + except Exception as e: + if settings.TRADERO_DEBUG: + logger.debug(f"Attempt #{i}: {str(e)}") + exc = e + raise exc + except Exception as e: + e_str = str(e) + if "nginx" in e_str: + e_str = e_str[1 : e_str.find("{")] + return (False, None, None, None, e_str) - def accept_quote( + def tradero_sell( self, - quote_id, - recv_window=None, - timestamp=None, - **kwargs, + symbol, + amount, + dummy=False, ): - check_required_parameters( - [ - [quote_id, "quoteId"], - # [timestamp, "timestamp"], - ] - ) + return self.tradero_market_order("SELL", symbol, amount, dummy=dummy) - url_path = "/sapi/v1/convert/acceptQuote" - # TODO: See if nothing happens if keys have null values and complete - # the parameters in the payload - payload = { - "quoteId": quote_id, - "timestamp": timestamp or timezone.now().timestamp() * 1000, - **kwargs, - } - return self.sign_request("POST", url_path, payload) - - def t_convert_assets( + def tradero_buy( self, - from_asset, - to_asset, - from_amount, + symbol, + amount, + dummy=False, ): - """ - TODO: Add error handling - """ - quote_id = self.get_quote(from_asset, to_asset, from_amount)["quoteId"] - return self.accept_quote(quote_id) + return self.tradero_market_order("BUY", symbol, amount, dummy=dummy) diff --git a/base/consumers.py b/base/consumers.py index 628122a..17be17c 100644 --- a/base/consumers.py +++ b/base/consumers.py @@ -18,15 +18,16 @@ def connect(self): self.accept() if "pytest" not in sys.modules: # pragma: no cover WSClient.objects.create( - channel_group=self.group_name, channel_name=self.channel_name + channel_group=self.get_group_name(), + channel_name=self.channel_name, ) # join the group async_to_sync(self.channel_layer.group_add)( - self.group_name, + self.get_group_name(), self.channel_name, ) - logger.info( - f"Should be connected [{self.group_name}] {self.channel_name}" + logger.warning( + f"Should be connected [{self.get_group_name()}] {self.channel_name}" ) def disconnect(self, close_code): @@ -35,11 +36,11 @@ def disconnect(self, close_code): time_disconnect=timezone.now() ) async_to_sync(self.channel_layer.group_discard)( - self.group_name, + self.get_group_name(), self.channel_name, ) - logger.info( - f"Should be DISconnected [{self.group_name}] {self.channel_name}" + logger.warning( + f"Should be DISconnected [{self.get_group_name()}] {self.channel_name}" ) def receive(self, text_data): @@ -47,6 +48,9 @@ def receive(self, text_data): message = text_data_json["message"] self.send(text_data=json.dumps({"message": message})) + def get_group_name(self): + return self.group_name + class SymbolHTMLConsumer(TraderoConsumer): group_name = "symbols_html" @@ -62,3 +66,14 @@ class SymbolJSONConsumer(TraderoConsumer): def symbol_json_message(self, event): # Handles the "symbol.json.message" event when it's sent to the consumer. self.send(text_data=event["message"]) + + +class BotHTMLConsumer(TraderoConsumer): + group_name = "bots_html" + + def bot_html_message(self, event): + # Handles the "symbol.html.message" event when it's sent to the consumer. + self.send(text_data=json.dumps(event["message"])) + + def get_group_name(self): + return f"bots_html_{self.scope['user'].username}" diff --git a/base/forms.py b/base/forms.py new file mode 100644 index 0000000..c33c280 --- /dev/null +++ b/base/forms.py @@ -0,0 +1,32 @@ +from django import forms + +from .models import TraderoBot, User + + +class TraderoBotForm(forms.ModelForm): + class Meta: + model = TraderoBot + fields = [ + "name", + "strategy", + "strategy_params", + "symbol", + "fund_quote_asset", + "fund_quote_asset_initial", + "fund_base_asset", + "is_jumpy", + "should_reinvest", + "should_stop", + ] + + +class UserForm(forms.ModelForm): + class Meta: + model = User + fields = [ + "first_name", + "last_name", + "email", + "api_key", + "api_secret", + ] diff --git a/base/management/commands/load_symbols.py b/base/management/commands/load_symbols.py index 42e76d3..57e6ac2 100644 --- a/base/management/commands/load_symbols.py +++ b/base/management/commands/load_symbols.py @@ -20,6 +20,7 @@ def handle(self, *args, **options): "status": symbol["status"], "base_asset": symbol["baseAsset"], "quote_asset": symbol["quoteAsset"], + "info": symbol, }, ) symbols_processed += 1 diff --git a/base/management/commands/scheduler.py b/base/management/commands/scheduler.py deleted file mode 100644 index 51859a3..0000000 --- a/base/management/commands/scheduler.py +++ /dev/null @@ -1,28 +0,0 @@ -import logging - -import django_rq -from django_rq.management.commands import rqscheduler - -from base.tasks import start_scheduling_round, update_all_indicators_job - -scheduler = django_rq.get_scheduler() -log = logging.getLogger(__name__) - - -def clear_scheduled_jobs(): - # Delete any existing jobs in the scheduler when the app starts up - for job in scheduler.get_jobs(): - log.warning("Deleting scheduled job %s", job) - job.delete() - - -def register_scheduled_jobs(): - start_scheduling_round(update_all_indicators_job) - - -class Command(rqscheduler.Command): - def handle(self, *args, **kwargs): - # This is necessary to prevent dupes - clear_scheduled_jobs() - register_scheduled_jobs() - super(Command, self).handle(*args, **kwargs) diff --git a/base/management/commands/warm_and_ready.py b/base/management/commands/warm_and_ready.py index 110cd84..6b4c980 100644 --- a/base/management/commands/warm_and_ready.py +++ b/base/management/commands/warm_and_ready.py @@ -20,7 +20,7 @@ def handle(self, *args, **options): Symbol.reset_symbols() Symbol.general_warm_up(n_periods=options["periods"]) Symbol.update_all_indicators(push=False) - Symbol.update_all_indicators(only_top=True) + Symbol.update_all_indicators(only_top=True, push=False) self.stdout.write( self.style.SUCCESS( diff --git a/base/migrations/0003_traderobot_alter_trainingdata_options_symbol_info_and_more.py b/base/migrations/0003_traderobot_alter_trainingdata_options_symbol_info_and_more.py new file mode 100644 index 0000000..fa5ebe7 --- /dev/null +++ b/base/migrations/0003_traderobot_alter_trainingdata_options_symbol_info_and_more.py @@ -0,0 +1,608 @@ +# Generated by Django 4.2.2 on 2023-07-21 06:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("base", "0002_wsclient_channel_group"), + ] + + operations = [ + migrations.CreateModel( + name="TraderoBot", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="Name", + ), + ), + ( + "strategy", + models.CharField( + default="acmadness", + max_length=50, + verbose_name="Strategy", + ), + ), + ( + "strategy_params", + models.CharField( + default="microgain=0.3,ac_factor=6", + max_length=510, + verbose_name="Strategy parameters", + ), + ), + ( + "is_jumpy", + models.BooleanField(default=False, verbose_name="Jumpy?"), + ), + ( + "jumpy_blacklist", + models.CharField( + default="", + max_length=510, + verbose_name="Jumpy Symbols' Blacklist", + ), + ), + ( + "should_reinvest", + models.BooleanField( + default=True, verbose_name="Should Reinvest?" + ), + ), + ( + "should_stop", + models.BooleanField( + default=False, + verbose_name="Should Stop After Selling?", + ), + ), + ( + "is_dummy", + models.BooleanField(default=True, verbose_name="Dummy?"), + ), + ( + "status", + models.SmallIntegerField( + choices=[ + (0, "Inactive"), + (1, "Buying"), + (2, "Selling"), + ], + default=0, + verbose_name="Bot Status", + ), + ), + ( + "fund_base_asset", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Fund (Base Asset)", + ), + ), + ( + "fund_quote_asset", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Fund (Quote Asset)", + ), + ), + ( + "fund_quote_asset_initial", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Initial Fund (Quote Asset)", + ), + ), + ( + "price_buying", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Buying Price the Base Asset", + ), + ), + ( + "price_current", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Current Price of the Base Asset", + ), + ), + ( + "price_selling", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Selling Price of the Base Asset", + ), + ), + ( + "timestamp_start", + models.DateTimeField( + blank=True, + null=True, + verbose_name="Timestamp of Start Buying", + ), + ), + ( + "timestamp_buying", + models.DateTimeField( + blank=True, + null=True, + verbose_name="Timestamp of Buying", + ), + ), + ( + "timestamp_selling", + models.DateTimeField( + blank=True, + null=True, + verbose_name="Timestamp of Selling", + ), + ), + ( + "receipt_buying", + models.JSONField( + blank=True, + default=dict, + null=True, + verbose_name="Receipt - Buying", + ), + ), + ( + "receipt_selling", + models.JSONField( + blank=True, + default=dict, + null=True, + verbose_name="Receipt - Selling", + ), + ), + ( + "others", + models.JSONField(default=dict, verbose_name="Others"), + ), + ], + options={ + "verbose_name": "Tradero Bot", + "verbose_name_plural": "Tradero Bots", + }, + ), + migrations.AlterModelOptions( + name="trainingdata", + options={ + "ordering": ["-time"], + "verbose_name": "Training Data", + "verbose_name_plural": "Training Data", + }, + ), + migrations.AddField( + model_name="symbol", + name="info", + field=models.JSONField( + default=dict, verbose_name="Symmbol's Binance's Info" + ), + ), + migrations.AddField( + model_name="symbol", + name="last_updated", + field=models.DateTimeField( + auto_now=True, + null=True, + verbose_name="Last Updated (timestamp)", + ), + ), + migrations.AddField( + model_name="user", + name="checkpoint", + field=models.DateTimeField( + blank=True, null=True, verbose_name="Checkpoint" + ), + ), + migrations.AlterField( + model_name="user", + name="api_secret", + field=models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="Binance's API key secret", + ), + ), + migrations.CreateModel( + name="TraderoBotLog", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_dummy", models.BooleanField(verbose_name="Dummy?")), + ( + "timestamp", + models.DateTimeField( + auto_now_add=True, verbose_name="Timestamp" + ), + ), + ( + "status", + models.SmallIntegerField( + choices=[ + (0, "Inactive"), + (1, "Buying"), + (2, "Selling"), + ], + verbose_name="Bot Status", + ), + ), + ( + "action", + models.SmallIntegerField( + choices=[ + (-1, "Error"), + (0, "Hold"), + (1, "Buy"), + (2, "Sell"), + (3, "Jump"), + (4, "Turn ON"), + (5, "Turn OFF"), + ], + verbose_name="Bot Action", + ), + ), + ( + "fund_base_asset", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Fund (Base Asset)", + ), + ), + ( + "fund_quote_asset", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Fund (Quote Asset)", + ), + ), + ( + "price_buying", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Buying Price the Base Asset (Net)", + ), + ), + ( + "price_current", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Current Market Price of the Base Asset", + ), + ), + ( + "price_selling", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Selling Price of the Base Asset (Net)", + ), + ), + ( + "variation", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Porcentual Variation between Buying and Selling Price", + ), + ), + ( + "message", + models.CharField( + blank=True, + max_length=2048, + null=True, + verbose_name="Extra message (others)", + ), + ), + ( + "bot", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="logs", + to="base.traderobot", + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Tradero Bot Log", + "verbose_name_plural": "Tradero Bots Logs", + }, + ), + migrations.AddField( + model_name="traderobot", + name="symbol", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="bots", + to="base.symbol", + verbose_name="Symbol", + ), + ), + migrations.AddField( + model_name="traderobot", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="bots", + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + migrations.CreateModel( + name="TradeHistory", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("is_dummy", models.BooleanField(verbose_name="Dummy?")), + ( + "strategy", + models.CharField( + blank=True, + max_length=50, + null=True, + verbose_name="Strategy", + ), + ), + ( + "strategy_params", + models.CharField( + blank=True, + max_length=255, + null=True, + verbose_name="Strategy parameters", + ), + ), + ( + "timestamp_start", + models.DateTimeField( + blank=True, null=True, verbose_name="Timestamp - Start" + ), + ), + ( + "timestamp_buying", + models.DateTimeField( + blank=True, + null=True, + verbose_name="Timestamp - Buying", + ), + ), + ( + "timestamp_selling", + models.DateTimeField( + blank=True, + null=True, + verbose_name="Timestamp - Selling", + ), + ), + ( + "fund_base_asset", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Fund (Base Asset)", + ), + ), + ( + "fund_base_asset_exec", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Fund (Base Asset) Executed", + ), + ), + ( + "fund_base_asset_unexec", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Fund (Base Asset) Unexecuted", + ), + ), + ( + "fund_quote_asset", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Fund (Quote Asset)", + ), + ), + ( + "price_buying", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Net Buying Price the Base Asset", + ), + ), + ( + "price_selling", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Net Selling Price of the Base Asset", + ), + ), + ( + "receipt_buying", + models.JSONField( + blank=True, + default=dict, + null=True, + verbose_name="Receipt - Buying", + ), + ), + ( + "receipt_selling", + models.JSONField( + blank=True, + default=dict, + null=True, + verbose_name="Receipt - Selling", + ), + ), + ( + "is_complete", + models.BooleanField( + default=False, verbose_name="Is Trade Complete?" + ), + ), + ( + "variation", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Porcentual Variation between Buying and Selling Price", + ), + ), + ( + "variation_quote_asset", + models.DecimalField( + blank=True, + decimal_places=8, + max_digits=40, + null=True, + verbose_name="Variation of the Quote Asset", + ), + ), + ( + "duration_seeking", + models.DurationField( + blank=True, + null=True, + verbose_name="Elapsed time looking for buy", + ), + ), + ( + "duration_trade", + models.DurationField( + blank=True, + null=True, + verbose_name="Elapsed time between Buying and Selling", + ), + ), + ( + "duration_total", + models.DurationField( + blank=True, + null=True, + verbose_name="Total Elapsed time since start", + ), + ), + ( + "bot", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="trades", + to="base.traderobot", + verbose_name="Bot", + ), + ), + ( + "symbol", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="trades_history", + to="base.symbol", + verbose_name="Symbol", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="trades", + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Trade History", + "verbose_name_plural": "Trades History", + }, + ), + ] diff --git a/base/models.py b/base/models.py index 2400b30..5c2035c 100644 --- a/base/models.py +++ b/base/models.py @@ -13,15 +13,18 @@ from django.core.cache import cache from django.db import models from django.template.loader import render_to_string +from django.urls import reverse from django.utils import timezone from django.utils.module_loading import import_string from django_ai.supervised_learning.models import HGBTreeRegressor, OneClassSVC from encore.concurrent.futures.synchronous import SynchronousExecutor +from requests.adapters import HTTPAdapter from sklearn.model_selection import GridSearchCV from .client import TraderoClient from .indicators import get_indicators -from .utils import datetime_minutes_rounder +from .strategies import get_strategies +from .utils import datetime_minutes_rounder, get_commission channel_layer = get_channel_layer() logger = logging.getLogger(__name__) @@ -48,9 +51,14 @@ class User(AbstractUser): "Binance's API key", max_length=255, blank=True, null=True ) api_secret = models.CharField( - "Binance's API key", max_length=255, blank=True, null=True + "Binance's API key secret", max_length=255, blank=True, null=True ) trading_active = models.BooleanField("Is Trading Active?", default=True) + checkpoint = models.DateTimeField( + "Checkpoint", + blank=True, + null=True, + ) others = models.JSONField("Others", default=dict) class Meta: @@ -62,6 +70,10 @@ def get_client(self, reinit=False): # pragma: no cover self._client = TraderoClient(self) return self._client + @property + def trade_summary(self): + return TradeHistory.summary_for_bot_or_user(user=self) + class WSClient(models.Model): channel_group = models.CharField(max_length=256) @@ -86,26 +98,60 @@ def is_open(self): class SymbolManager(models.Manager): - def top_symbols(self, n=settings.SYMBOLS_QUANTITY): + def available(self): return self.filter( status="TRADING", is_enabled=True, - model_score__gte=settings.MODEL_SCORE_THRESHOLD, - volume_quote_asset__gte=settings.MARKET_SIZE_THRESHOLD, - ).order_by("-model_score")[:n] + ) - def available(self): - return self.filter( + def top_symbols(self, n=settings.SYMBOLS_QUANTITY): + qs1 = self.filter( status="TRADING", is_enabled=True, + model_score__gte=settings.MODEL_SCORE_THRESHOLD, + volume_quote_asset__gte=settings.MARKET_SIZE_THRESHOLD, + ).prefetch_related( + models.Prefetch( + "bots", + queryset=TraderoBot.objects.enabled(), + to_attr="bots_prefetched", + ), ) + qs2 = self.filter( + bots__status__gt=TraderoBot.Status.INACTIVE, + bots__user__trading_active=True, + ).prefetch_related( + models.Prefetch( + "bots", + queryset=TraderoBot.objects.enabled(), + to_attr="bots_prefetched", + ), + ) + return qs1.order_by("-model_score")[:n].union(qs2) def all_top_symbols(self, n=settings.SYMBOLS_QUANTITY): - return self.filter( + qs1 = self.filter( status="TRADING", is_enabled=True, volume_quote_asset__gte=settings.MARKET_SIZE_THRESHOLD, - ).order_by("-model_score")[:n] + ).prefetch_related( + models.Prefetch( + "bots", + queryset=TraderoBot.objects.enabled(), + to_attr="bots_prefetched", + ), + ) + qs2 = self.filter( + bots__status__gt=TraderoBot.Status.INACTIVE, + bots__user__trading_active=True, + ).prefetch_related( + models.Prefetch( + "bots", + queryset=TraderoBot.objects.enabled(), + to_attr="bots_prefetched", + ), + ) + return qs1.order_by("-model_score")[:n].union(qs2) class Symbol(models.Model): @@ -115,6 +161,8 @@ class Symbol(models.Model): _last_td = None _serializer_class = None + client = Spot() + symbol = models.CharField("Symbol", max_length=20) status = models.CharField("Status", max_length=20) is_enabled = models.BooleanField("Is Enabled?", default=True) @@ -186,6 +234,13 @@ class Symbol(models.Model): null=True, ) others = models.JSONField("Others", default=dict) + info = models.JSONField("Symmbol's Binance's Info", default=dict) + last_updated = models.DateTimeField( + "Last Updated (timestamp)", + blank=True, + null=True, + auto_now=True, + ) objects = SymbolManager() @@ -219,12 +274,10 @@ def load_all_data_sync(cls): # pragma: no cover s.load_data() def load_data(self, start_time=None, end_time=None): - n_klines = Kline.load_klines( - self, start_time=start_time, end_time=end_time - ) - n_td = TrainingData.from_klines(self) + Kline.load_klines(self, start_time=start_time, end_time=end_time) + td = TrainingData.from_klines(self) logger.warning(f"{self}: Data loaded") - return (n_klines, n_td) + return td def get_prediction_model(self): if not self._prediction_model_class: @@ -299,8 +352,12 @@ def update_prediction_model(self): ]["r2"] return predictor - def update_indicators(self, push=True): - self._last_td = self.training_data.last() + def update_indicators( + self, push=True, last_td=None, bot_early_notification=False + ): + self._last_td = ( + last_td or self.training_data.first() # negative ordering) + ) klines_24h = self.klines.filter( time_close__gte=timezone.now() - timezone.timedelta(days=1) ) @@ -349,6 +406,17 @@ def update_indicators(self, push=True): self.render_json_snippet(set_cache=True) self.save() + + # Early bot notification if available + if bot_early_notification: # pragma: no cover + if getattr(self, "bots_prefetched", []): + price = self.client.ticker_price(symbol=self.symbol)["price"] + # Re-fetch to avoid errors + for bot in self.bots.enabled(): + bot.price_current = Decimal(price) + bot.symbol = self + bot.decide() + if push: async_to_sync(self.push_to_ws)() # Use warning to make sure it goes @@ -402,9 +470,20 @@ def render_json_snippet(self, set_cache=False): cache.set(cache_key, j, settings.TIME_INTERVAL * 60 + 9) return j - def retrieve_and_update(self): - self.load_data() - self.update_indicators() + def retrieve_and_update(self, push=False): + tds = self.load_data() + last_td = tds[0] if tds else None + self.update_indicators(push=push, last_td=last_td) + + @classmethod + def clean_data(cls): + # TODO: return n records + date_threshold = timezone.now() - timezone.timedelta( + minutes=settings.TIME_INTERVAL * 1000 + ) + Kline.objects.filter(time_close__lt=date_threshold).delete() + TrainingData.objects.filter(time__lt=date_threshold).delete() + logger.warning("-->: Data cleaned for all Symbols") @classmethod def update_all_indicators( @@ -414,20 +493,26 @@ def update_all_indicators( model_score_threshold=settings.MODEL_SCORE_THRESHOLD, threads=settings.EXECUTOR_THREADS, ): - Kline.load_all_klines(model_score=model_score_threshold) + timestamp = timezone.now() if only_top: symbols = cls.objects.all_top_symbols() else: symbols = cls.objects.available() + with thread_pool_executor(threads) as executor: for symbol in symbols: - executor.submit(TrainingData.from_klines, symbol) - with thread_pool_executor(threads) as executor: - for symbol in symbols: - executor.submit(symbol.update_indicators, push=push) + executor.submit(symbol.retrieve_and_update, push=push) logger.warning( f"-> UPDATE ALL PREDICTIONS DONE (MST: {model_score_threshold}) <-" ) + cls.clean_data() + message = ( + f"---> Elapsed Time: " + f"{ (timezone.now() - timestamp).total_seconds() } " + f"({len(symbols)} Symbols) <----" + ) + logger.warning(message) + return message @classmethod def general_warm_up( @@ -502,7 +587,7 @@ def reset_symbols( class Kline(models.Model): - client = Spot() + _client = None symbol = models.ForeignKey( Symbol, @@ -555,6 +640,13 @@ def __str__(self): ) return f_str + @classmethod + def get_client(cls): + if not cls._client: + cls._client = Spot() + cls._client.session.mount("https://", HTTPAdapter(pool_maxsize=36)) + return cls._client + @classmethod def from_binance_kline(cls, symbol, time_interval, b_kline): """ @@ -666,7 +758,7 @@ def load_klines( end_time=None, limit=1000, ): - last_kline = symbol.klines.order_by("time_open").last() + last_kline = symbol.klines.order_by("time_open").last() # Check if last_kline and not start_time: logger.warning(f"{symbol}: Last kline is {last_kline}") start_time = datetime_minutes_rounder(last_kline.time_close) @@ -695,7 +787,8 @@ def load_klines( logger.warning( f"{symbol}: Requesting klines from {start_time} to {end_time}." ) - klines_binance = cls.client.klines( + client = cls.get_client() + klines_binance = client.klines( symbol.symbol, interval=f"{interval}m", startTime=int(start_time.timestamp() * 1000), @@ -721,7 +814,7 @@ def load_klines( f"{symbol}: Received {len(klines_binance)} klines, " f"created {len(created)}." ) - return created + return sorted(created, key=lambda x: x.time_close, reverse=True) @classmethod def load_all_klines( @@ -932,9 +1025,7 @@ class TrainingData(models.Model): class Meta: verbose_name = "Training Data" verbose_name_plural = "Training Data" - ordering = [ - "time", - ] + ordering = ["-time"] # Descending ordering constraints = [ models.UniqueConstraint( fields=["symbol", "time", "time_interval"], @@ -1039,14 +1130,14 @@ def from_klines(cls, symbol): symbol=symbol, time__in=times, time_interval__in=time_intervals ).values_list("time", flat=True) tds_to_create = [td for td in tds if td.time not in existing_data] - created = len(cls.objects.bulk_create(tds_to_create)) - if created > 0: - logger.warning(f"{symbol}: Created {created} Training Data.") + created = cls.objects.bulk_create(tds_to_create) + if len(created) > 0: + logger.warning(f"{symbol}: Created {len(created)} Training Data.") else: logger.warning( f"{symbol}: TD already exist: {tds} {tds_to_create}" ) - return created + return sorted(created, key=lambda x: x.time, reverse=True) class TraderoMixin: @@ -1087,7 +1178,7 @@ def get_targets(self): return [float(t) for t in targets] def predict_next(self): - last_td = self.symbol.training_data.last() + last_td = self.symbol.training_data.first() obs = {"variation_01": last_td.variation} for i in range(2, self.get_window_size() + 1): obs[f"variation_{i:02d}"] = getattr( @@ -1101,7 +1192,7 @@ def predict_next_times(self, n): """ Assumption: n < self.WINDOW """ - last_td = self.symbol.training_data.order_by("time").last() + last_td = self.symbol.training_data.first() preds = [] obss = [] preds.append(self.predict_next()) @@ -1139,12 +1230,18 @@ def calibrate_window(self, save=True): self.save() def calibrate_model(self, save=True): + """ + TOOD: Review this + """ + print("Before:", self.symbol.model_score) gs_eo = GridSearchCV(self.get_engine_object(), self.CALIBRATION_PARAMS) data, targets = self.get_data(), self.get_targets() gs_eo.fit(data, targets) for param, value in gs_eo.best_params_.items(): setattr(self, param, value) logger.warning(f"{self.symbol}: Best params: {gs_eo.best_params_}") + self.perform_inference(save=False) + print("After:", self.metadata["inference"]["current"]["scores"]["r2"]) if save: # pragma: no cover self.save() return gs_eo.best_params_ @@ -1221,3 +1318,736 @@ class OutliersSVC(OutlierDetectionModel, OneClassSVC): class Meta: verbose_name = "Outliers SVC" verbose_name_plural = "Outliers SVCs" + + +class TraderoBotManager(models.Manager): + def enabled(self): + return self.get_queryset().exclude( + models.Q(status=TraderoBot.Status.INACTIVE) + | models.Q(user__trading_active=False) + ) + + def get_queryset(self): + return super().get_queryset().select_related("symbol", "user") + + +class TraderoBot(models.Model): + _client = None + _strategies = get_strategies() + _last_klines = None + + class Status(models.IntegerChoices): + INACTIVE = 0, "Inactive" + BUYING = 1, "Buying" + SELLING = 2, "Selling" + + class Action(models.IntegerChoices): + ERROR = -1, "Error" + HOLD = 0, "Hold" + BUY = 1, "Buy" + SELL = 2, "Sell" + JUMP = 3, "Jump" + TURN_ON = 4, "Turn ON" + TURN_OFF = 5, "Turn OFF" + + user = models.ForeignKey( + User, + verbose_name="User", + related_name="bots", + on_delete=models.PROTECT, + ) + symbol = models.ForeignKey( + Symbol, + verbose_name="Symbol", + related_name="bots", + on_delete=models.PROTECT, + ) + name = models.CharField( + "Name", + max_length=255, + blank=True, + null=True, + ) + strategy = models.CharField("Strategy", max_length=50, default="acmadness") + strategy_params = models.CharField( + "Strategy parameters", + max_length=510, + default="microgain=0.3,ac_factor=6", + ) + is_jumpy = models.BooleanField("Jumpy?", default=False) + jumpy_blacklist = models.CharField( + "Jumpy Symbols' Blacklist", max_length=510, default="" + ) + should_reinvest = models.BooleanField("Should Reinvest?", default=True) + should_stop = models.BooleanField( + "Should Stop After Selling?", default=False + ) + is_dummy = models.BooleanField("Dummy?", default=True) + status = models.SmallIntegerField( + "Bot Status", choices=Status.choices, default=Status.INACTIVE + ) + fund_base_asset = models.DecimalField( + "Fund (Base Asset)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + fund_quote_asset = models.DecimalField( + "Fund (Quote Asset)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + fund_quote_asset_initial = models.DecimalField( + "Initial Fund (Quote Asset)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + price_buying = models.DecimalField( + "Buying Price the Base Asset", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + price_current = models.DecimalField( + "Current Price of the Base Asset", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + price_selling = models.DecimalField( + "Selling Price of the Base Asset", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + timestamp_start = models.DateTimeField( + "Timestamp of Start Buying", + blank=True, + null=True, + ) + timestamp_buying = models.DateTimeField( + "Timestamp of Buying", + blank=True, + null=True, + ) + timestamp_selling = models.DateTimeField( + "Timestamp of Selling", + blank=True, + null=True, + ) + receipt_buying = models.JSONField( + "Receipt - Buying", default=dict, blank=True, null=True + ) + receipt_selling = models.JSONField( + "Receipt - Selling", default=dict, blank=True, null=True + ) + others = models.JSONField("Others", default=dict) + + class Meta: + verbose_name = "Tradero Bot" + verbose_name_plural = "Tradero Bots" + + objects = TraderoBotManager() + + def __str__(self): + return f"BOT #{self.id}: {self.symbol.symbol} | {self.get_status_display()}" + + def get_absolute_url(self): + return reverse("base:botzinhos-detail", kwargs={"pk": self.pk}) + + def save(self, *args, **kwargs): + self.others["ws_group_name"] = f"bots_html_{self.user.username}" + super().save(*args, **kwargs) + if not self.name: + self.name = f"{settings.BOT_DEFAULT_NAME}-{self.pk:03d}" + super().save(*args, **kwargs) + self.render_html_snippet(set_cache=True) + async_to_sync(self.push_to_ws)() + + def get_client(self, reinit=False): # pragma: no cover + if not self._client or reinit: + self._client = self.user.get_client() + return self._client + + def get_strategy(self): + params = {} + for pv in self.strategy_params.split(","): + pv = pv.split("=") + params[pv[0]] = pv[1] + return self._strategies[self.strategy](self, **params) + + def on(self): + if self.price_buying: + self.status = self.Status.SELLING + else: + self.timestamp_start = self.timestamp_start or timezone.now() + self.status = self.Status.BUYING + self.log_trade() + self.log(self.Action.TURN_ON, "Turned ON") + self.save() + return True + + def off(self): + self.status = self.Status.INACTIVE + self.log(self.Action.TURN_OFF, "Turned OFF") + self.save() + return True + + def buy(self): + client = self.get_client() + amount = self.fund_quote_asset or self.fund_quote_asset_initial + success, price, quantity, receipt, message = client.tradero_buy( + self.symbol, + amount, + dummy=self.is_dummy, + ) + if success: + self.receipt_buying = receipt + self.timestamp_buying = timezone.now() + if not self.timestamp_start: # pragma: no cover + self.timestamp_start = self.timestamp_buying + self.price_buying = price + self.fund_base_asset = quantity + self.log(self.Action.BUY) + self.log_trade() + self.fund_quote_asset = ( + None # Unexecuted FQA is not logged when Buying + ) + if self.status != self.Status.INACTIVE: + self.status = self.Status.SELLING + self.save() + return True + self.log(self.Action.ERROR, message=message) + self.save() + return False + + def sell(self): + client = self.get_client() + success, price, quantity, receipt, message = client.tradero_sell( + self.symbol, + self.fund_base_asset, + dummy=self.is_dummy, + ) + if success: + self.receipt_selling = receipt + self.price_selling = price + self.fund_quote_asset = quantity + self.timestamp_selling = timezone.now() + self.log(self.Action.SELL) + self.log_trade() + # Reset state + self.fund_base_asset = self.fund_base_asset - Decimal( + self.receipt_selling["executedQty"] + ) + if not self.should_reinvest: + self.fund_quote_asset = self.fund_quote_asset_initial + self.timestamp_selling, self.timestamp_buying = None, None + self.receipt_selling, self.receipt_buying = None, None + self.price_selling, self.price_buying = None, None + if self.status != self.Status.INACTIVE: + self.status = self.Status.BUYING + if self.should_stop: + self.status = self.Status.INACTIVE + self.timestamp_start = None + else: + self.timestamp_start = timezone.now() + self.save() + return True + self.log(self.Action.ERROR, message=message) + self.save() + return False + + def jump(self, to_symbol): + current_symbol = self.symbol + fba = self.fund_quote_asset + fba_msg = ( + f" (leaving {fba:.6f} {current_symbol.symbol} behind)" + if fba + else "" + ) + self.symbol = to_symbol + self.fund_base_asset = None + self.price_current = self.get_current_price() + self.log( + self.Action.JUMP, + f"Jumped from {current_symbol} to {to_symbol}{fba_msg}", + ) + self.save() + + def decide(self): + strategy = self.get_strategy() + if self.status == self.Status.BUYING: + should_buy, message = strategy.evaluate_buy() + if should_buy: + self.buy() + return + if self.is_jumpy: + should_jump, symbol = strategy.evaluate_jump() + if should_jump: + self.jump(symbol) + self.get_strategy() # Update values + self.decide() + return + message += " and no other symbol to go was found." + if self.status == self.Status.SELLING: + should_sell, message = strategy.evaluate_sell() + if should_sell: + self.sell() + return + self.log(self.Action.HOLD, message) + self.save() + + def get_current_price(self): + client = self.get_client() + price = client.ticker_price(symbol=self.symbol.symbol)["price"] + return Decimal(price) + + def log(self, action, message=None): + log = TraderoBotLog( + bot=self, + is_dummy=self.is_dummy, + status=self.status, + action=action, + fund_base_asset=self.fund_base_asset, + fund_quote_asset=self.fund_quote_asset, + price_buying=self.price_buying, + price_current=self.price_current, + message=message, + ) + if action == self.Action.BUY: + log.message = ( + f"Bought {self.fund_base_asset:.3f} of {self.symbol} at " + f"{self.price_buying:.6f} (" + f"{self.fund_base_asset * self.price_buying:.6f} " + f"{self.symbol.quote_asset})" + ) + if action == self.Action.SELL: + log.price_selling = self.price_selling + log.variation = (self.price_selling / self.price_buying - 1) * 100 + log.message = ( + f"BOOM! Sold {self.fund_base_asset:.3f} of {self.symbol} at " + f"{self.price_selling:.6f} - VAR: {log.variation:.3f}%" + ) + log.save() + last_logs = self.others.get("last_logs", []) + last_logs.append(f"{log.timestamp:%Y-%m-%d %H:%M:%S}| {log.message}") + self.others["last_logs"] = last_logs[-4:] + + def log_trade(self): + trade_history, _ = self.trades.update_or_create( + bot=self, + user=self.user, + timestamp_start=self.timestamp_start, + defaults={ + "is_dummy": self.is_dummy, + "symbol": self.symbol, + "strategy": self.strategy, + "strategy_params": self.strategy_params, + "timestamp_start": self.timestamp_start, + "timestamp_buying": self.timestamp_buying, + "timestamp_selling": self.timestamp_selling, + "fund_base_asset": self.fund_base_asset, + "price_buying": self.price_buying, + "price_selling": self.price_selling, + "receipt_buying": self.receipt_buying, + "receipt_selling": self.receipt_selling, + }, + ) + trade_history.save() + return trade_history + + def get_last_log_message(self): + last_log = self.logs.last() + if last_log: + return last_log.message + return None + + def render_html_snippet(self, set_cache=False): + cache_key = f"bot_{self.pk}_html" + html = cache.get(cache_key) + if not html or set_cache: + html = render_to_string( + "base/bot_snippet.html", + { + "bot": self, + }, + ) + cache.set(cache_key, html, settings.TIME_INTERVAL_BOTS * 60 + 9) + return html + + async def push_to_ws(self): + bot = f"botzinho-{self.pk}" + text = self.render_html_snippet() + await channel_layer.group_send( + self.others["ws_group_name"], + { + "type": "bot.html.message", + "message": { + "type": "bot_update", + "bot": bot, + "text": text, + }, + }, + ) + + @classmethod + def update_all_bots(cls): + client = Spot() + bots = cls.objects.enabled() + if bots: + symbols = list( + Symbol.objects.filter(bots__in=bots) + .distinct() + .values_list("symbol", flat=True) + ) + prices = { + t["symbol"]: t["price"] + for t in client.ticker_price(symbols=symbols) + } + for bot in bots: + bot.price_current = Decimal(prices[bot.symbol.symbol]) + bot.decide() + message = f"{len(bots)} bots updated." + else: # pragma: no cover + message = "No bots were found." + logger.warning(message) + return message + + +class TraderoBotLog(models.Model): + bot = models.ForeignKey( + TraderoBot, + verbose_name="User", + related_name="logs", + on_delete=models.PROTECT, + ) + is_dummy = models.BooleanField("Dummy?") + timestamp = models.DateTimeField("Timestamp", auto_now_add=True) + status = models.SmallIntegerField( + "Bot Status", + choices=TraderoBot.Status.choices, + ) + action = models.SmallIntegerField( + "Bot Action", + choices=TraderoBot.Action.choices, + ) + fund_base_asset = models.DecimalField( + "Fund (Base Asset)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + fund_quote_asset = models.DecimalField( + "Fund (Quote Asset)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + price_buying = models.DecimalField( + "Buying Price the Base Asset (Net)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + price_current = models.DecimalField( + "Current Market Price of the Base Asset", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + price_selling = models.DecimalField( + "Selling Price of the Base Asset (Net)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + variation = models.DecimalField( + "Porcentual Variation between Buying and Selling Price", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + message = models.CharField( + "Extra message (others)", + max_length=2048, + blank=True, + null=True, + ) + + class Meta: + verbose_name = "Tradero Bot Log" + verbose_name_plural = "Tradero Bots Logs" + + def __str__(self): + return ( + f"{self.bot} {'[[DUMMY]]' if self.is_dummy else ''}| " + f"{self.timestamp} | [{self.get_action_display()}] " + f"FBA: {self.fund_base_asset}, " + f"FQA: {self.fund_quote_asset}, PB(N): {self.price_buying}, " + f"PC(M): {self.price_current}, PS(N): {self.price_selling}, " + f"VAR: {self.variation} || {self.message}" + ) + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + logger.warning(str(self)) + + +class TradeHistoryManager(models.Manager): + def get_queryset(self): + return super().get_queryset().select_related("symbol", "user", "bot") + + +class TradeHistory(models.Model): + """ + Highly denormalized on purpose + """ + + user = models.ForeignKey( + User, + verbose_name="User", + related_name="trades", + on_delete=models.PROTECT, + ) + bot = models.ForeignKey( + TraderoBot, + verbose_name="Bot", + related_name="trades", + on_delete=models.PROTECT, + ) + is_dummy = models.BooleanField("Dummy?") + symbol = models.ForeignKey( + Symbol, + verbose_name="Symbol", + related_name="trades_history", + on_delete=models.PROTECT, + ) + strategy = models.CharField( + "Strategy", + max_length=50, + blank=True, + null=True, + ) + strategy_params = models.CharField( + "Strategy parameters", + max_length=255, + blank=True, + null=True, + ) + timestamp_start = models.DateTimeField( + "Timestamp - Start", + blank=True, + null=True, + ) + timestamp_buying = models.DateTimeField( + "Timestamp - Buying", + blank=True, + null=True, + ) + timestamp_selling = models.DateTimeField( + "Timestamp - Selling", + blank=True, + null=True, + ) + fund_base_asset = models.DecimalField( + "Fund (Base Asset)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + fund_base_asset_exec = models.DecimalField( + "Fund (Base Asset) Executed", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + fund_base_asset_unexec = models.DecimalField( + "Fund (Base Asset) Unexecuted", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + fund_quote_asset = models.DecimalField( + "Fund (Quote Asset)", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + price_buying = models.DecimalField( + "Net Buying Price the Base Asset", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + price_selling = models.DecimalField( + "Net Selling Price of the Base Asset", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + receipt_buying = models.JSONField( + "Receipt - Buying", default=dict, blank=True, null=True + ) + receipt_selling = models.JSONField( + "Receipt - Selling", default=dict, blank=True, null=True + ) + is_complete = models.BooleanField("Is Trade Complete?", default=False) + variation = models.DecimalField( + "Porcentual Variation between Buying and Selling Price", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + variation_quote_asset = models.DecimalField( + "Variation of the Quote Asset", + max_digits=40, + decimal_places=8, + blank=True, + null=True, + ) + duration_seeking = models.DurationField( + "Elapsed time looking for buy", + blank=True, + null=True, + ) + duration_trade = models.DurationField( + "Elapsed time between Buying and Selling", + blank=True, + null=True, + ) + duration_total = models.DurationField( + "Total Elapsed time since start", + blank=True, + null=True, + ) + + class Meta: + verbose_name = "Trade History" + verbose_name_plural = "Trades History" + + objects = TradeHistoryManager() + + def __str__(self): + return ( + f"{self.user}: #{self.bot.id} {'[[DUMMY]]' if self.is_dummy else ''} |" + f" {self.symbol.symbol} | {self.timestamp_start} - " + f"{self.timestamp_selling or '...'}:: Var: {self.variation}% " + f"Var FQA: {self.variation_quote_asset}" + ) + + def save(self, *args, **kwargs): + if self.timestamp_buying and self.timestamp_start: + self.duration_seeking = ( + self.timestamp_buying - self.timestamp_start + ) + if ( + self.timestamp_selling + and self.timestamp_buying + and self.timestamp_start + ): + self.duration_trade = ( + self.timestamp_selling - self.timestamp_buying + ) + self.duration_total = self.timestamp_selling - self.timestamp_start + if self.price_buying and self.price_selling: + self.variation = (self.price_selling / self.price_buying - 1) * 100 + if self.receipt_buying: + self.fund_quote_asset = Decimal( + self.receipt_buying["cummulativeQuoteQty"] + ) + if self.receipt_buying and self.receipt_selling: + self.is_complete = True + self.fund_base_asset = Decimal( + self.receipt_buying["executedQty"] + ) - get_commission(self.receipt_buying) + self.fund_base_asset_exec = Decimal( + self.receipt_selling["executedQty"] + ) + self.fund_base_asset_unexec = self.fund_base_asset - Decimal( + self.fund_base_asset_exec + ) + self.variation_quote_asset = ( + Decimal(self.receipt_selling["cummulativeQuoteQty"]) + - get_commission(self.receipt_selling) + - self.fund_quote_asset + ) + super().save(*args, **kwargs) + + @classmethod + def summary(cls, trades_qs, checkpoint=None): + result = {"rows": {}} + dates = { + "24h": timezone.now() - timezone.timedelta(days=1), + "1w": timezone.now() - timezone.timedelta(days=7), + "checkpoint": checkpoint, + "alltime": None, + } + for label, t in dates.items(): + t_qs = trades_qs.filter(is_complete=True) + if t: + t_qs = t_qs.filter(timestamp_selling__gte=t) + result["rows"][label] = t_qs.aggregate( + variation_quote_asset_total=models.Sum( + "variation_quote_asset" + ), + variation_average=models.Avg("variation"), + trades_quantity=models.Count("pk"), + ) + result["meta"] = { + "descriptions": { + "rows": { + "24h": "Last 24 Hours", + "1w": "Last Week", + "checkpoint": "Checkpoint", + "alltime": "All-Time", + }, + "cols": { + "variation_quote_asset_total": "Total Gains (QA)", + "variation_average": "Avg. % Var", + "trades_quantity": "#Trades", + }, + "formats": { + "variation_quote_asset_total": ".4f", + "variation_average": ".3f", + "trades_quantity": "i", + }, + } + } + return result + + @classmethod + def summary_for_bot_or_user(cls, bot=None, user=None): + if bot: + qs = cls.objects.filter(bot=bot) + checkpoint = bot.user.checkpoint + else: + qs = cls.objects.filter(user=user) + checkpoint = user.checkpoint + result = { + "object": bot or user, + "cp": checkpoint, + "dummy": cls.summary(qs.filter(is_dummy=True), checkpoint), + "real": cls.summary(qs.filter(is_dummy=False), checkpoint), + } + return result diff --git a/base/routing.py b/base/routing.py index 3ab909e..fd99411 100644 --- a/base/routing.py +++ b/base/routing.py @@ -5,4 +5,5 @@ websocket_urlpatterns = [ path("ws/symbols/html", consumers.SymbolHTMLConsumer.as_asgi()), path("ws/symbols/json", consumers.SymbolJSONConsumer.as_asgi()), + path("ws/bots/html", consumers.BotHTMLConsumer.as_asgi()), ] diff --git a/base/strategies.py b/base/strategies.py new file mode 100644 index 0000000..b2c258a --- /dev/null +++ b/base/strategies.py @@ -0,0 +1,153 @@ +import inspect +import sys +from decimal import Decimal + +from django.db.models import Q +from django.utils import timezone + + +def get_strategies(): + available_strats = { + i.slug: i + for j, i in inspect.getmembers(sys.modules[__name__], inspect.isclass) + if getattr(i, "slug", None) + } + return available_strats + + +class TradingStrategy: + bot = None + slug = None + + def evaluate_buy(self): + raise NotImplementedError + + def evaluate_sell(self): + raise NotImplementedError + + def evaluate_jump(self): + raise NotImplementedError + + @property + def time_safeguard(self): + return (timezone.now() - self.bot.symbol.last_updated).seconds > 60 + + +class ACMadness(TradingStrategy): + """ + ACMAdness Trading Strategy + + (requires the STP indicator) + """ + + slug = "acmadness" + + def __init__(self, bot, **kwargs): + self.bot = bot + self.ac = Decimal(self.bot.symbol.others["stp"]["next_n_sum"]) + self.microgain = Decimal(kwargs.get("microgain", "0.3")) + self.ac_threshold = self.microgain * Decimal( + kwargs.get("ac_factor", 3) + ) + self.keep_going = bool(int(kwargs.get("keep_going", "0"))) + self.outlier_protection = bool(int(kwargs.get("ol_prot", "1"))) + self.max_var_protection = Decimal(kwargs.get("max_var_prot", "0")) + self.ac_adjusted = Decimal(kwargs.get("ac_adjusted", "1")) + + def evaluate_buy(self): + if self.time_safeguard: + return ( + False, + "Time Safeguard - waiting for next turn...", + ) + if ( + self.outlier_protection + and self.bot.symbol.others["outliers"]["o1"] + ): + return ( + False, + "Outlier Protection - waiting for next turn...", + ) + if ( + self.max_var_protection > 0 + and self.bot.symbol.last_variation > self.max_var_protection + ): + return ( + False, + f"Max Var Protection ({self.bot.symbol.last_variation:.3f} > " + f"{self.max_var_protection:.3f}) - waiting for next turn...", + ) + if self.get_ac() > self.ac_threshold: + return True, None + return ( + False, + f"Current AC {'(adj) ' if self.ac_adjusted else '' }" + f"({self.get_ac():.3f}) is below threshold " + f"({self.ac_threshold:.3f})", + ) + + def evaluate_sell(self): + selling_threshold = Decimal(self.bot.price_buying) * Decimal( + 1 + (self.microgain / 100) + ) + if self.bot.price_current > selling_threshold: + if self.keep_going: # and self.cg: + should_buy, message = self.evaluate_buy() + if should_buy: + return ( + False, + "Kept goin' due to current AC", + ) + return True, None + return ( + False, + f"Current price ({self.bot.price_current:.8f}) is below threshold " + f"({selling_threshold:.8f})", + ) + + def evaluate_jump(self): + if self.time_safeguard: + return False, None + symbols_with_siblings = list( + self.bot.user.bots.filter( + Q( + timestamp_buying__gt=timezone.now() + - timezone.timedelta(hours=1) + ) + | Q( + timestamp_start__gt=timezone.now() + - timezone.timedelta(hours=1) + ), + status__gte=self.bot.Status.INACTIVE, + strategy=self.bot.strategy, + strategy_params=self.bot.strategy_params, + ).values_list("symbol", flat=True) + ) + symbols = self.bot.symbol._meta.concrete_model.objects.top_symbols() + symbols = sorted( + symbols, key=lambda s: s.others["stp"]["next_n_sum"], reverse=True + ) + symbols_blacklist = self.bot.jumpy_blacklist.split(",") + for symbol in symbols: + symbol_ac = Decimal(symbol.others["stp"]["next_n_sum"]) + symbol_ac = ( + symbol_ac * symbol.model_score + if self.ac_adjusted + else symbol_ac + ) + if ( + symbol_ac > self.ac_threshold + and symbol.pk not in symbols_with_siblings + and symbol.symbol not in symbols_blacklist + ): + if self.outlier_protection: + if symbol.others["outliers"]["o1"]: + continue + return True, symbol + return False, None + + def get_ac(self): + if self.ac_adjusted: + return self.ac * self.bot.symbol.model_score + else: + return self.ac diff --git a/base/tasks.py b/base/tasks.py index 1749f3b..670d0ed 100644 --- a/base/tasks.py +++ b/base/tasks.py @@ -1,64 +1,23 @@ -import logging +from celery import shared_task -from django.conf import settings -from django.utils import timezone -from django_rq import get_scheduler, job +from .models import Symbol, TraderoBot -from .models import Symbol -logger = logging.getLogger(__name__) - - -def start_scheduling_round( - job, - time_interval=None, - repeat=None, - meta=None, - queue_alias="default", - *args, - **kwargs, -): - """ - TODO: Ugly solution, works but should be better - """ - should_start_at = timezone.now() - time_interval = time_interval or settings.TIME_INTERVAL - minutes_pending = ( - ((should_start_at.minute // time_interval) * time_interval) - + time_interval - - should_start_at.minute - ) - should_start_at = ( - should_start_at.replace(second=0, microsecond=0) - + timezone.timedelta(minutes=minutes_pending) - + timezone.timedelta(seconds=1) - ) - - scheduler = get_scheduler(queue_alias) - - job_scheduled = scheduler.schedule( - scheduled_time=should_start_at, - func=job, - args=args, - kwargs=kwargs, - interval=time_interval * 60, - repeat=repeat, - meta=meta, - ) - logger.warning( - f"Job {job_scheduled} scheduled at {should_start_at} with " - f"an interval of {time_interval} mins" - ) - - -@job("default", timeout=900) +@shared_task def retrieve_and_update_symbol(symbol): # pragma: no cover symbol.retrieve_and_update() -@job("default", timeout=900) +@shared_task def update_all_indicators_job(): # pragma: no cover if Symbol.objects.available().filter(model_score__isnull=True).count() > 0: # Case of cold start - Symbol.update_all_indicators(push=False) - Symbol.update_all_indicators(only_top=True) + message = Symbol.update_all_indicators(push=False) + message = Symbol.update_all_indicators(only_top=True) + return message + + +@shared_task +def update_all_bots_job(): # pragma: no cover + message = TraderoBot.update_all_bots() + return message diff --git a/base/templates/admin/base/user/change_form.html b/base/templates/admin/base/user/change_form.html new file mode 100644 index 0000000..42dafc9 --- /dev/null +++ b/base/templates/admin/base/user/change_form.html @@ -0,0 +1,7 @@ +{% extends "admin/ai_base/change_form.html" %} +{% load django_ai_tags static i18n %} + +{% block after_related_objects %} +

{% trans "Trades Summary" %}

+ {% include "base/trade_summary.html" with summary=original.trade_summary %} +{% endblock %} diff --git a/base/templates/base/_paginator.html b/base/templates/base/_paginator.html new file mode 100644 index 0000000..0302ffe --- /dev/null +++ b/base/templates/base/_paginator.html @@ -0,0 +1,17 @@ + diff --git a/base/templates/base/base.html b/base/templates/base/base.html index e962f84..58e42c2 100644 --- a/base/templates/base/base.html +++ b/base/templates/base/base.html @@ -4,9 +4,11 @@ {% block bootstrap5_content %} +{% comment %} {% cache None navbar %} -{% include "base/navbar.html" %} {% endcache %} +{% endcomment %} +{% include "base/navbar.html" %}
diff --git a/base/templates/base/bot_snippet.html b/base/templates/base/bot_snippet.html new file mode 100644 index 0000000..89e9607 --- /dev/null +++ b/base/templates/base/bot_snippet.html @@ -0,0 +1,42 @@ +{% load mathfilters symbols %} + +
  • +
    +
    + #{{ bot.id|stringformat:"03d" }}: {{ bot.name }} | + {% with bot.get_status_display as status %} + {% if status == "Selling" %} + {{ status }} + {% elif status == "Buying" %} + {{ status }} + {% elif status == "Inactive" %} + {{ status }} + {% endif %} + {% endwith %} + + {{ bot.symbol.base_asset }}/{{ bot.symbol.quote_asset }} + +
    + + {% if bot.is_dummy %} + DUMMY + {% endif %} + {% if bot.is_jumpy %} + Jumpy + {% endif %} + {% if bot.should_reinvest %} + Reinvest + {% endif %} + {% if bot.should_stop %} + Stop After Selling + {% endif %} +
    {{ bot.strategy|upper }}({{ bot.strategy_params }}) +
    +
    + +

    + {% for log in bot.others.last_logs %} + {{ log }} {% if not forloop.last %}
    {% endif %} + {% endfor %} +

    +
  • diff --git a/base/templates/base/botzinhos.html b/base/templates/base/botzinhos.html new file mode 100644 index 0000000..b9388c5 --- /dev/null +++ b/base/templates/base/botzinhos.html @@ -0,0 +1,170 @@ +{% extends "base/base.html" %} + +{% load bootstrap5 mathfilters symbols %} + +{% block title %} + Botzinhos (小bots) +{% endblock title %} + +{% block content %} +
    +
    + | + + UTC - Time Interval: | {{ time_interval }}m | +
    + +
    + {% for bot in bots %} + {% bot_render_html_snippet bot=bot %} + {% endfor %} +
    + + {% comment "not needed for now" %} +
    + +
    + +
    + + +
    + +
    + + +
    + {% endcomment %} + +
    + +
    + Novo +
    + + {% if user.bots_motto %} +
    +
    {{ user.bots_motto }}
    +
    + {% endif %} +{% endblock content %} + +{% block bootstrap5_extra_script %} + +{% endblock %} diff --git a/base/templates/base/botzinhos_detail.html b/base/templates/base/botzinhos_detail.html new file mode 100644 index 0000000..4513b34 --- /dev/null +++ b/base/templates/base/botzinhos_detail.html @@ -0,0 +1,127 @@ +{% extends "base/base.html" %} + +{% load bootstrap5 mathfilters symbols %} + +{% block title %} + Botzinhos (小bots) +{% endblock title %} + +{% block content %} +
    +
    + | + + UTC - Time Interval: | {{ time_interval }}m | +
    + +
    + {% bot_render_html_snippet bot=bot %} +
    + +

    + {% if bot.fund_quote_asset %} +

    Fund (Quote Assett): {{ bot.fund_quote_asset }} {{ bot.symbol.quote_asset }}

    + {% endif %} + {% if bot.fund_quote_asset_initial %} +

    Fund (Quote Assett) Initial: {{ bot.fund_quote_asset_initial }} {{ bot.symbol.quote_asset }}

    + {% endif %} + {% if bot.fund_base_asset %} +

    Fund (Base Assett): {{ bot.fund_base_asset }} {{ bot.symbol.base_asset }}

    + {% endif %} + {% if bot.price_buying %} +

    Price Buying (NET): {{ bot.price_buying }} | Current Market Price: {{ bot.price_current }} + {% with bot.price_current|div:bot.price_buying|sub:1|mul:100 as var_pc %} + ({{ var_pc|floatformat:4 }}%) + {% endwith %} +

    + {% else %} +

    + Current Market Price: {{ bot.price_current }} +

    + {% endif %} + {% if bot.timestamp_start %} +

    Timestamp - Start: {{ bot.timestamp_start }} ({{ bot.timestamp_start|timesince }} ago)

    + {% endif %} + {% if bot.timestamp_buying %} +

    Timestamp - Buying: {{ bot.timestamp_buying }} ({{ bot.timestamp_buying|timesince }} ago)

    + {% endif %} + {% if bot.receipt_buying %} +

    Receipt - Buying:

    {{ bot.receipt_buying }}

    + {% endif %} + +

    Actions

    +
      +
    • + Update +
    • +
    • + {% if not bot.price_buying %} + Force BUY + {% else %} + Force SELL + {% endif %} +
    • +
    • + {% if bot.status > bot.Status.INACTIVE %} + Turn OFF + {% else %} + Turn ON + {% endif %} +
    • +
    + +
    + + {% include "base/trades_section.html" with summary=summary trades=trades page_obj=page_obj %} + +{% endblock content %} + +{% block bootstrap5_extra_script %} + +{% endblock %} diff --git a/base/templates/base/botzinhos_form.html b/base/templates/base/botzinhos_form.html new file mode 100644 index 0000000..a51223e --- /dev/null +++ b/base/templates/base/botzinhos_form.html @@ -0,0 +1,21 @@ +{% extends "base/base.html" %} + +{% load bootstrap5 %} + +{% block title %} + Botzinhos (小bots) +{% endblock title %} + +{% block content %} +
    +

    {% if form.instance.pk %}Atualizar{% else %}Novo{% endif %} Botzinho

    +
    {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %} +
    + +
    + +{% endblock content %} diff --git a/base/templates/base/instrucoes.html b/base/templates/base/instrucoes.html index 4b401e2..5822306 100644 --- a/base/templates/base/instrucoes.html +++ b/base/templates/base/instrucoes.html @@ -29,7 +29,7 @@

    - To spot favorable situations for this, the MACD/CG indicator was developed: + To spot favorable situations for this, the MACD/CG indicator was developed:

    @@ -37,7 +37,7 @@

    - The full documentation is here and you are welcome to join the community on GitHub, Discord and / or the mailing list. + The full documentation is here and you are welcome to join the community on Discord, GitHub and / or the mailing list.

    @@ -59,5 +59,9 @@

    100, traded and reinvested with a gain of 0.3% in an hour for 1350 hours (168 days / 8 hours a day), is about 5000.

    + +

    + If that could be automated... +

    {% endblock content %} diff --git a/base/templates/base/login.html b/base/templates/base/login.html new file mode 100644 index 0000000..5806067 --- /dev/null +++ b/base/templates/base/login.html @@ -0,0 +1,19 @@ +{% extends "base/base.html" %} + +{% load bootstrap5 %} + +{% block title %} + Entrar +{% endblock title %} + +{% block content %} +
    +
    {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %} +
    +
    + +{% endblock content %} diff --git a/base/templates/base/navbar.html b/base/templates/base/navbar.html index dc9351e..ebe7c71 100644 --- a/base/templates/base/navbar.html +++ b/base/templates/base/navbar.html @@ -11,8 +11,20 @@