First Trading with SaaS 2025-12-28
This commit is contained in:
parent
734c24304e
commit
2d3a86ad45
Binary file not shown.
Binary file not shown.
@ -56,6 +56,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'core',
|
||||
'signals',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@ -22,6 +22,7 @@ from django.conf.urls.static import static
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("", include("core.urls")),
|
||||
path("api/", include("signals.urls")),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
Django==5.2.7
|
||||
mysqlclient==2.2.7
|
||||
python-dotenv==1.1.1
|
||||
ccxt
|
||||
|
||||
0
signals/__init__.py
Normal file
0
signals/__init__.py
Normal file
BIN
signals/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
signals/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
signals/__pycache__/admin.cpython-311.pyc
Normal file
BIN
signals/__pycache__/admin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
signals/__pycache__/apps.cpython-311.pyc
Normal file
BIN
signals/__pycache__/apps.cpython-311.pyc
Normal file
Binary file not shown.
BIN
signals/__pycache__/engine.cpython-311.pyc
Normal file
BIN
signals/__pycache__/engine.cpython-311.pyc
Normal file
Binary file not shown.
BIN
signals/__pycache__/models.cpython-311.pyc
Normal file
BIN
signals/__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
BIN
signals/__pycache__/urls.cpython-311.pyc
Normal file
BIN
signals/__pycache__/urls.cpython-311.pyc
Normal file
Binary file not shown.
BIN
signals/__pycache__/views.cpython-311.pyc
Normal file
BIN
signals/__pycache__/views.cpython-311.pyc
Normal file
Binary file not shown.
3
signals/admin.py
Normal file
3
signals/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
signals/apps.py
Normal file
6
signals/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SignalsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'signals'
|
||||
67
signals/engine.py
Normal file
67
signals/engine.py
Normal file
@ -0,0 +1,67 @@
|
||||
|
||||
import ccxt
|
||||
import pandas as pd
|
||||
|
||||
def get_ohlcv(symbol, timeframe):
|
||||
exchange = ccxt.binance()
|
||||
ohlcv = exchange.fetch_ohlcv(symbol, timeframe)
|
||||
df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
||||
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
||||
return df
|
||||
|
||||
def get_trend(df):
|
||||
df['ema20'] = df['close'].ewm(span=20, adjust=False).mean()
|
||||
df['ema50'] = df['close'].ewm(span=50, adjust=False).mean()
|
||||
df['rsi'] = 100 - (100 / (1 + df['close'].diff().apply(lambda x: x if x > 0 else 0).ewm(alpha=1/14, adjust=False).mean() / df['close'].diff().apply(lambda x: abs(x) if x < 0 else 0).ewm(alpha=1/14, adjust=False).mean()))
|
||||
|
||||
last_row = df.iloc[-1]
|
||||
if last_row['ema20'] > last_row['ema50'] and last_row['close'] > last_row['ema20'] and last_row['rsi'] > 50:
|
||||
return 'UP'
|
||||
elif last_row['ema20'] < last_row['ema50'] and last_row['close'] < last_row['ema20'] and last_row['rsi'] < 50:
|
||||
return 'DOWN'
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_setup(df):
|
||||
df['rsi'] = 100 - (100 / (1 + df['close'].diff().apply(lambda x: x if x > 0 else 0).ewm(alpha=1/14, adjust=False).mean() / df['close'].diff().apply(lambda x: abs(x) if x < 0 else 0).ewm(alpha=1/14, adjust=False).mean()))
|
||||
|
||||
last_row = df.iloc[-1]
|
||||
if last_row['rsi'] > 40 and last_row['rsi'] < 60:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_entry(df):
|
||||
df['ema20'] = df['close'].ewm(span=20, adjust=False).mean()
|
||||
df['ema50'] = df['close'].ewm(span=50, adjust=False).mean()
|
||||
df['rsi'] = 100 - (100 / (1 + df['close'].diff().apply(lambda x: x if x > 0 else 0).ewm(alpha=1/14, adjust=False).mean() / df['close'].diff().apply(lambda x: abs(x) if x < 0 else 0).ewm(alpha=1/14, adjust=False).mean()))
|
||||
df['volume_sma20'] = df['volume'].rolling(window=20).mean()
|
||||
|
||||
last_row = df.iloc[-1]
|
||||
if last_row['ema20'] > last_row['ema50'] and last_row['rsi'] > 50 and last_row['close'] > last_row['ema20'] and last_row['volume'] > last_row['volume_sma20']:
|
||||
return 'BUY'
|
||||
elif last_row['ema20'] < last_row['ema50'] and last_row['rsi'] < 50 and last_row['close'] < last_row['ema20'] and last_row['volume'] > last_row['volume_sma20']:
|
||||
return 'SELL'
|
||||
else:
|
||||
return 'WAIT'
|
||||
|
||||
def generate_signal(symbol):
|
||||
# 1h trend
|
||||
df_1h = get_ohlcv(symbol, '1h')
|
||||
trend = get_trend(df_1h)
|
||||
|
||||
if trend:
|
||||
# 15m setup
|
||||
df_15m = get_ohlcv(symbol, '15m')
|
||||
setup = get_setup(df_15m)
|
||||
|
||||
if setup:
|
||||
# 5m entry
|
||||
df_5m = get_ohlcv(symbol, '5m')
|
||||
entry = get_entry(df_5m)
|
||||
|
||||
if (trend == 'UP' and entry == 'BUY') or (trend == 'DOWN' and entry == 'SELL'):
|
||||
return entry
|
||||
|
||||
return 'WAIT'
|
||||
|
||||
47
signals/migrations/0001_initial.py
Normal file
47
signals/migrations/0001_initial.py
Normal file
@ -0,0 +1,47 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-28 14:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Backtest',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('symbol', models.CharField(max_length=20)),
|
||||
('start_date', models.DateTimeField()),
|
||||
('end_date', models.DateTimeField()),
|
||||
('profit_loss', models.FloatField()),
|
||||
('win_rate', models.FloatField()),
|
||||
('max_drawdown', models.FloatField()),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Signal',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('symbol', models.CharField(max_length=20)),
|
||||
('signal', models.CharField(choices=[('BUY', 'Buy'), ('SELL', 'Sell'), ('WAIT', 'Wait')], max_length=4)),
|
||||
('timeframe', models.CharField(max_length=10)),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ConfidenceScore',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('score', models.FloatField()),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('signal', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='confidence_score', to='signals.signal')),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
signals/migrations/__init__.py
Normal file
0
signals/migrations/__init__.py
Normal file
BIN
signals/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
signals/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
BIN
signals/migrations/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
signals/migrations/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
36
signals/models.py
Normal file
36
signals/models.py
Normal file
@ -0,0 +1,36 @@
|
||||
from django.db import models
|
||||
|
||||
class Signal(models.Model):
|
||||
SIGNAL_CHOICES = [
|
||||
('BUY', 'Buy'),
|
||||
('SELL', 'Sell'),
|
||||
('WAIT', 'Wait'),
|
||||
]
|
||||
|
||||
symbol = models.CharField(max_length=20)
|
||||
signal = models.CharField(max_length=4, choices=SIGNAL_CHOICES)
|
||||
timeframe = models.CharField(max_length=10)
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.symbol} - {self.signal} ({self.timeframe})"
|
||||
|
||||
class ConfidenceScore(models.Model):
|
||||
signal = models.OneToOneField(Signal, on_delete=models.CASCADE, related_name='confidence_score')
|
||||
score = models.FloatField()
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.signal} - {self.score}"
|
||||
|
||||
class Backtest(models.Model):
|
||||
symbol = models.CharField(max_length=20)
|
||||
start_date = models.DateTimeField()
|
||||
end_date = models.DateTimeField()
|
||||
profit_loss = models.FloatField()
|
||||
win_rate = models.FloatField()
|
||||
max_drawdown = models.FloatField()
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Backtest for {self.symbol} from {self.start_date} to {self.end_date}"
|
||||
3
signals/tests.py
Normal file
3
signals/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
9
signals/urls.py
Normal file
9
signals/urls.py
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'signals'
|
||||
|
||||
urlpatterns = [
|
||||
path('signal/<str:symbol>/', views.get_signal, name='get_signal'),
|
||||
]
|
||||
6
signals/views.py
Normal file
6
signals/views.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.http import JsonResponse
|
||||
from .engine import generate_signal
|
||||
|
||||
def get_signal(request, symbol):
|
||||
signal = generate_signal(symbol)
|
||||
return JsonResponse({'symbol': symbol, 'signal': signal})
|
||||
0
trading/__init__.py
Normal file
0
trading/__init__.py
Normal file
BIN
trading/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
trading/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
trading/__pycache__/admin.cpython-311.pyc
Normal file
BIN
trading/__pycache__/admin.cpython-311.pyc
Normal file
Binary file not shown.
BIN
trading/__pycache__/apps.cpython-311.pyc
Normal file
BIN
trading/__pycache__/apps.cpython-311.pyc
Normal file
Binary file not shown.
BIN
trading/__pycache__/models.cpython-311.pyc
Normal file
BIN
trading/__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
BIN
trading/__pycache__/signals.cpython-311.pyc
Normal file
BIN
trading/__pycache__/signals.cpython-311.pyc
Normal file
Binary file not shown.
26
trading/admin.py
Normal file
26
trading/admin.py
Normal file
@ -0,0 +1,26 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.contrib.auth.models import User
|
||||
from .models import UserProfile, Signal, Trade, Backtest
|
||||
|
||||
class UserProfileInline(admin.StackedInline):
|
||||
model = UserProfile
|
||||
can_delete = False
|
||||
verbose_name_plural = 'Profile'
|
||||
fk_name = 'user'
|
||||
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
inlines = (UserProfileInline, )
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
if not obj:
|
||||
return list()
|
||||
return super(CustomUserAdmin, self).get_inline_instances(request, obj)
|
||||
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, CustomUserAdmin)
|
||||
|
||||
admin.site.register(Signal)
|
||||
admin.site.register(Trade)
|
||||
admin.site.register(Backtest)
|
||||
9
trading/apps.py
Normal file
9
trading/apps.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TradingConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'trading'
|
||||
|
||||
def ready(self):
|
||||
import trading.signals
|
||||
59
trading/migrations/0001_initial.py
Normal file
59
trading/migrations/0001_initial.py
Normal file
@ -0,0 +1,59 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-28 13:55
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Signal',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('symbol', models.CharField(max_length=20)),
|
||||
('signal', models.CharField(choices=[('BUY', 'Buy'), ('SELL', 'Sell'), ('WAIT', 'Wait')], max_length=4)),
|
||||
('confidence', models.FloatField()),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Backtest',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('start_date', models.DateTimeField()),
|
||||
('end_date', models.DateTimeField()),
|
||||
('results', models.JSONField()),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Trade',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('symbol', models.CharField(max_length=20)),
|
||||
('entry_price', models.FloatField()),
|
||||
('exit_price', models.FloatField(blank=True, null=True)),
|
||||
('entry_timestamp', models.DateTimeField()),
|
||||
('exit_timestamp', models.DateTimeField(blank=True, null=True)),
|
||||
('signal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='trading.signal')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserProfile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('role', models.CharField(choices=[('Free', 'Free'), ('Pro', 'Pro'), ('Admin', 'Admin')], default='Free', max_length=10)),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
trading/migrations/__init__.py
Normal file
0
trading/migrations/__init__.py
Normal file
BIN
trading/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
trading/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
BIN
trading/migrations/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
trading/migrations/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
50
trading/models.py
Normal file
50
trading/models.py
Normal file
@ -0,0 +1,50 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class UserProfile(models.Model):
|
||||
USER_ROLE_CHOICES = (
|
||||
('Free', 'Free'),
|
||||
('Pro', 'Pro'),
|
||||
('Admin', 'Admin'),
|
||||
)
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
||||
role = models.CharField(max_length=10, choices=USER_ROLE_CHOICES, default='Free')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} - {self.role}'
|
||||
|
||||
class Signal(models.Model):
|
||||
SIGNAL_CHOICES = (
|
||||
('BUY', 'Buy'),
|
||||
('SELL', 'Sell'),
|
||||
('WAIT', 'Wait'),
|
||||
)
|
||||
symbol = models.CharField(max_length=20)
|
||||
signal = models.CharField(max_length=4, choices=SIGNAL_CHOICES)
|
||||
confidence = models.FloatField()
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.timestamp} - {self.symbol} - {self.signal}'
|
||||
|
||||
class Trade(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
symbol = models.CharField(max_length=20)
|
||||
entry_price = models.FloatField()
|
||||
exit_price = models.FloatField(null=True, blank=True)
|
||||
entry_timestamp = models.DateTimeField()
|
||||
exit_timestamp = models.DateTimeField(null=True, blank=True)
|
||||
signal = models.ForeignKey(Signal, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} - {self.symbol}'
|
||||
|
||||
class Backtest(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
start_date = models.DateTimeField()
|
||||
end_date = models.DateTimeField()
|
||||
results = models.JSONField()
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.user.username} - {self.timestamp}'
|
||||
16
trading/signals.py
Normal file
16
trading/signals.py
Normal file
@ -0,0 +1,16 @@
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.contrib.auth.models import User
|
||||
from .models import UserProfile
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
UserProfile.objects.create(user=instance)
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def save_user_profile(sender, instance, **kwargs):
|
||||
try:
|
||||
instance.profile.save()
|
||||
except UserProfile.DoesNotExist:
|
||||
UserProfile.objects.create(user=instance)
|
||||
3
trading/tests.py
Normal file
3
trading/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
trading/views.py
Normal file
3
trading/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Loading…
x
Reference in New Issue
Block a user