From 2d3a86ad45789e9f263284a7d35befca6ad70835 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 28 Dec 2025 21:36:27 +0000 Subject: [PATCH] First Trading with SaaS 2025-12-28 --- config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 5561 bytes config/__pycache__/urls.cpython-311.pyc | Bin 1557 -> 1642 bytes config/settings.py | 1 + config/urls.py | 1 + requirements.txt | 1 + signals/__init__.py | 0 signals/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 160 bytes signals/__pycache__/admin.cpython-311.pyc | Bin 0 -> 215 bytes signals/__pycache__/apps.cpython-311.pyc | Bin 0 -> 533 bytes signals/__pycache__/engine.cpython-311.pyc | Bin 0 -> 6667 bytes signals/__pycache__/models.cpython-311.pyc | Bin 0 -> 2796 bytes signals/__pycache__/urls.cpython-311.pyc | Bin 0 -> 420 bytes signals/__pycache__/views.cpython-311.pyc | Bin 0 -> 545 bytes signals/admin.py | 3 + signals/apps.py | 6 ++ signals/engine.py | 67 ++++++++++++++++++ signals/migrations/0001_initial.py | 47 ++++++++++++ signals/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 2445 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 171 bytes signals/models.py | 36 ++++++++++ signals/tests.py | 3 + signals/urls.py | 9 +++ signals/views.py | 6 ++ trading/__init__.py | 0 trading/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 160 bytes trading/__pycache__/admin.cpython-311.pyc | Bin 0 -> 1960 bytes trading/__pycache__/apps.cpython-311.pyc | Bin 0 -> 719 bytes trading/__pycache__/models.cpython-311.pyc | Bin 0 -> 3712 bytes trading/__pycache__/signals.cpython-311.pyc | Bin 0 -> 1329 bytes trading/admin.py | 26 +++++++ trading/apps.py | 9 +++ trading/migrations/0001_initial.py | 59 +++++++++++++++ trading/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 3396 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 171 bytes trading/models.py | 50 +++++++++++++ trading/signals.py | 16 +++++ trading/tests.py | 3 + trading/views.py | 3 + 40 files changed, 346 insertions(+) create mode 100644 signals/__init__.py create mode 100644 signals/__pycache__/__init__.cpython-311.pyc create mode 100644 signals/__pycache__/admin.cpython-311.pyc create mode 100644 signals/__pycache__/apps.cpython-311.pyc create mode 100644 signals/__pycache__/engine.cpython-311.pyc create mode 100644 signals/__pycache__/models.cpython-311.pyc create mode 100644 signals/__pycache__/urls.cpython-311.pyc create mode 100644 signals/__pycache__/views.cpython-311.pyc create mode 100644 signals/admin.py create mode 100644 signals/apps.py create mode 100644 signals/engine.py create mode 100644 signals/migrations/0001_initial.py create mode 100644 signals/migrations/__init__.py create mode 100644 signals/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 signals/migrations/__pycache__/__init__.cpython-311.pyc create mode 100644 signals/models.py create mode 100644 signals/tests.py create mode 100644 signals/urls.py create mode 100644 signals/views.py create mode 100644 trading/__init__.py create mode 100644 trading/__pycache__/__init__.cpython-311.pyc create mode 100644 trading/__pycache__/admin.cpython-311.pyc create mode 100644 trading/__pycache__/apps.cpython-311.pyc create mode 100644 trading/__pycache__/models.cpython-311.pyc create mode 100644 trading/__pycache__/signals.cpython-311.pyc create mode 100644 trading/admin.py create mode 100644 trading/apps.py create mode 100644 trading/migrations/0001_initial.py create mode 100644 trading/migrations/__init__.py create mode 100644 trading/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 trading/migrations/__pycache__/__init__.cpython-311.pyc create mode 100644 trading/models.py create mode 100644 trading/signals.py create mode 100644 trading/tests.py create mode 100644 trading/views.py diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 5be02db206d695487566bb415cb6b1e287631fb4..6cfabd5f4759435e4d859fd87f3b87f875b124c5 100644 GIT binary patch delta 150 zcmdm>y;GZaIWI340}$Lc56leS$lJxi$T4{a#~jXE?8TYsd5Jm2n@u={nc3ulN{X~L z`|=!TVqCphl;4%f)C44C3M6hZXXcd@nSr<#Ai@$vSb+#@5Mc`>Zm|{@r3dXyE%GwpmlSoe2Ol{3RFw delta 140 zcmdm~y+NCIIWI340}#CB)yQ<&$lJxi$Ub=n$DGZ!oWjg(azKe9&COvv$C(&cZI;#`MJ6jS$}@skqMSUz3@JdY$-6m@@hsD3dlm^srjHDhBU$@7Kd>_MGc|C3 K;F`?EmH+^NW*Bz> diff --git a/config/settings.py b/config/settings.py index 291d043..5c208b3 100644 --- a/config/settings.py +++ b/config/settings.py @@ -56,6 +56,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'core', + 'signals', ] MIDDLEWARE = [ diff --git a/config/urls.py b/config/urls.py index bcfc074..c19926c 100644 --- a/config/urls.py +++ b/config/urls.py @@ -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: diff --git a/requirements.txt b/requirements.txt index e22994c..c494051 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Django==5.2.7 mysqlclient==2.2.7 python-dotenv==1.1.1 +ccxt diff --git a/signals/__init__.py b/signals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/signals/__pycache__/__init__.cpython-311.pyc b/signals/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f10361aa947a964ba16e22f84f839f7385c70e85 GIT binary patch literal 160 zcmZ3^%ge<81lP<1GePuY5CH>>P{wCAAY(d13PUi1CZpd?%JijQrxF9h(RlhhhJufk*SRZVfUP0wA4x8Nkl+v73yCM#t0gOOgEDIz) TFf%eT-e6F;fDIL~0aXA1+`l&% literal 0 HcmV?d00001 diff --git a/signals/__pycache__/apps.cpython-311.pyc b/signals/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc6ca6006a7e73c7c95ee45ab55051365045985c GIT binary patch literal 533 zcmZutJ5R$f5I!dJB#n4X9ryu=rT+kg z_%oeQm6eIDs7#$WCn*wwvwi-~_xZE$p6m4rpuKIKIghx%%V2J$0!$4AL*T%P3m&lu z0R@hMEBC-vL7r5AS6sPS3fJ18mp){ulXPh>3=aao=h)qXk!_?1rVRu`5MU7wc$Ziz zS1gUIsG+6xwqyIujdOv!4SvCVJ1}_1@B%K}*w}aMy`&!;Il|@XVw|&EdMPba>N8JJ znpP-9a^gx{qx33aZjo8#qQ?@qPZ>(kUZ$bR+zU+Q_hzPf9(ck`I*H#;OmQQ+NQ}(u zAi9i0))i*)pvl58Hp1ITb}Vxmg1Cup@H58E?UB|PSJwuIazynxx23CTwWv%AMY5N< yR869cxQu94-pMQ3jaD#}EsRgmO;ke27&b@0=VU=4>l66v9!@^x>DNE0?6GhAyM_}0 literal 0 HcmV?d00001 diff --git a/signals/__pycache__/engine.cpython-311.pyc b/signals/__pycache__/engine.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cec9057b4e83ebb1a65b030d41605fe2b5e2e70 GIT binary patch literal 6667 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 literal 0 HcmV?d00001 diff --git a/signals/__pycache__/views.cpython-311.pyc b/signals/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1073f34870991f079e22f91e313bd5e75b849473 GIT binary patch literal 545 zcmZWmJ5K^Z5Z>K89^trXr-cQHSX{$d84I8=#=?R`jTAHnxXppy19$gC5=R$U+u& zkc&$gBa2vyLtLe#fUZ8Hl4?b+kP#ir6a$CJbMAYWls9~j(=lE+w`}TBRu=Sz*KM!t zR96jTQUs0yXL<_IMMCB*sg2*k`dWLQh>(%%+cvTAE7^>JVMxIsVU)Mritm7&w=@Wo zjhM_glrYBVeL%TjG3ks0aCwtTrDlg_&3CC8R02-~CViyUK={mT`s|K3%2jGE3Uc$I zPMbW_XoU%zisj3g231s9?f{^JM*7}Mv9G5Gdb)Eq(stgCzKgxxEO1}T4YXWO%}pT? z6^uxzS+`}+_A@mh8uK~BDD~_*Y;Hceto(^s5(JZ9H;ZRo-wGU>XWL-PCGiu0iGnd6 WqI7SQhiJdI$rF{}`~>}nd5=F2)_*?$ literal 0 HcmV?d00001 diff --git a/signals/admin.py b/signals/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/signals/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/signals/apps.py b/signals/apps.py new file mode 100644 index 0000000..c819cdb --- /dev/null +++ b/signals/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SignalsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'signals' diff --git a/signals/engine.py b/signals/engine.py new file mode 100644 index 0000000..87e665a --- /dev/null +++ b/signals/engine.py @@ -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' + diff --git a/signals/migrations/0001_initial.py b/signals/migrations/0001_initial.py new file mode 100644 index 0000000..2b050a3 --- /dev/null +++ b/signals/migrations/0001_initial.py @@ -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')), + ], + ), + ] diff --git a/signals/migrations/__init__.py b/signals/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/signals/migrations/__pycache__/0001_initial.cpython-311.pyc b/signals/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e45db15426fd037c8c1d781e209813551095bf13 GIT binary patch literal 2445 zcmb_eO>7fK6rNq%8{2WhIt_%x*iH~am-E?;y zC#Z)UdgzfGIiem?RpihkhaP(DaV+h@niCRIrQD3jiBsR~+NR-0p=!rFZ|42X%=g~R zd-5*>15pI+uj^lG+kFVVScsTbD@!oxoc`1HY<<0v3{BgZZA*$>>nr+64T!Nbvi;o+Ev>Ek!{ z33#v{fmk=*WT4u8#i$Co<+tjo-c0V!Z~~t@NFL9avRDbe>t7{MjN=sKQJO{Wb@OM9 zg%{P*Lx6ZZ6B8D>LKF@`!2^bam;M8{AG3R=ZbbpYaxnE+S> z7QgxyNPUM}7gp}x1xj?CbKfl7UIVIX&LPim5cQQdOszz0i^tYKxom-Jhdh_rsU_1W zYnT`%QYe`gVIkWa4@lt4UZ&++mt1Q4J zuJYKh$kK*t`O|@AIDHL*>a)>R-Bg{9>I;LcnLvD2!d2C%m`ud$EbO6&g3J1r779xX z%PTBuZjw$B{uRB3GtryH+=f{rx%ztDaOyd-O-g{k$~8>up}nb=NUjr@+}rw{o0^)+ zD;?Len>!Y-%O2s6-hKcDXt(!!F*vk)s~r@ErQN&jFp8vqin`%cE1q}}KTSt2Ki+aj zEN<4#OBCuZG=St?&`#m~{y^j_IbP5%+3sg-7G#Z9eHd9@vn3=g(Lu}Jh4 z0&>Jjd+e=WqyNRPp2Ku@qx03bXrj?fG~7gk%G(~SEQghSex9c0o2hv>HBaT6AIIA- zal8?Z7swGO?cuk0&wm2mvvh27@1i?4*&Ms#j$NU0_M^vnmzLv~mV18j_l!Gt$DJ#< zbKlYG4{d}N#Rnn`=L+I0j~;mxB=I$|9SL!uVGb0?5hv~Z#E`UmhySek?jy@cY}9|| zS-hV0|4L-LZ{4~MW-QXvd%5PNm;Xv{@LjyEZo=$M_=cakH?0L=`9A`C9Y{MM2tr#z k!URR*{?|g6=!d?xbV-of=za1y>dF^hamCRGv>t}P0ou=hUjP6A literal 0 HcmV?d00001 diff --git a/signals/migrations/__pycache__/__init__.cpython-311.pyc b/signals/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..216d77b0a6e97232d6a16a34a51c23aa1b4edd9a GIT binary patch literal 171 zcmZ3^%ge<81lP<1GePuY5CH>>P{wCAAY(d13PUi1CZpd&r pyk0@&FAkgB{FKt1RJ$Tppm87zi}``X2WCb_#t#fIqKFwN1^^gYDiZ(z literal 0 HcmV?d00001 diff --git a/signals/models.py b/signals/models.py new file mode 100644 index 0000000..21887ff --- /dev/null +++ b/signals/models.py @@ -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}" \ No newline at end of file diff --git a/signals/tests.py b/signals/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/signals/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/signals/urls.py b/signals/urls.py new file mode 100644 index 0000000..258e3c6 --- /dev/null +++ b/signals/urls.py @@ -0,0 +1,9 @@ + +from django.urls import path +from . import views + +app_name = 'signals' + +urlpatterns = [ + path('signal//', views.get_signal, name='get_signal'), +] diff --git a/signals/views.py b/signals/views.py new file mode 100644 index 0000000..1ba781e --- /dev/null +++ b/signals/views.py @@ -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}) \ No newline at end of file diff --git a/trading/__init__.py b/trading/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/trading/__pycache__/__init__.cpython-311.pyc b/trading/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6094d67b6e3783a2d01a6679390d3779a2ba83c1 GIT binary patch literal 160 zcmZ3^%ge<81RKo)GePuY5CH>>P{wCAAY(d13PUi1CZpdP{uRQ@6CH}=FRWT zvwyBuD}YTbf9d{F0Qg-BdDG6BvmXe+AvoX=4}4-0BKd+>@D)=@dBszG&D4-r9nCBH zC9~w~rtX)`vR^SP1PZVUPH_*M5+z$>`3A=dzzL45nd7OIj#g!=T+mg~Zz8umGIDPC zO|@f;^8`oM2#ol=lFtvnsYa{EcMRCpD3~WzmUoqRELjTZiZ-X z9pyuyU=jz+0#!`aA*SXO%%Y>1B}c_bG%-1}{-=TG2K2Tl<@>1-Zr-ZvLbt5I_Nirw zie>p7C-NjeZdvyt+sibpgxd)XmDCfJwjEf=(2$C$`;@JBIL%hr@gjyNC~a(~@?$uY zQNMh)p?i+Cr?(CKJ=$Rr#>l+-n4~{0g7Fh3fA|K-(Y)o3alZ#3}5RO>G|@ z5VMHTslxau;$g>^0H&#Hwexjw6;N?V`RqQDhvX1;3w$EXlY?`|I8Jb6Z4JU9?@)T5 zw_O;$;73DtxedTB*)5FPt-)i`!Z20O<)P4cv_l!Ip@*2>q@m@e$;KBC?VwG$wB*#= z5G6+MMR=)%(pldUWy@-NHs_YbWlZz)^IdiW8TJN}@7}xsLF2NOWwG;jko?(&#JDy9 zQm#Fnn*DM4r{(?6pBmE-ts|#rEcK10C&tRDvGU98uM0h+)i+wP-pVre8hY1DOs>S_ zcYsYwBCmibWe4GDK%#mcZku|ZWz`8=KpQSmU&+Fj0Hi2LXMP6>MwLH_F&|GXbXNze zqTL|_xC+ezmfN)IlVZZzvV+Y|qumKY=B_iDJMrr1WW$cayN&c>Gl{XNU8YY9i!=F6 z7sYcymK@V1DN7>%1fseY;#Yw>Sutm2l4f1yE>>eD3K-qQ8$p@SbB^YLg;pc#+$1eW5_p1`~DfAxW?l9>Vgn>^C`H`Y;#6TmqV$ojQf7gO49Id$8Dt#TXWU f8=Aj;sr9WM)cR11p*Fn6Cq1~?hnsQUQ(XT7id@1z literal 0 HcmV?d00001 diff --git a/trading/__pycache__/apps.cpython-311.pyc b/trading/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b12213842b1a0e8f873aa238abe86a9a5e4b5cf5 GIT binary patch literal 719 zcmZuuO=}cE5Urk%bmS-&HNQ2M zAsVM3K{5?v7BdD8?1OYpK)Q;3cLC0DHZ69fcL0I^m$m_`ZtoSvi#!{p$*{dqFRa}x zx6n8R1u>I=Z&~b0C-$U!0N&YHT zT2{9v&F$fp1SvTtkx5Yi;hJ35jNk*}O4E}gd1PW*n)NxGz21roT<&+qy7 zd(ZFL-_P&8{wm8M4vt4-pB5hZIPOoZG!Jj1@a#7z+~G7%<8`jc=Xf5=9^F$EastnJ zxbvJQe8*`b;aA0G<9hwqWml>2vMN}q5_(f;gaDe zIDjPY_=+Ws=w&F0i)BLdje)UCYoZ2J?{I|6@fw%&Xnaoaa70{f`Z?l*n!njp^K1(p z(+sfqU@Jl-g67@!bwq_Um|{-S0x-a!BjI>Hp{3=5PLe{>k1iU1xOaLe8) ztynN9cJKJt8ZA6duw*DOH)0YeOo^_VL{kj4NS@%Qb^8&VP*J_1=)|~ct~mbuN~w@1 zWyi0PWwoN4c^)F02YxjCJsSaYhr7kCHa*;GEAB@S19&`5*N@y!LWaBVN#7I(-X2H_ zj#z@_Kh6H*j-L`5f14lBx66;;PULoK$LnZ2Z3T9}_zQXei{NX=#_ zz8gngFi5rpX0AYVjiT%we`+d|nwpuVhp-k*t*F%X_ClQ=0dt?DuVeWz8oP%faimsz~srBB><7G=) ztVxTuv{;?l6?yObyWD=`J|3Uk1^|u#k7qxFIS2qIdH8q_T0+PHvo9k8SwPtK-UDGg zXo({S#6k?Jm>u`PK4_FVE*E58mWS}B=G*q)Ykvly0>Ff|AOI6`e3`;kL)958!t~;2 zjxb$WM{Y8+^YdVdu4C=W)P*cqwO~GZ4Z6M0(+FIi;9Qd)R;E!XXF-%<6@;f4rLPrL z)7oG_^cf6ipwJFO1A*3?=z@ed!_hcCPjG@}p-9T6T3mB{<@MrHNrx+&2TeePON1*7 zRLaeWJISWBk7CRq7+2aOR&*N9yV+kpcS}3jhmDdIm}Xx&os=oIqJ3zP3MZ1ea6UaX z-=<{xCc586^B%^8=Jb+kGTl0%e92fYXvD}9N1lPal7%_Al70sp;6UkdG<&MXF;X-= z@GE}{rcKoeUo{P)j`VL_w&i16aZ4Vq$-}lhT%D`S$K0E-owOyVYI4e!Q*b+Wd^2Ik zj@M#?c5JZvQN3qy(`WY#?hINz3U4d&=&@2dZ0GZK=CM`%=4C6E$%kJz*Au$&DQyJw)2QU3-YK7_r#{ z={aytG0g|G#Wg?B7I3^-NWzf0sRYgGTWE&CWDzl=qU(-#Nmq?)ZPXw!jGBMHgfw_z zIg}!|t~R?eBD0}25jh3H`?CL-6&-`@e-tiw2F4m}9O_`h&FZ1K5+wlT$7G$QbUJ_o zz;yl+ChtSlk%(c^b!CmhI_iYTje_Zx={S0$u6Gal2#aXd86b?et_)4!1JU%uulzfh zJ%;L1A13|2AF^AQx4*Qcb2aIlEuDkapovc#Q}Gjx^F=0g~fQWv$;2D@` z5E1Q01pNTI=tO}@JOpbQH5Cn31t&lZ%`F8#$}FVa#lufY8Mc;X`pTtZ2Kz>H8prt! zn13Sk*bpuUL$%l`J9Y}B8$Gdc-Hx89MThL@Q1!z)EG^4+q`wv!up{upW1kSk|&JXBCh{HWZ`7bcL0?+exuFq;e>)cyb`?)KIc-Z0XH=8HFAAXAWpYJ&j zhk0BUyW5S+x4(GGe9vEw=nL@x^M122cY7ZDAIJV15r+djyvY4#8BU F{{{u)9?1Xz literal 0 HcmV?d00001 diff --git a/trading/__pycache__/signals.cpython-311.pyc b/trading/__pycache__/signals.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..072687e7569c7e5a07e06b42f21c32638ee0d47b GIT binary patch literal 1329 zcmah}&r2IY6rS1L>`oGYRw%K>TBDaxNKi^orBJZf&>}sUi)?nMx-r=eGaF;UgOnb6 z>eXHfo@_<`iT(wR5-=xPpD%t|;9l_Dn8fE;qFFaRAGUb(_8Uk17*xasqpCK&pe<4WIjEgl%rfgR|ZO;fB( ziRE|<<9eyE;njGdQEs??qrkVh)$l!0XnJB()=i5Sd|}#-yIzpax@%TtzWzR#ilVJy z4r}#@2yqW0>M-R~P`l*980!+6SUP7@ZT9etWlvf5J8QA*30rKl#a1j`6+j{K3hoi> zHXoQ|$RbvFk0gPY3WCD1Kj0?z;5yt35o`0rkGl^Ll|h{rh*+S*X9L`R;&#;7{##vE z!>TmNwz^81RNxno(*vVVOQ2ylMP(XU_P8v1{;O?A`eFotVieJ8SS>MzjtNi_GlbfR zNmzqP3@}V}2K>m2v_S2WuBIlII&7lNGG}b&l+B#5**2SP#nL-Z-*2bqLy~XQ{|W;Y z3TxBr_2fAG&WGt4^0n}CLj5sLK^JqWAY)fdciqd|<$TSvIW$MVNd&$i7>|z~=p^6T z5Xf%upzpOj*B4GXZ#Mjm{&j(*2YM7MG1@&7cN_OC3 TL`mI&Y6t~&1C2eQJABL^FsUpX literal 0 HcmV?d00001 diff --git a/trading/admin.py b/trading/admin.py new file mode 100644 index 0000000..b676649 --- /dev/null +++ b/trading/admin.py @@ -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) \ No newline at end of file diff --git a/trading/apps.py b/trading/apps.py new file mode 100644 index 0000000..abf4355 --- /dev/null +++ b/trading/apps.py @@ -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 diff --git a/trading/migrations/0001_initial.py b/trading/migrations/0001_initial.py new file mode 100644 index 0000000..f4687d2 --- /dev/null +++ b/trading/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/trading/migrations/__init__.py b/trading/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/trading/migrations/__pycache__/0001_initial.cpython-311.pyc b/trading/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..827589f54dde0bf3dcd5a7bcd8d4069653151698 GIT binary patch literal 3396 zcmcH*OKcm*b$3ZFKcZyPk*!FSNI6mx({}WYEWs^`$~Gn2ktI2j-I_tMSaC+w%FA75 zcBxMeg%3USm|Jx;dQe+f2On}UiW~y;lH(AVgD|H6J^4n%I^~o%yIjh&VpuNF{k(lM z?=$Z=Z}uOZoe2r9|6ILi{2Q?Uia~Hin#ikVK>j8n3CX5Zl;NwGN+nbb$zm>KhAWX` zq@os8SyH5V35EY6p$Lv_Mgv9rAxU})uYVPzNM4ch>I*Sk%*#CHVAnOQvXfVMqGFVZ z?i#k`aJ6D1Y&xZ;ocBpL2d};l3!AWKPfJHn8zCr zW{`q}bYiokH6tS#g;C@{-G_Ca!s}n_3W|E1$zTS>4&wW5`hh3W&Qq39$KPd?Jm}n4 zKpVU*ZCxH`XE3u5$kSjTfu=Jay(^eP@IclJHJ`=EWj6ZUk! zgMQAVp9$RGfT}dDCjt?{T#%;6y|R+Ual6;#cJV5L1*r8Oi@!hGOr?Q0_f4 zH0Z^pwNGIWUVuIL5gIxeZtq91%lwKv*Q%paR~-7dt>!*?Q*$}=rbp#)@jUtz`rkz! zzZ>+rRj#fWWlJ}?VxT#$+`gF)^Q2yLZLLJG?qb9{s>G=1WLJBHcX`~w#L!LS8_bhi zn5@|j)-1h(x$5jz)@<{I*b>lL6BT_&GqF{6H-O}=kPktZ4PC$g5I}7g@bF4uVF4gE zO)>hz?Cn*6y5YVMT@R+Br48FCVTXv`%M&HrS~n23N|?u8qkT`*@ef+Zun<<$^vqb4L8A@Q(Yi z>@u;}4HNS)u}$m+Dm+IpCS!geLO>_uJ|3Ax6~iJEF!IPRL}SW_NFRK76ybFpyzwIz zt@>(#`mvxU2{uJay^N7hU}`bn!(*B^)-;~bG&uNbCI&pIXR;-iT<)7D)d@4N|SuR_Y3Tz6KM76UGuU?{IIpf**ssas{6 zD`<_!ybQu&&Z9SGS8mMSEbz`H3$NM$nv!-EH_!WjNw2^Z>F4XC8+HYc*4Ap4TN}kY zxCDlg(QTVNa;ka>k2X^`di<*}IyN>ot~H}GQr#uu*TVxbK%6xQ%H!7kQ`gzY^9@ys zU;Jx=#XkLKj9vVFJvPf?vyG6_tv+68B&2vw(6Wc=-k;LvXwRi@w^+}m=iwiRX;GsV zp*>E$$6-AVO}W5BGnef*=Ct-n%X%` z576A$-WJP^)pHXpH$hXAhv|Nro!DDv*@+**G+U@=3oKipskugn+TGQN#N&yV5`ZHC z8r{dHW=>%$EH^2Zo2=)iSZ<1@t{$c{bYOh%0UH>v4@|OwNt*inFx^M{uk6jS{wwwV z5!OFKQ=@M+>oDCzdoS&sXT6u6J^1b+n<}uWMK<+-epO;$IkeZU_qwdtrKwt@Gb$WN z2nPTh3D7uy?7)YoaNw=6&eFm0Cn_5puMbYLLEnp3aG$w94Ab0vJvYyC^E7qqF#Qo7 zo_R9IhG*)-*VwS1t_yT%`pH!`H2oa?*iDBP>qCodXpyFtyi8sZDZcV->$_bxJ z+4LXjpDHwC)iV~$STtq7MdY(>fG#|3lkfb@~ zmI9x?+saE19(fRE(svZk!q0@%XV2B|<80;@o4Lnk9@34k>DHftb6Z~WgqJek5oGqyTKZqe&O1 ziQlv0EeWr^d40*>$6pA&p7OhZ{T%zP-yVNQ?!i#});lhMMo5-r*w*p|Dh>M2VSv0g ebCF8F@-);*S#3xs!M6#P8h$AVN54Y#toj9nF=8eF literal 0 HcmV?d00001 diff --git a/trading/migrations/__pycache__/__init__.cpython-311.pyc b/trading/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20b9a48d1d5be7602e232f707c23120470f54f80 GIT binary patch literal 171 zcmZ3^%ge<81RKo)GePuY5CH>>P{wCAAY(d13PUi1CZpd&r pyk0@&FAkgB{FKt1RJ$Tppm87zi}``X2WCb_#t#fIqKFwN1_1sDDdhkF literal 0 HcmV?d00001 diff --git a/trading/models.py b/trading/models.py new file mode 100644 index 0000000..e6a9f0c --- /dev/null +++ b/trading/models.py @@ -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}' \ No newline at end of file diff --git a/trading/signals.py b/trading/signals.py new file mode 100644 index 0000000..f4951ff --- /dev/null +++ b/trading/signals.py @@ -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) diff --git a/trading/tests.py b/trading/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/trading/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/trading/views.py b/trading/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/trading/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.