Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
548 changes: 548 additions & 0 deletions .gitignore

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,39 @@ We want a clean, readable and maintainable code with meaningful comments and doc
3. Push your code to your repository
4. Send us a pull request, we will review and get back to you
5. Enjoy

#----------------------------------------------------------------
# All functions which have been performed in the system
- User is able to sign up , sign in, request to track position(per symbol)
- Authentication has been implemented (JWT token)
- User is able to see list of positions (cache system is applied)
- KuCoin api key & secrets are stored in encrypted format
- The system can handle multiuser requests for tracking
- Swagger has been provided in `/docs` endpoint
- pagination is provided in `/positions` endpoint
- All endpoints must be set **access token** in head as **Authorization** with prefix JWT except for `auth/user`
## All dependencies
- Redis has been applied as message broker and cache system
- Celery has been applied as task queue
- MySQL or Sqlite has been applied as database (default database is `MySQL`)
## Installation
- activate virtualenv
- install all python requirments by `pip install -r requirements.txt`
- migrate database through `python manage.py migrate`
- **check the following fields** in settings.py
1) `CELERY_BROKER_URL`
2) `CELERY_RESULT_BACKEND`
3) `CELERY_TASK_SERIALIZER`
4) `CELERY_RESULT_SERIALIZER`
5) `CELERY_ACCEPT_CONTENT`
6) `CELERY_BEAT_SCHEDULER ,CACHES ,EXPIRE_TIME`
7) `TARGET_SCHEDULE_APP_NAME, INTERVAL`

- Change the database backend in setting.py
- Ensure that the database and redis are installed and available.
- Run these commands in separate shells in the same python virtualenv.
1) `celery -A position beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler`
2) `celery -A position worker -l info -P gevent`
3) in development mode (`python manage.py runserver`), but you are able to use gunicorne or uwsgi
## Admin Panel
- If you register as superuser can access the `PERIODIC TASK`, it allows you to enable or disable every task whatever you want.
Empty file added project/__init__.py
Empty file.
Empty file added project/core/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions project/core/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.contrib import admin
from .models import User
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .forms import UserCreationForm, UserChangeForm


# Register your models here.

@admin.register(User)
class UserAdmin(BaseUserAdmin):
form = UserChangeForm
add_form = UserCreationForm

add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'password1', 'password2', 'email', 'first_name', 'last_name'),
}),
)

list_display = ("username", "email", "first_name", "last_name", "is_staff", "is_active",)
list_filter = ("is_staff", "is_superuser", "is_active", "groups")

6 changes: 6 additions & 0 deletions project/core/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class CoreConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'core'
15 changes: 15 additions & 0 deletions project/core/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import User


class UserCreationForm(UserCreationForm):
class Meta:
model = User
fields = ('email',)


class UserChangeForm(UserChangeForm):
class Meta:
model = User
fields = ('email',)
44 changes: 44 additions & 0 deletions project/core/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.2 on 2023-04-15 10:30

import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 4.2 on 2023-04-15 10:33

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='user',
name='kucoin_api_key',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='user',
name='kucoin_api_secret',
field=models.CharField(blank=True, max_length=255, null=True),
),
]
18 changes: 18 additions & 0 deletions project/core/migrations/0003_user_kucoin_passphrase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2 on 2023-04-15 21:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0002_user_kucoin_api_key_user_kucoin_api_secret'),
]

operations = [
migrations.AddField(
model_name='user',
name='kucoin_passphrase',
field=models.CharField(blank=True, max_length=255, null=True),
),
]
29 changes: 29 additions & 0 deletions project/core/migrations/0004_alter_user_kucoin_api_key_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2 on 2023-04-18 21:30

from django.db import migrations, models
import django_cryptography.fields


class Migration(migrations.Migration):

dependencies = [
('core', '0003_user_kucoin_passphrase'),
]

operations = [
migrations.AlterField(
model_name='user',
name='kucoin_api_key',
field=django_cryptography.fields.encrypt(models.CharField(blank=True, max_length=255, null=True)),
),
migrations.AlterField(
model_name='user',
name='kucoin_api_secret',
field=django_cryptography.fields.encrypt(models.CharField(blank=True, max_length=255, null=True)),
),
migrations.AlterField(
model_name='user',
name='kucoin_passphrase',
field=django_cryptography.fields.encrypt(models.CharField(blank=True, max_length=255, null=True)),
),
]
Empty file.
16 changes: 16 additions & 0 deletions project/core/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import gettext as _
from django_cryptography.fields import encrypt


# provide name, username & kucoin details
class User(AbstractUser):
"""
Custom User model
all KuCoins details have been encrypted and stored in the database.
"""
kucoin_api_key = encrypt(models.CharField(max_length=255, null=True, blank=True))
kucoin_api_secret = encrypt(models.CharField(max_length=255, null=True, blank=True))
kucoin_passphrase = encrypt(models.CharField(max_length=255, blank=True, null=True))
email = models.EmailField(_('email address'), unique=True)
20 changes: 20 additions & 0 deletions project/core/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from djoser.serializers import UserCreateSerializer as BaseUserCreateSerializer, UserSerializer as BaseUserSerializer



class UserCreateSerializer(BaseUserCreateSerializer):
"""
Custom serializer inorder to create a new user based on the custom User data model.
"""
class Meta(BaseUserCreateSerializer.Meta):
fields = ['id', 'first_name', 'last_name', 'email', 'password', 'username',
'kucoin_api_key', 'kucoin_api_secret', 'kucoin_passphrase']


class UserSerializer(BaseUserSerializer):
"""
Custom serializer for representing users infor
(KuCoin details have been excluded)
"""
class Meta(BaseUserSerializer.Meta):
fields = ['id', 'first_name', 'last_name', 'username', 'email']
3 changes: 3 additions & 0 deletions project/core/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
3 changes: 3 additions & 0 deletions project/core/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.shortcuts import render

# Create your views here.
22 changes: 22 additions & 0 deletions project/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)


if __name__ == '__main__':
main()
4 changes: 4 additions & 0 deletions project/position/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .celery import app as celery_app


__all__ = ("celery_app",)
3 changes: 3 additions & 0 deletions project/position/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions project/position/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class PositionConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'position'
13 changes: 13 additions & 0 deletions project/position/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import os
from celery import Celery

# Set the default Django settings module
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')

# Create a Celery instance
app = Celery('project')

app.config_from_object("django.conf:settings", namespace="CELERY")

# Load task modules from all registered Django app configs
app.autodiscover_tasks()
26 changes: 26 additions & 0 deletions project/position/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 4.2 on 2023-04-15 10:43

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Position',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('symbol', models.CharField(max_length=10)),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 4.2 on 2023-04-15 17:20

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('position', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='position',
name='mark_price',
field=models.FloatField(max_length=2, null=True),
),
migrations.AddField(
model_name='position',
name='mark_value',
field=models.FloatField(max_length=2, null=True),
),
migrations.AddField(
model_name='position',
name='rist_limit',
field=models.IntegerField(null=True),
),
migrations.AlterField(
model_name='position',
name='symbol',
field=models.CharField(default='XBTUSDM', max_length=10),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2 on 2023-04-15 23:09

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('position', '0002_position_mark_price_position_mark_value_and_more'),
]

operations = [
migrations.RenameField(
model_name='position',
old_name='rist_limit',
new_name='risk_limit',
),
]
Empty file.
Loading