diff --git a/Makefile b/Makefile index 277af169..44d25284 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,11 @@ all: setup-testing-environment makemigrations migrate runserver setup: ifeq ($(OS),Darwin) # Mac OS X - ./setup-osx.sh + ./setup/setup-osx.sh else ifeq ($(OS),Linux) - ./setup-unix.sh + ./setup/setup-unix.sh else - ./setup-windows.sh + ./setup/setup-windows.sh endif setup-testing-environment: diff --git a/README.md b/README.md index eb2abb2e..61cc3e3b 100644 --- a/README.md +++ b/README.md @@ -22,47 +22,29 @@

Django Napse ยท - Usefull commands . + Documentation . How to contribute


## django-napse -.... -## Useful commands -Unless otherwise specified, all commands are to be run at the root folder of the project. +Django napse is a portfolio management module using trading bots. +This directory can be used as a django module, which you can install as follows: -### Create a new project -- Unix \ -```source setup-unix.sh``` - -- Windows \ -```.\setup-windows.ps1``` +```bash +pip install django-napse +``` -### Run a test version of the project +Or you can use it as a local backend for the [Napse desktop application](https://github.com/napse-invest/Napse), by cloning the repository. -- Build migrations \ -```make makemigrations``` -- Apply migrations \ -```make migrate``` -- Run server \ -```make runserver``` +Find more details for installation in the [documentation](https://napse-invest.github.io/django-napse/#installation). -### Run coverage tests +### Documentation -- Run tests \ -```test-napse``` -- Run tests with coverage \ -```coverage``` -- Run tests with coverage and open coverage report \ -```coverage-open``` +You can find the documentation [here](https://napse-invest.github.io/django-napse/). -## Documentation -[Docs](https://napse-invest.github.io/django-napse/) +## How to contribute -Run mkdocs server: -``` -make mkdocs -``` \ No newline at end of file +If you want to contribute to the project, please read the [contributing guidelines](https://napse-invest.github.io/django-napse/contributing/) first. You will find the setup instructions and our standards. \ No newline at end of file diff --git a/django_napse/api/fleets/serializers/__init__.py b/django_napse/api/fleets/serializers/__init__.py index b18113f7..deafb8a4 100644 --- a/django_napse/api/fleets/serializers/__init__.py +++ b/django_napse/api/fleets/serializers/__init__.py @@ -1 +1,2 @@ +from .cluster_serialisers import ClusterSerializer from .fleet_serializers import FleetDetailSerializer, FleetSerializer diff --git a/django_napse/api/fleets/serializers/cluster_serialisers.py b/django_napse/api/fleets/serializers/cluster_serialisers.py new file mode 100644 index 00000000..2508c7c1 --- /dev/null +++ b/django_napse/api/fleets/serializers/cluster_serialisers.py @@ -0,0 +1,17 @@ +from rest_framework import serializers + +from django_napse.api.bots.serializers import BotSerializer +from django_napse.core.models import Cluster + + +class ClusterSerializer(serializers.ModelSerializer): + template_bot = BotSerializer() + + class Meta: + model = Cluster + fields = [ + "template_bot", + "share", + "breakpoint", + "autoscale", + ] diff --git a/django_napse/api/fleets/serializers/fleet_serializers.py b/django_napse/api/fleets/serializers/fleet_serializers.py index a2138c8d..d8fa5831 100644 --- a/django_napse/api/fleets/serializers/fleet_serializers.py +++ b/django_napse/api/fleets/serializers/fleet_serializers.py @@ -2,12 +2,18 @@ from rest_framework.fields import empty from django_napse.api.bots.serializers import BotSerializer +from django_napse.api.fleets.serializers.cluster_serialisers import ClusterSerializer from django_napse.core.models import ConnectionWallet, Fleet class FleetSerializer(serializers.ModelSerializer): value = serializers.SerializerMethodField(read_only=True) bot_count = serializers.SerializerMethodField(read_only=True) + clusters = ClusterSerializer( + write_only=True, + many=True, + required=True, + ) class Meta: model = Fleet @@ -20,8 +26,6 @@ class Meta: ] read_only_fields = [ "uuid", - "value", - "bot_count", ] def __init__(self, instance=None, data=empty, space=None, **kwargs): diff --git a/django_napse/api/fleets/views/fleet_view.py b/django_napse/api/fleets/views/fleet_view.py index 72831927..15afa269 100644 --- a/django_napse/api/fleets/views/fleet_view.py +++ b/django_napse/api/fleets/views/fleet_view.py @@ -5,7 +5,7 @@ from django_napse.api.custom_permissions import HasSpace from django_napse.api.custom_viewset import CustomViewSet from django_napse.api.fleets.serializers import FleetDetailSerializer, FleetSerializer -from django_napse.core.models import NapseSpace +from django_napse.core.models import Fleet, NapseSpace class FleetView(CustomViewSet): @@ -13,7 +13,10 @@ class FleetView(CustomViewSet): serializer_class = FleetSerializer def get_queryset(self): - self.space = NapseSpace.objects.get(uuid=self.request.query_params["space"]) + space_uuid = self.request.query_params.get("space", None) + if space_uuid is None: + return Fleet.objects.all() + self.space = NapseSpace.objects.get(uuid=space_uuid) return self.space.fleets def get_serialiser_class(self, *args, **kwargs): @@ -25,6 +28,13 @@ def get_serialiser_class(self, *args, **kwargs): result = actions.get(self.action) return result if result else super().get_serializer_class() + def get_permissions(self): + match self.action: + case "list" | "create": + return [HasAPIKey()] + case _: + return super().get_permissions() + def list(self, request): serializer = self.serializer_class(self.get_queryset(), many=True, space=self.space) return Response(serializer.data, status=status.HTTP_200_OK) @@ -34,6 +44,7 @@ def retrieve(self, request, pk=None): def create(self, request, *args, **kwargs): return Response(status=status.HTTP_501_NOT_IMPLEMENTED) + # serializer = self.serializer_class(data=request.data, space=self.space) def delete(self): return Response(status=status.HTTP_501_NOT_IMPLEMENTED) diff --git a/django_napse/api/spaces/views/space_view.py b/django_napse/api/spaces/views/space_view.py index 4a7da171..c34250cd 100644 --- a/django_napse/api/spaces/views/space_view.py +++ b/django_napse/api/spaces/views/space_view.py @@ -1,13 +1,11 @@ from rest_framework import status -from rest_framework.decorators import action from rest_framework.response import Response from rest_framework_api_key.permissions import HasAPIKey from django_napse.api.custom_permissions import HasFullAccessPermission, HasMasterKey, HasReadPermission from django_napse.api.custom_viewset import CustomViewSet -from django_napse.api.exchanges.serializers.exchange_account_serializer import ExchangeAccountSerializer from django_napse.api.spaces.serializers import SpaceDetailSerializer, SpaceSerializer -from django_napse.core.models import ExchangeAccount, NapseSpace +from django_napse.core.models import NapseSpace from django_napse.utils.errors import SpaceError @@ -38,7 +36,7 @@ def get_permissions(self): return [HasReadPermission()] case "list": return [HasAPIKey()] - case "possible_exchange_accounts" | "create": + case "create": return [HasMasterKey()] case _: @@ -82,11 +80,3 @@ def delete(self, request, *args, **kwargs): except SpaceError.DeleteError: return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) return Response(status=status.HTTP_204_NO_CONTENT) - - @action(detail=False, methods=["GET"]) - def possible_exchange_accounts(self, request): - serialized_exchange_account = ExchangeAccountSerializer(ExchangeAccount.objects.all(), many=True) - return Response( - serialized_exchange_account.data, - status=status.HTTP_200_OK, - ) diff --git a/django_napse/core/models/fleets/cluster.py b/django_napse/core/models/fleets/cluster.py index fbfb026a..b75c17e4 100644 --- a/django_napse/core/models/fleets/cluster.py +++ b/django_napse/core/models/fleets/cluster.py @@ -8,8 +8,16 @@ class Cluster(models.Model): - fleet = models.ForeignKey("Fleet", on_delete=models.CASCADE, related_name="clusters") - template_bot = models.OneToOneField("Bot", on_delete=models.CASCADE, related_name="cluster") + fleet = models.ForeignKey( + "Fleet", + on_delete=models.CASCADE, + related_name="clusters", + ) + template_bot = models.OneToOneField( + "Bot", + on_delete=models.CASCADE, + related_name="cluster", + ) share = models.FloatField() breakpoint = models.FloatField() autoscale = models.BooleanField() diff --git a/django_napse/core/models/fleets/fleet.py b/django_napse/core/models/fleets/fleet.py index 2def824e..c603847d 100644 --- a/django_napse/core/models/fleets/fleet.py +++ b/django_napse/core/models/fleets/fleet.py @@ -11,12 +11,25 @@ class Fleet(models.Model): - uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) - name = models.CharField(max_length=100, default="Fleet") - exchange_account = models.ForeignKey("ExchangeAccount", on_delete=models.CASCADE) + uuid = models.UUIDField( + default=uuid.uuid4, + editable=False, + unique=True, + ) + name = models.CharField( + max_length=100, + default="Fleet", + ) + exchange_account = models.ForeignKey( + "ExchangeAccount", + on_delete=models.CASCADE, + ) running = models.BooleanField(default=False) setup_finished = models.BooleanField(default=False) - created_at = models.DateTimeField(auto_now_add=True, blank=True) + created_at = models.DateTimeField( + auto_now_add=True, + blank=True, + ) objects = FleetManager() diff --git a/django_napse/utils/api_test_case.py b/django_napse/utils/api_test_case.py index ffd8db89..5db965b8 100644 --- a/django_napse/utils/api_test_case.py +++ b/django_napse/utils/api_test_case.py @@ -96,42 +96,42 @@ def run_tests(self, mode): self.check_auth(name="No permissions", mode=mode, error_list=error_list, divider=100, expected=2) else: if HasSpace in permissions: - self.check_auth(name="HasSpace \w no key", mode=mode, error_list=error_list, expected=400) + self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400) self.build_url(kwargs={"space": str("random uuid"), **self.kwargs}) - self.check_auth(name="HasSpace \w no key", mode=mode, error_list=error_list, expected=400) + self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400) self.build_url(kwargs={"space": str("7aafc68d-f619-4874-aaf5-c123a176e303"), **self.kwargs}) - self.check_auth(name="HasSpace \w no key", mode=mode, error_list=error_list, expected=400) + self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400) self.build_url(kwargs={"space": str(self.space.uuid), **self.kwargs}) if HasAPIKey in permissions: - self.check_auth(name="HasAPIKey \w no key", mode=mode, error_list=error_list) + self.check_auth(name="HasAPIKey with no key", mode=mode, error_list=error_list) if HasReadPermission in permissions: - self.check_auth(name="HasReadPermission \w no key", mode=mode, error_list=error_list) + self.check_auth(name="HasReadPermission with no key", mode=mode, error_list=error_list) self.build_key([]) self.authenticate() - self.check_auth(name="HasReadPermission \w no permissions", mode=mode, error_list=error_list) + self.check_auth(name="HasReadPermission with no permissions", mode=mode, error_list=error_list) if HasFullAccessPermission in permissions: - self.check_auth(name="HasFullAccessPermission \w no key", mode=mode, error_list=error_list) + self.check_auth(name="HasFullAccessPermission with no key", mode=mode, error_list=error_list) self.build_key([]) self.authenticate() - self.check_auth(name="HasFullAccessPermission \w no permissions", mode=mode, error_list=error_list) + self.check_auth(name="HasFullAccessPermission with no permissions", mode=mode, error_list=error_list) self.build_key([HasReadPermission]) self.authenticate() - self.check_auth(name="HasFullAccessPermission \w read permissions", mode=mode, error_list=error_list) + self.check_auth(name="HasFullAccessPermission with read permissions", mode=mode, error_list=error_list) if HasAdminPermission in permissions: - self.check_auth(name="HasAdminPermission \w no key", mode=mode, error_list=error_list) + self.check_auth(name="HasAdminPermission with no key", mode=mode, error_list=error_list) self.build_key([]) self.authenticate() - self.check_auth(name="HasAdminPermission \w no permissions", mode=mode, error_list=error_list) + self.check_auth(name="HasAdminPermission with no permissions", mode=mode, error_list=error_list) self.build_key([HasReadPermission]) self.authenticate() - self.check_auth(name="HasAdminPermission \w read permissions", mode=mode, error_list=error_list) + self.check_auth(name="HasAdminPermission with read permissions", mode=mode, error_list=error_list) self.build_key([HasReadPermission, HasFullAccessPermission]) self.authenticate() - self.check_auth(name="HasAdminPermission \w read and full access permissions", mode=mode, error_list=error_list) + self.check_auth(name="HasAdminPermission with read and full access permissions", mode=mode, error_list=error_list) self.build_key(permissions) self.authenticate() diff --git a/docs/contributing.md b/docs/contributing.md index f270ac66..5c67726c 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -64,13 +64,19 @@ You can commit the code from your fork through a pull request on the official re === "Linux" ```bash - source setup-unix.sh + source setup/setup-unix.sh + ``` + +=== "MacOS" + + ```bash + source setup/setup-osx.sh ``` === "Windows" ```powershell - .\setup-windows.ps1 + .\setup\setup-windows.ps1 ``` #### Setup initial exchange accounts @@ -89,9 +95,11 @@ At `tests/test_app/`, build a `secret.json` file (or run the `./setup_secrets.sh } } } - ``` +!!! note + We **strongly recommend** to add the `secret.json` file to your `.gitignore` file to avoid sharing your API keys. + #### Run ```bash diff --git a/docs/index.md b/docs/index.md index 5de2077f..c3f8c100 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,13 +22,19 @@ # Welcome to django-napse's documentation! +Django napse is a portfolio management module using trading bots. + ## Installation +--- + +This project can be used as a django module, which you can install as follows: -To install the latest version of django-napse: ```bash pip install django-napse ``` +Or you can use it as a local backend for the [Napse desktop application](https://github.com/napse-invest/Napse), by cloning the repo (possibly after forking it). + ### Setup initial exchange accounts To make full use of the project, we recommend that you fill in the API keys of at least one exchange (among the django-napse [compabile exchanges](#compatible-exchanges)). @@ -38,7 +44,7 @@ At the root of your project, build a `secret.json` file. Here is an exemple with { "Exchange Accounts": { "Binance EA_NAME": { - "exchange": "BINANCE", # Name of your exchange (BINANCE, DYDX, ...) + "exchange": "BINANCE", # Name of your exchange "testing": true, "public_key": "YOUR_PUBLIC_KEY", "private_key": "YOUR_PRIVATE_KEY" @@ -46,10 +52,27 @@ At the root of your project, build a `secret.json` file. Here is an exemple with } } ``` -We **strongly** recommand you to add the `secret.json` file to your `.gitignore`. - +??? note "Note for developers" + We **strongly recommend** to add the `secret.json` file to your `.gitignore` file to avoid sharing your API keys. ## Use django-napse +--- + +### Local backend + +If you want to use django-napse as a local backend for the Napse desktop application, clone the repository and setup the project: +```bash +make setup +``` + +Then, you can run the server: +```bash +make up +``` + +Please check the documentation for more information about [endpoints](https://napse-invest.github.io/django-napse/api/). + +### Django module After the installation step, in a `.py` file you can use django-napse after importing it: ```python @@ -59,6 +82,7 @@ exchange_account_query = ExchangeAccount.objects.all() ``` ## Miscellaneous +--- ### Compatible exchanges diff --git a/docs/api.md b/docs/sources/api.md similarity index 100% rename from docs/api.md rename to docs/sources/api.md diff --git a/docs/sources/reference/spaces.md b/docs/sources/reference/spaces.md index 725e8635..97e99d40 100644 --- a/docs/sources/reference/spaces.md +++ b/docs/sources/reference/spaces.md @@ -1,8 +1,6 @@ # Space ## Space's Model ::: django_napse.core.models.NapseSpace - options: - --- ## Space's manager diff --git a/docs/theme/assets/stylesheets/docstring.css b/docs/theme/assets/stylesheets/docstring.css deleted file mode 100644 index eb51d691..00000000 --- a/docs/theme/assets/stylesheets/docstring.css +++ /dev/null @@ -1,83 +0,0 @@ -.tile { -display: flex; -text-align: center; -width: 120px; -height: 120px; -display: inline-block; -margin: 10px; -padding: 5px; -border-radius: .5rem; -} - -.tile img { -width: 100px; -} - -.md-typeset__table > table { -max-height: 60vh; -} - -.md-typeset__table > table thead { -position: sticky; -top: 0; -background-color: var(--md-default-bg-color); -} - -.md-typeset__table > table th { -border-bottom: .05rem solid var(--md-typeset-table-color); -} - -.md-typeset__table > table tr:first-child td { -border-top: none; -} - -/* API documentation link admonition */ -:root { ---md-admonition-icon--api: url('data:image/svg+xml;charset=utf-8,') -} -.md-typeset .admonition.api, .md-typeset details.api { -border-color: #448aff; -} -.md-typeset .api > .admonition-title, .md-typeset .api > summary { -background-color: #448aff1a; -} -.md-typeset .api > .admonition-title::before, .md-typeset .api > summary::before { -background-color: #448aff; --webkit-mask-image: var(--md-admonition-icon--api); - mask-image: var(--md-admonition-icon--api); -} - -/* Revert hue value to that of pre mkdocs-material v9.4.0 */ -[data-md-color-scheme="slate"] { ---md-hue: 230; ---md-default-bg-color: hsla(230, 15%, 21%, 1); -} - - - /* Indentation. */ -div.doc-contents:not(.first) { - padding-left: 25px; - border-left: .05rem solid var(--md-typeset-table-color); -} - -/* Mark external links as such. */ -a.external::after, -a.autorefs-external::after { - /* https://primer.style/octicons/arrow-up-right-24 */ - mask-image: url('data:image/svg+xml,'); - -webkit-mask-image: url('data:image/svg+xml,'); - content: ' '; - - display: inline-block; - vertical-align: middle; - position: relative; - - height: 1em; - width: 1em; - background-color: var(--md-typeset-a-color); -} - -a.external:hover::after, -a.autorefs-external:hover::after { - background-color: var(--md-accent-fg-color); -} diff --git a/docs/theme/partials/header.html b/docs/theme/partials/header.html deleted file mode 100644 index d301d733..00000000 --- a/docs/theme/partials/header.html +++ /dev/null @@ -1,63 +0,0 @@ -{#- - This file was automatically generated - do not edit - -#} - {% set class = "md-header" %} - {% if "navigation.tabs.sticky" in features %} - {% set class = class ~ " md-header--shadow md-header--lifted" %} - {% elif "navigation.tabs" not in features %} - {% set class = class ~ " md-header--shadow" %} - {% endif %} -
- - {% if "navigation.tabs.sticky" in features %} - {% if "navigation.tabs" in features %} - {% include "partials/tabs.html" %} - {% endif %} - {% endif %} -
\ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index c719f9cb..b5fc1435 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -167,8 +167,8 @@ nav: - "sources/reference/spaces.md" - "sources/reference/transactions.md" - "sources/reference/wallets.md" - - API: "api.md" + - API: "sources/api.md" - Development: - Contributing: "contributing.md" - Security: "SECURITY.md" - - Coverage: coverage.md \ No newline at end of file + - Coverage: coverage.md diff --git a/setup-osx.sh b/setup/setup-osx.sh similarity index 100% rename from setup-osx.sh rename to setup/setup-osx.sh diff --git a/setup-unix.sh b/setup/setup-unix.sh similarity index 100% rename from setup-unix.sh rename to setup/setup-unix.sh diff --git a/setup-windows.ps1 b/setup/setup-windows.ps1 similarity index 100% rename from setup-windows.ps1 rename to setup/setup-windows.ps1