From 53ad1abb78ee82c101e18e84a240bf81036df9b4 Mon Sep 17 00:00:00 2001 From: Narayanan Madaswamy Date: Wed, 5 Aug 2026 23:51:18 +0530 Subject: [PATCH] Customize - Stock map list, watch list, home page --- Dockerfile.frontend | 21 ++ backend/Dockerfile.backend | 14 + .../app/api/__pycache__/auth.cpython-313.pyc | Bin 6041 -> 6752 bytes .../api/__pycache__/scanner.cpython-313.pyc | Bin 1635 -> 3693 bytes backend/app/api/auth.py | 12 +- backend/app/api/scanner.py | 40 ++- .../core/__pycache__/config.cpython-313.pyc | Bin 411 -> 481 bytes backend/app/core/config.py | 2 +- .../app/db/__pycache__/models.cpython-313.pyc | Bin 871 -> 929 bytes backend/app/db/models.py | 3 +- .../__pycache__/stock_engine.cpython-313.pyc | Bin 20347 -> 20457 bytes backend/app/services/stock_engine.py | 26 +- backend/requirements.txt | 114 ++++++ docker-compose.yml | 19 + nginx.conf | 10 + package.json | 1 + src/App.js | 22 +- src/components/AboutScanner.js | 159 +++++---- src/components/Backtest.js | 324 +++++++++--------- src/components/Chart.js | 296 ++++++++-------- src/components/ChartPage.js | 40 +-- src/components/Login.js | 4 +- src/components/LongTerm.js | 228 ++++++------ src/components/MainDashboard.js | 155 +++++++++ src/components/MarketIntel.js | 2 +- src/components/Popup.js | 42 +-- src/components/Profile.js | 4 +- src/components/Ribbon.js | 35 +- src/components/Scanner.js | 70 +++- src/components/Signup.js | 4 +- src/components/SmartMoney.js | 43 ++- src/components/TrendScanner.js | 37 +- src/components/WatchlistManager.js | 279 +++++++++++++++ src/index.css | 4 +- 34 files changed, 1423 insertions(+), 587 deletions(-) create mode 100644 Dockerfile.frontend create mode 100644 backend/Dockerfile.backend create mode 100644 backend/requirements.txt create mode 100644 docker-compose.yml create mode 100644 nginx.conf create mode 100644 src/components/MainDashboard.js create mode 100644 src/components/WatchlistManager.js diff --git a/Dockerfile.frontend b/Dockerfile.frontend new file mode 100644 index 0000000..70331a5 --- /dev/null +++ b/Dockerfile.frontend @@ -0,0 +1,21 @@ +# Build stage +FROM node:18-alpine AS build + +WORKDIR /app +COPY package*.json ./ +RUN npm install + +COPY . . +RUN npm run build + +# Production stage +FROM nginx:alpine + +# Copy built assets +COPY --from=build /app/build /usr/share/nginx/html + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/backend/Dockerfile.backend b/backend/Dockerfile.backend new file mode 100644 index 0000000..5131562 --- /dev/null +++ b/backend/Dockerfile.backend @@ -0,0 +1,14 @@ +FROM python:3.13-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y git cmake g++ && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/app/api/__pycache__/auth.cpython-313.pyc b/backend/app/api/__pycache__/auth.cpython-313.pyc index df4a7b4c37355ebc73b8b054cbc993a856217bd9..0532e8540d0143da2aa5988f860e6a3dfb5226d6 100644 GIT binary patch delta 1596 zcmZ{kOKcle6o&82cqXou_&Ij`h+narbXwf-NEF(XSE*Z3B|*)oKw%Qgc!o3~am*dN zs1eBNszO47>K3VG(@i(sAR#Zx0wh>~*Z_`#NN_h@u&KntRU)ydkT_??iL8i>G~a!k zbAF#Y=Um?&c{u9c?(1_Ac)oqlT>Qa%!yV@SzA^kj3zI>jdZR@3X|6E=9SEUGEjIIt zJ@2OKALHz`8iluS2DP{v(ww6XwLeN09gEyvWafa#Fh<6^ zBP07Fv+QRsS&wRmJIXQJPKOT2d|)?ojUOc1(C%uKQ{$R%lvfk`^oK#`!vCBpxf_W( zyfy@hM)E-|i859ylA78lr5qrY?v}EZGC?iTr8Kxt>F9h$$u{ot-wFMt?%X)V_~^ip)uH) zR(j|Fj1;Xjfk*;c4t=3irDy#4~}_90$GL=Z0_M1%(sMR*Yt2tPm(>0Uzg zH158NI1ex}FolnG)RwrkGQUu!ldy}&F+T^ak%w;YXO};@e6#Um?^#|t0ekc$^Lb+2 zIhON0b@Ex<2q0uYOPDvR6mKLwC|yOOQ)nM!%yU6}6Q(b-N1ngjJut(PZ+{B-f~Eao zmiGsl(|5Xlk<3_^91LBL${m-oMB%6JN%qG$0!FGJjv|gBMi6fU6pp?HgNawYb6yE@ zEi4=K9oSOQb_TFU+7bz-H)UmAR<^>~&G0kqD_`QwR%mcDlv@ww9{2V1N^R03N>=?> zJtH-%`Equ@nWf#n1u??IYaI$dDjq5;@G}Ct=8ncvMCDZeX%3DcOw&_fUmdXf*<^PD zp*MItcl^Ur1iOZK0IRc1)ZD9^+fJa+Nr29RaSmQH1Aqc1(s#$3)=9o8jYa a9KX@6p{c*WgG-c2wt1MfUF`M#^Zx-ik2U51 delta 857 zcmZ{i%Wl&^6ox%^>{c#Lotx`8Nt~RH#)K0cwE)vS5LdNP$N3hd~+@{|D0pbH|ur5KU5T%V`Fg7 zZLQ7j`*reU|K#T-C$_zY;iJP=OJo$W0|r+*OwuIVy$*2_hb9Ruhn$2RcBU<#9Wl6; zuSNduOXI$yis;!I+#%7}zjJdF=Q8k>EbQq{_E0xE%G2C2RT|?e9X`!Dxv^&vJLUu| z-i}Y`FEX90|EVi6K8ZTKlY^&xG!t^>QN}ApvgaqHPVFq1$&N0(7vRm*OEJ$>6P@z( z9xoM`NsI7SPLVQvkr(AE3#8a|pTj`0I+td8ge*Z`iIQd5R4TQzctAm%LqriP2oW)h zFc2!Dj0iHAA|2g?fmOss#1;6Y7~%#qi=CcQ=OqiHCD>A96~4P#Y4y*akc9+BPzsh2TJcxgINT{54hhO;!2LXZUNY@LQ_#ThH*Vudl*i zQKdS;JOt(BIDM1ofx;i6!c$gRf`iB0Y{POk!o$ca)qez%8cDe#zEipB4OC0s0% zfhVPD59@)DseIF}zu@l8yL$`nEBj%^a#d!o7L_+}njgMwy-ij1 z)@$-Hx#kO%Dm!)-3XT4JqrcGDw@)jUewpbns%!;+Jc*Yxij!E-;)Gm4Wt^1LV+SgF z0EJ0*|K88HR2+=mfFPMzB)&`B)~#5YWv02P1j~th498bgY9n45g5(EjDjdNo)CMX- z2PuJ$=%UDuB22$d(9Hv+NG}TBcJz8*M1PGIm?#r2SDG>dyd#F8*AbecVi3cCAf9}V z%Ek)%s6I?|IY$weLxUxq1w+)(fPRKVnuDhSP5Ti_qZe`+Bc?J}nEcLj6fu{ROl9(! zy$o8S+w^RJ2nm*pC~6Lq;6F!EOHnS79VU@}js$De`of#C zm!(uBVn~8sR0V%hn?Rzy3#3%R)t1i%TrI)PE*0-1%61?Qb-?WwZ#Y>oYeVxSmubYJ zm=7$r&67yeC{kCp61=lMYO|yMliq@KC}oYbnI^LpRt4LrZT3F=Gi{cnl4w}hijtJC zVJ>GTC8K9_8H!@gGMmn&*4AR&U?7+qT#=-VIK*)qSsrip1(GQN${ZiqjIE^8fp~hI zd$X4-EtZ1o#>Q~Y)47pNOT0Lq&I;0SZ?IF~-zt3sp$M}pd@KPq>e&;pM>Q0&szKab zUrMj3kn3~0z+41|v0Klym?w^}Qr?q#cN?eTyjqX>axMnJ5gYj4uqBj&*vQ4&=hlyTJ}N--%bF2>UdUWLvPD!-BR3U8&P71g*| zvQ1@wnu3anMS!GJg33ze&y)-eP+P@>_&HkKUP?z{GG z%jpS9c~3{K1FQd(->BQ2Y}GfAR_Q7vrCe@=thWI=M5s8^}9+2fm(%u07X& zx8fUtrEbO1`P_uuT}KX7+aepA4=lE=o8NuYg_;(q@4hjk3)lbiZ7p)O9V1;`;7do- z?yU!t`=tYd*7wv;tXlCgUsX=|pyoAHie54UU9FGmJ+%ilY9GI{$`z|E6h*{uhGb z*odgwr>6S%JX?mZDE3|VJMQf%g>oFaT4irgarGV}it0bsA-eI1hS`w;(MAn*Q=y!P z?l!r-PjUAX)F44!AgIAp)T~0e4=?^u?);H*@d`m*BPb6+T{}fhD^%lQlUHu*RhpnF zrKkadauU?QDQZrkJcq5Fa@Q55^(sNN5L7ckwVa|fz1n^9hqf|={%~o4$X+cY&UtU;#~Hk|r}*3$maVVxb%<(Rs3R z<%%gKWhd1SFnkxq6^MaPNth?4!R`?)`qSiq4)_rde!!D2jqt}j__8NIIKq#5@I#(_ zWrRQO!4G@#BP0BY{jq%XHx?={IKk)J8#~RmsWV4&=FK>;!Rd0saS11fx>;+O`q7DBMSA6` zOPBxwy$~OaUL2+~@^*06@77&%PPg)u1>pRgI^pX{8Hmm~>*^cG&`|5;YV}y%X3Yb= zVj8MpHYR!2W2uZ2sRtrXrX9D&@>(~(y!^Xi^5Qc$bJ|#bu$<9+tjP+ng zP9)X6?*Hb_!oY^?C3vw2Om5073QH&8`ec)^;b;BGvCf($CfEenPZZS{6!*5#WpH#O zdT8jE#Q=cO35b^hHOCBpB{&9`Puoh?60LNe{nWHvr8t=5&#}G#+$6(j^&>){G#?XlYdLkM5 z#U*+v`N`R-MS7WeC8s3|fv%pBWegoR}JTZtxm6aNXb$Zs5Ejr*M%$x`+p; F2LKaGHl_do delta 179 zcmaFJJexV-GcPX}0}wn*D9*YDq#uJgFu(+5e3k(+rZNOG1T%Uw6fp!d6)^@g7cm91 z6fp<07O|u=YO=iqDfO!gnAo7lZ)jkwXJ}*!#Fl!7hGr8Fxe2r0V#+VR#pB`_;^^cU z>>3{$5BL)scy90-H*nqH5pLkTA*XPWLAr<= Gr~&}*#3uj% diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 3aa511d..e0adb94 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,6 +1,6 @@ import os -DATABASE_URL = "postgresql://postgres:M%40triXPostgr3s%406202@103.125.129.116:5333/stock_scanner" +DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:M%40triXPostgr3s%406202@host.docker.internal:5333/stock_scanner") SECRET_KEY = "stock-scanner-super-secret-key-12345" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 60 diff --git a/backend/app/db/__pycache__/models.cpython-313.pyc b/backend/app/db/__pycache__/models.cpython-313.pyc index 7491bf3e407689fd96fb634e47a6b0ce42df9165..628655fe8d9aa1a227a736436d9de21aa07c7d77 100644 GIT binary patch delta 347 zcmaFPwve6oGcPX}0}xcC6=(gO$Sdh>2jom;2xbUo^k(p4EMoLxDq`|tE@Jj#DPmCo zi8Fb#da)I;DKJPdC@};xOECnq#ITt%gA8F{C}K}%)nt1KVrsHX%#~!j#o`t0?>Dhc zjD^dn>8G*Q13P^llW@Kc%&tP(y!Q?K3!WR}UM%@mRuM9v6tQP=WicHf0 delta 307 zcmZ3;{+x~XGcPX}0}xzGF3vhNkyp~)3dott5X=zF=*{58Sj6bXRK(=PT*RyZ5@Yga z@nS7vRbY@{P+|yXmSPBIiD5Nm1{uJ>P{fwbs>${e#MES-SSUGhf*fPR#Pzi-!R$r+ zlkFHSM0r3`jJG)QN^^1&lX6m5GWcl$Nm)-4X3-29Z%oK(9aMIe_Eh>N9w#0O?ZM#lRLdKVe=?lLHRVc}%d M?lAev0HnZr0r7W5=l}o! diff --git a/backend/app/db/models.py b/backend/app/db/models.py index 3ae74a9..61bf8a4 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, String +from sqlalchemy import Column, Integer, String, JSON from app.db.database import Base class User(Base): @@ -11,3 +11,4 @@ class User(Base): email_id = Column(String, nullable=True) mobile_no = Column(String, nullable=True) gender = Column(String, nullable=True) + sector_map = Column(JSON, nullable=True) diff --git a/backend/app/services/__pycache__/stock_engine.cpython-313.pyc b/backend/app/services/__pycache__/stock_engine.cpython-313.pyc index 2136e4cb4051d5ccc2eb08aec448519c58043b69..85e78cf6a433c46f2161c64598c724cb069b14ba 100644 GIT binary patch delta 1610 zcmc&!O-vI(7@gU6XSXdZNEf2(4-1NlM%zMZ^ipjR6|@L$){t-@N(=trYGz9`F)@u^ zFvehgci1(1#`(ITd}B%=#5#A~tYIzdB*$M9|!bXYJ%ejymiR-q@jfyV-iMARBn zbWhh&>ij}gQ-J+#Qqg|FK!)r^mShU>)`r2bc^Ia zL{4BVvHs@GRlb>uVLL`Z_56zw~C_%s`Bj*C9HjLs&SBW1H%<0~yRp@k;32vedt z+{8wsNBqQ}+(8N|N$$x1h<=jRmXw7!vVbJmm1GkTm_cY5Xgj_3&&mkBGJ6p|sKGAK z+7f#j41x2Mg8Ol*O?)k;vU8F0lY{QUuh$d@P@|`l?tygr6rhpN2RwerfqQ~J;_#Bg zV}RR)K7nCIq8WR5C~G^L=zae98HalSHUZFemWbD`I16D8%_^0-J;fYdQrMYzH2r#6 z^+;k$(?^b8-uKmms%mECic#f~ay2_CulgE5E1SM3s4ST4{5}^zmFw*?s8;a$6LC_g zB7prR3GMu6g!+CF3XZI9_ki!bhUoYfFL4_-2zN1en+e^JynK|4{Kkdwxk9k%)+_As z&Tibo)I>TYmDJ=86m&G91s+WTEHKXsiIoj+VY(3vWRF2mQh^cjhT&wS{?8(?)?{*f zU6BCEdM*H9X#tx4lK@+a1Tct8bQmvjyIe%_E8NA#cJ;LV=1%90t_IF#e1n;6UzTR9 zG<^s2U7XBYe}T-=wwSuQm`v3nQ_WAeee*1P&yV&F9kWi*j}X$&Y_|O(p3kQ{ZedXc zV$*qL{{)wa=z9n9&r~qx2Gx_@4US{fP-_IhRNKv8cgPQ;wbjh HTFChcs*ypl delta 1642 zcmdUuOH30{6o${8na)h751<9qBH9E*K?Q1oxG+dTM2$S84k30ys#K%mXzmnOMo~AK zXf(OH^tEuIaiMmliH~ZcCN2z#0n!y16Qc`)U`UJ`&z&+A(HLENk~uy1b*A%w|NVH4 ze7j1#Gaioy7=gHX;8E8-Z$oVjAKIZ{A>uQ0kpNtVS}>pn3=*@~p<86b`GImjY&ixn zC;%KOi#hDo7(Z@Bl++V;!xa!>8auPGyKvRqd_SIO?*h=JZV#Kt^Rvc~dyy1%@D{oh zm8*qbVI{9gcOHv}{LIxQC( zF6&CvSuK`>DFpE`9DAWs>!5A;0(N&@ZK`x#hTBJkvKqJNA}8Y%PF>~_ z!a@myC}9t9ZT}$O&X@&@_ti-~YR97<(eN+?UGPJXXbLs>aoElSa5!uWVkzyyb7Gg! zo|je75DZZV))y)|A=q7l(B3tdnc^ABAy%&=s>e9~&_$#I~aQlZkzD_HUUtzlOcv{vXVL4qW zW8Q<~T;vFBu#EgW0^G#->6VFoe@j95g@UMyNJ09E!lqv+Nb@L&hLoki9&T~D+jrUW zqvhwW#CotBSiXiIzSaS~FH3;G$~@k!PAn9nt7= G-18m#r7|o4 diff --git a/backend/app/services/stock_engine.py b/backend/app/services/stock_engine.py index 83ed439..bd2ef7e 100644 --- a/backend/app/services/stock_engine.py +++ b/backend/app/services/stock_engine.py @@ -425,12 +425,12 @@ def analyze(stock): # ========================================= # ✅ API - FAST SCAN # ========================================= -def get_scan(): +def get_scan(user_sector_map): sector_output = {} all_stocks = [] - for sector, stocks in sector_map.items(): + for sector, stocks in user_sector_map.items(): data = list(ThreadPoolExecutor(5).map(analyze, stocks)) data = [x for x in data if x] @@ -536,26 +536,28 @@ def analyze_ribbon(stock): return None -def get_ribbon(): +def get_ribbon(user_sector_map): results = [] - for stocks in sector_map.values(): + for sector, stocks in user_sector_map.items(): data = list(ThreadPoolExecutor(5).map(analyze_ribbon, stocks)) data = [x for x in data if x] + for x in data: + x["sector"] = sector results.extend(data) return results -def get_smartmoney(): +def get_smartmoney(user_sector_map): results = [] - for sector, stocks in sector_map.items(): - + for sector, stocks in user_sector_map.items(): data = list(ThreadPoolExecutor(5).map(analyze, stocks)) data = [x for x in data if x] - + for x in data: + x["sector"] = sector results.extend(data) @@ -575,15 +577,15 @@ def get_smartmoney(): "all": results } -def get_trend(): +def get_trend(user_sector_map): results = [] - for sector, stocks in sector_map.items(): - + for sector, stocks in user_sector_map.items(): data = list(ThreadPoolExecutor(5).map(analyze, stocks)) data = [x for x in data if x] - + for x in data: + x["sector"] = sector results.extend(data) results.sort( diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..7e9fea4 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,114 @@ +alembic==1.18.5 +annotated-doc==0.0.4 +annotated-types==0.7.0 +anyio==4.12.1 +APScheduler==3.11.2 +argon2-cffi==25.1.0 +argon2-cffi-bindings==25.1.0 +bcrypt==5.0.0 +beautifulsoup4==4.15.0 +cachetools==7.0.5 +certifi==2026.1.4 +cffi==2.0.0 +charset-normalizer==3.4.7 +click==8.3.1 +cryptography==48.0.0 +curl_cffi==0.16.0 +defusedxml==0.7.1 +dnspython==2.8.0 +-e git+https://github.com/maddy23285/docvision.git@b0a6bea7ad1db9f515c9128964370c6bfdaad9e1#egg=docfingerprint +-e git+https://github.com/maddy23285/docvision.git@b0a6bea7ad1db9f515c9128964370c6bfdaad9e1#egg=docfingerprint_embedding&subdirectory=embedding_service +ecdsa==0.19.2 +email-validator==2.3.0 +fastapi==0.128.0 +filelock==3.17.0 +fonttools==4.62.1 +fpdf2==2.8.7 +fsspec==2025.2.0 +h11==0.16.0 +hf-xet==1.5.2 +httpcore==1.0.9 +httptools==0.7.1 +httpx==0.28.1 +huggingface_hub==0.36.2 +idna==3.11 +ImageHash==4.3.2 +imap-tools==1.13.0 +iniconfig==2.3.0 +Jinja2==3.1.4 +lxml==6.1.1 +Mako==1.3.12 +MarkupSafe==3.0.2 +mpmath==1.3.0 +multitasking==0.0.13 +networkx==3.4.2 +numpy==2.2.3 +ollama==0.6.2 +opencv-python==4.12.0.88 +opencv-python-headless==4.13.0.92 +packaging==26.2 +pandas==3.0.5 +passlib==1.7.4 +pdf2image==1.17.0 +pdfminer.six==20251230 +pdfplumber==0.11.9 +peewee==4.3.0 +pgvector==0.5.0 +pillow==11.1.0 +platformdirs==4.11.0 +pluggy==1.6.0 +protobuf==7.35.1 +psycopg==3.3.4 +psycopg-binary==3.3.4 +psycopg2-binary==2.9.11 +pyasn1==0.6.4 +pycparser==3.0 +pycryptodome==3.23.0 +pycryptodomex==3.21.0 +pydantic==2.12.5 +pydantic-settings==2.14.2 +pydantic_core==2.41.5 +Pygments==2.20.0 +PyJWT==2.11.0 +pypdf==6.13.0 +pypdfium2==5.9.0 +pytesseract==0.3.13 +pytest==8.4.2 +pytest-asyncio==0.26.0 +python-dateutil==2.9.0.post0 +python-docx==1.2.0 +python-dotenv==1.2.1 +python-jose==3.5.0 +python-multipart==0.0.22 +pytz==2026.3.post1 +PyWavelets==1.9.0 +PyYAML==6.0.3 +redis==5.2.0 +regex==2026.7.19 +requests==2.34.2 +rsa==4.9.1 +safetensors==0.8.0 +scipy==1.18.0 +setuptools==75.8.0 +six==1.17.0 +soupsieve==2.9.1 +SQLAlchemy==2.0.46 +starlette==0.50.0 +structlog==25.5.0 +sympy==1.13.1 +tokenizers==0.22.2 +torch==2.6.0 +torchaudio==2.6.0 +torchvision==0.21.0 +tqdm==4.69.0 +transformers==4.57.6 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +tzlocal==5.3.1 +urllib3==2.7.0 +uvicorn==0.40.0 +uvloop==0.22.1 +watchfiles==1.1.1 +websockets==16.0 +yfinance==1.5.2 +zxing-cpp==2.3.0 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d5ef1d1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ + +services: + frontend: + image: hub.technobeesolutions.in/stock-scanner-frontend:latest + restart: always + ports: + - "3055:80" + depends_on: + - backend + + backend: + image: hub.technobeesolutions.in/stock-scanner-backend:latest + restart: always + ports: + - "8055:8000" + extra_hosts: + - "host.docker.internal:host-gateway" + environment: + - DATABASE_URL=postgresql://postgres:M%40triXPostgr3s%406202@host.docker.internal:5333/stock_scanner diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..2a6bbb6 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,10 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } +} diff --git a/package.json b/package.json index 1724a3c..0199060 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test": "react-scripts test", "eject": "react-scripts eject" }, + "proxy": "http://127.0.0.1:8000", "eslintConfig": { "extends": [ "react-app", diff --git a/src/App.js b/src/App.js index b10f14d..eadafb7 100644 --- a/src/App.js +++ b/src/App.js @@ -9,22 +9,25 @@ import Popup from "./components/Popup"; import AboutScanner from "./components/AboutScanner"; import MarketIntel from "./components/MarketIntel"; import TrendScanner from "./components/TrendScanner"; +import WatchlistManager from "./components/WatchlistManager"; import Login from "./components/Login"; import Signup from "./components/Signup"; import ProtectedRoute from "./components/ProtectedRoute"; import Profile from "./components/Profile"; +import MainDashboard from "./components/MainDashboard"; import "./index.css"; function Dashboard() { const [selectedStock, setSelectedStock] = useState("NSE:RELIANCE"); const [signal, setSignal] = useState(null); - const [view, setView] = useState("scanner"); + const [view, setView] = useState("dashboard"); const [userProfile, setUserProfile] = useState(null); const [dropdownOpen, setDropdownOpen] = useState(false); + const [scannerLoaded, setScannerLoaded] = useState(false); const navigate = useNavigate(); const fetchProfile = () => { - axios.get("http://127.0.0.1:8000/api/auth/me") + axios.get("/api/auth/me") .then(res => setUserProfile(res.data)) .catch(err => { if (err.response?.status === 401) handleLogout(); @@ -44,7 +47,10 @@ function Dashboard() {
{/* HEADER */}
-

📈 Stock Scanner Pro

+
+ 📊 +

Stock Scanner Pro

+
{userProfile && (
@@ -74,18 +80,22 @@ function Dashboard() {
+ +
{view === "scanner" && ( <> - - +

TradingView Chart

@@ -93,10 +103,12 @@ function Dashboard() { {signal && } )} + {view === "dashboard" && } {view === "ribbon" && } {view === "smartmoney" && } {view === "marketintel" && } {view === "trend" && } + {view === "watchlists" && } {view === "profile" && }
diff --git a/src/components/AboutScanner.js b/src/components/AboutScanner.js index f6b9c3c..e854200 100644 --- a/src/components/AboutScanner.js +++ b/src/components/AboutScanner.js @@ -1,71 +1,90 @@ -import React from "react"; - -function AboutScanner() { - return ( -
-

🧠 How The Scanner Works

- -
    -
  • - 📈 Trend: Price above EMA20 indicates bullish trend. -
  • - -
  • - ⚡ Momentum: RSI measures strength of buying momentum. -
  • - -
  • - 🚀 Breakout: Detects stocks breaking recent resistance levels. -
  • - -
  • - 🔥 Volume Ratio: Compares current volume with average volume. -
  • - -
  • - 💪 Relative Strength (RS): Measures stock performance vs NIFTY. -
  • - -
  • - 🏦 Smart Money Score: Estimates institutional accumulation probability. -
  • - -
  • - 🤖 AI Score: Combined score using trend, RSI, breakout, volume and RS. -
  • - -
  • - ✅ Confidence: Probability of a quality setup based on AI Score. -
  • - -
  • - 🎯 Fund Candidate: High-volume breakout stocks showing potential accumulation. -
  • - -
  • - 🏆 Trade Of The Day: Highest ranked stock across all sectors. -
  • -
- -

🏅 Score Guide

- -
    -
  • A+ (90-100) = Exceptional
  • -
  • A (80-89) = Strong
  • -
  • B (70-79) = Good
  • -
  • C (60-69) = Average
  • -
  • D (<60) = Avoid
  • -
-
- ); -} - +import React, { useState, useEffect } from "react"; + +function AboutScanner({ isLoaded }) { + const [isCollapsed, setIsCollapsed] = useState(false); + + useEffect(() => { + if (isLoaded) { + setIsCollapsed(true); + } + }, [isLoaded]); + + return ( +
+
setIsCollapsed(!isCollapsed)} + > +

🧠 How The Scanner Works

+ {isCollapsed ? "▼" : "▲"} +
+ + {!isCollapsed && ( +
+ +
    +
  • + 📈 Trend: Price above EMA20 indicates bullish trend. +
  • + +
  • + ⚡ Momentum: RSI measures strength of buying momentum. +
  • + +
  • + 🚀 Breakout: Detects stocks breaking recent resistance levels. +
  • + +
  • + 🔥 Volume Ratio: Compares current volume with average volume. +
  • + +
  • + 💪 Relative Strength (RS): Measures stock performance vs NIFTY. +
  • + +
  • + 🏦 Smart Money Score: Estimates institutional accumulation probability. +
  • + +
  • + 🤖 AI Score: Combined score using trend, RSI, breakout, volume and RS. +
  • + +
  • + ✅ Confidence: Probability of a quality setup based on AI Score. +
  • + +
  • + 🎯 Fund Candidate: High-volume breakout stocks showing potential accumulation. +
  • + +
  • + 🏆 Trade Of The Day: Highest ranked stock across all sectors. +
  • +
+ +

🏅 Score Guide

+ +
    +
  • A+ (90-100) = Exceptional
  • +
  • A (80-89) = Strong
  • +
  • B (70-79) = Good
  • +
  • C (60-69) = Average
  • +
  • D (<60) = Avoid
  • +
+
+ )} +
+ ); +} + export default AboutScanner; \ No newline at end of file diff --git a/src/components/Backtest.js b/src/components/Backtest.js index 1dbc9ef..a6dadd9 100644 --- a/src/components/Backtest.js +++ b/src/components/Backtest.js @@ -1,163 +1,163 @@ -import React, { useEffect, useState } from "react"; -import axios from "axios"; - -function Backtest() { - const [data, setData] = useState({}); - - // ✅ Fetch backtest data - const loadBacktest = async () => { - try { - const res = await axios.get("http://127.0.0.1:8000/backtest"); - setData(res.data); - } catch (err) { - console.error("Backtest Error:", err); - } - }; - - useEffect(() => { - loadBacktest(); - }, []); - - return ( -
-

- 📊 Backtest Dashboard (Strategy Performance) -

- - {/* ✅ EXPLANATION PANEL */} -
-

📘 How to Read Backtest

- -

- ✅ Trades = Total opportunities generated -

-

- ✅ Wins = Profitable trades -

-

- ✅ Loss = Losing trades -

-

- ✅ Win Rate = Success % of strategy -

-

- ✅ Profit = Net gain (simulated) -

- -
- -

- 💡 Best Strategy Rules: -

-
    -
  • Win Rate ≥ 60% ✅
  • -
  • Trades ≥ 10 ✅
  • -
  • Profit positive ✅
  • -
-
- - {/* ✅ SECTORS */} - {Object.entries(data).map(([sector, stocks]) => ( -
-

- {sector} -

- - - - - - - - - - - - - - - {stocks.map((s, i) => ( - - - - - - - - - - {/* ✅ WIN RATE COLOR */} - - - {/* ✅ PROFIT COLOR */} - - - ))} - -
StockTradesWinsLossWin %Profit
{s.symbol}{s.trades} - {s.wins} - - {s.loss} - = 70 - ? "#3fb950" - : s.win_rate >= 60 - ? "#d29922" - : "#f85149", - fontWeight: "bold", - }} - > - {s.win_rate}% - 0 ? "#3fb950" : "#f85149", - fontWeight: "bold", - }} - > - {s.profit} -
-
- ))} -
- ); -} - +import React, { useEffect, useState } from "react"; +import axios from "axios"; + +function Backtest() { + const [data, setData] = useState({}); + + // ✅ Fetch backtest data + const loadBacktest = async () => { + try { + const res = await axios.get("/api/backtest"); + setData(res.data); + } catch (err) { + console.error("Backtest Error:", err); + } + }; + + useEffect(() => { + loadBacktest(); + }, []); + + return ( +
+

+ 📊 Backtest Dashboard (Strategy Performance) +

+ + {/* ✅ EXPLANATION PANEL */} +
+

📘 How to Read Backtest

+ +

+ ✅ Trades = Total opportunities generated +

+

+ ✅ Wins = Profitable trades +

+

+ ✅ Loss = Losing trades +

+

+ ✅ Win Rate = Success % of strategy +

+

+ ✅ Profit = Net gain (simulated) +

+ +
+ +

+ 💡 Best Strategy Rules: +

+
    +
  • Win Rate ≥ 60% ✅
  • +
  • Trades ≥ 10 ✅
  • +
  • Profit positive ✅
  • +
+
+ + {/* ✅ SECTORS */} + {Object.entries(data).map(([sector, stocks]) => ( +
+

+ {sector} +

+ + + + + + + + + + + + + + + {stocks.map((s, i) => ( + + + + + + + + + + {/* ✅ WIN RATE COLOR */} + + + {/* ✅ PROFIT COLOR */} + + + ))} + +
StockTradesWinsLossWin %Profit
{s.symbol}{s.trades} + {s.wins} + + {s.loss} + = 70 + ? "#3fb950" + : s.win_rate >= 60 + ? "#d29922" + : "#f85149", + fontWeight: "bold", + }} + > + {s.win_rate}% + 0 ? "#3fb950" : "#f85149", + fontWeight: "bold", + }} + > + {s.profit} +
+
+ ))} +
+ ); +} + export default Backtest; \ No newline at end of file diff --git a/src/components/Chart.js b/src/components/Chart.js index c1ded22..cb18a9f 100644 --- a/src/components/Chart.js +++ b/src/components/Chart.js @@ -1,149 +1,149 @@ -import React, { useEffect, useRef } from "react"; -import { createChart, CandlestickSeries, LineSeries } from "lightweight-charts"; -import axios from "axios"; - -function Chart({ symbol }) { - const ref = useRef(null); - - useEffect(() => { - if (!ref.current || !symbol) return; - - ref.current.innerHTML = ""; - - try { - const chart = createChart(ref.current, { - width: 800, - height: 500, - layout: { - background: { color: "#111" }, - textColor: "#DDD", - }, - grid: { - vertLines: { color: "#333" }, - horzLines: { color: "#333" }, - }, - }); - - // ✅ MAIN CANDLE SERIES - const candleSeries = chart.addSeries(CandlestickSeries); - - // ✅ EMA LINE - const emaSeries = chart.addSeries(LineSeries, { - color: "yellow", - lineWidth: 2, - }); - - // ✅ RSI LINE (drawn on same chart for simplicity) - const rsiSeries = chart.addSeries(LineSeries, { - color: "cyan", - lineWidth: 1, - }); - - // ✅ EMA CALCULATION - const calculateEMA = (data, period = 20) => { - const k = 2 / (period + 1); - let ema = data[0].close; - return data.map((d) => { - ema = d.close * k + ema * (1 - k); - return { time: d.time, value: ema }; - }); - }; - - // ✅ RSI CALCULATION - const calculateRSI = (data, period = 14) => { - let gains = 0; - let losses = 0; - - const result = []; - - for (let i = 1; i < data.length; i++) { - const diff = data[i].close - data[i - 1].close; - - if (diff > 0) gains += diff; - else losses -= diff; - - if (i >= period) { - const rs = gains / (losses || 1); - const rsi = 100 - 100 / (1 + rs); - - result.push({ time: data[i].time, value: rsi }); - } - } - - return result; - }; - - // ✅ LOAD DATA FUNCTION - const loadData = () => { - axios - .get(`http://localhost:8000/history?symbol=${symbol}`) - .then((res) => { - if (!res.data || res.data.length === 0) return; - - const formatted = res.data.map((d) => ({ - time: Math.floor(d.time), - open: Number(d.open), - high: Number(d.high), - low: Number(d.low), - close: Number(d.close), - })); - - // ✅ SET CANDLES - candleSeries.setData(formatted); - - // ✅ EMA - const emaData = calculateEMA(formatted); - emaSeries.setData(emaData); - - // ✅ RSI - const rsiData = calculateRSI(formatted); - rsiSeries.setData(rsiData); - - // ✅ ENTRY / SL / TARGET - const lastPrice = formatted[formatted.length - 1].close; - - candleSeries.createPriceLine({ - price: lastPrice, - color: "blue", - lineWidth: 2, - title: "Entry", - }); - - candleSeries.createPriceLine({ - price: lastPrice * 0.97, - color: "red", - title: "SL", - }); - - candleSeries.createPriceLine({ - price: lastPrice * 1.05, - color: "green", - title: "Target", - }); - - chart.timeScale().fitContent(); - }) - .catch((err) => console.error(err)); - }; - - // ✅ INITIAL LOAD - loadData(); - - // ✅ LIVE UPDATE (every 1 min) - const interval = setInterval(loadData, 60000); - - return () => { - clearInterval(interval); - chart.remove(); - }; - - } catch (err) { - console.error("Chart crash:", err); - } - - }, [symbol]); - - return
; -} - +import React, { useEffect, useRef } from "react"; +import { createChart, CandlestickSeries, LineSeries } from "lightweight-charts"; +import axios from "axios"; + +function Chart({ symbol }) { + const ref = useRef(null); + + useEffect(() => { + if (!ref.current || !symbol) return; + + ref.current.innerHTML = ""; + + try { + const chart = createChart(ref.current, { + width: 800, + height: 500, + layout: { + background: { color: "#111" }, + textColor: "#DDD", + }, + grid: { + vertLines: { color: "#333" }, + horzLines: { color: "#333" }, + }, + }); + + // ✅ MAIN CANDLE SERIES + const candleSeries = chart.addSeries(CandlestickSeries); + + // ✅ EMA LINE + const emaSeries = chart.addSeries(LineSeries, { + color: "yellow", + lineWidth: 2, + }); + + // ✅ RSI LINE (drawn on same chart for simplicity) + const rsiSeries = chart.addSeries(LineSeries, { + color: "cyan", + lineWidth: 1, + }); + + // ✅ EMA CALCULATION + const calculateEMA = (data, period = 20) => { + const k = 2 / (period + 1); + let ema = data[0].close; + return data.map((d) => { + ema = d.close * k + ema * (1 - k); + return { time: d.time, value: ema }; + }); + }; + + // ✅ RSI CALCULATION + const calculateRSI = (data, period = 14) => { + let gains = 0; + let losses = 0; + + const result = []; + + for (let i = 1; i < data.length; i++) { + const diff = data[i].close - data[i - 1].close; + + if (diff > 0) gains += diff; + else losses -= diff; + + if (i >= period) { + const rs = gains / (losses || 1); + const rsi = 100 - 100 / (1 + rs); + + result.push({ time: data[i].time, value: rsi }); + } + } + + return result; + }; + + // ✅ LOAD DATA FUNCTION + const loadData = () => { + axios + .get(`http://localhost:8000/history?symbol=${symbol}`) + .then((res) => { + if (!res.data || res.data.length === 0) return; + + const formatted = res.data.map((d) => ({ + time: Math.floor(d.time), + open: Number(d.open), + high: Number(d.high), + low: Number(d.low), + close: Number(d.close), + })); + + // ✅ SET CANDLES + candleSeries.setData(formatted); + + // ✅ EMA + const emaData = calculateEMA(formatted); + emaSeries.setData(emaData); + + // ✅ RSI + const rsiData = calculateRSI(formatted); + rsiSeries.setData(rsiData); + + // ✅ ENTRY / SL / TARGET + const lastPrice = formatted[formatted.length - 1].close; + + candleSeries.createPriceLine({ + price: lastPrice, + color: "blue", + lineWidth: 2, + title: "Entry", + }); + + candleSeries.createPriceLine({ + price: lastPrice * 0.97, + color: "red", + title: "SL", + }); + + candleSeries.createPriceLine({ + price: lastPrice * 1.05, + color: "green", + title: "Target", + }); + + chart.timeScale().fitContent(); + }) + .catch((err) => console.error(err)); + }; + + // ✅ INITIAL LOAD + loadData(); + + // ✅ LIVE UPDATE (every 1 min) + const interval = setInterval(loadData, 60000); + + return () => { + clearInterval(interval); + chart.remove(); + }; + + } catch (err) { + console.error("Chart crash:", err); + } + + }, [symbol]); + + return
; +} + export default Chart; \ No newline at end of file diff --git a/src/components/ChartPage.js b/src/components/ChartPage.js index 393f90c..255f9e6 100644 --- a/src/components/ChartPage.js +++ b/src/components/ChartPage.js @@ -1,21 +1,21 @@ -import React from "react"; - -function ChartPage({ symbol }) { - const tvSymbol = symbol || "NSE:RELIANCE"; - - const url = `https://www.tradingview.com/chart/?symbol=${tvSymbol}`; - - return ( -
- -
- ); -} - +import React from "react"; + +function ChartPage({ symbol }) { + const tvSymbol = symbol || "NSE:RELIANCE"; + + const url = `https://www.tradingview.com/chart/?symbol=${tvSymbol}`; + + return ( +
+ +
+ ); +} + export default ChartPage; \ No newline at end of file diff --git a/src/components/Login.js b/src/components/Login.js index 80a7c87..fa14713 100644 --- a/src/components/Login.js +++ b/src/components/Login.js @@ -15,13 +15,13 @@ function Login() { setLoading(true); setError(""); try { - const resKey = await axios.get("http://127.0.0.1:8000/api/auth/public-key"); + const resKey = await axios.get("/api/auth/public-key"); const publicKeyPem = resKey.data.public_key; const publicKey = forge.pki.publicKeyFromPem(publicKeyPem); const encrypted = publicKey.encrypt(password, 'RSA-OAEP'); const encryptedBase64 = forge.util.encode64(encrypted); - const res = await axios.post("http://127.0.0.1:8000/api/auth/login", { + const res = await axios.post("/api/auth/login", { username, password: encryptedBase64 }); diff --git a/src/components/LongTerm.js b/src/components/LongTerm.js index 97129dc..5272c53 100644 --- a/src/components/LongTerm.js +++ b/src/components/LongTerm.js @@ -1,114 +1,114 @@ -import React, { useEffect, useState } from "react"; -import axios from "axios"; - -function LongTerm() { - const [data, setData] = useState({}); - - const loadData = async () => { - try { - const res = await axios.get("http://127.0.0.1:8000/longterm"); - setData(res.data); - } catch (err) { - console.error(err); - } - }; - - useEffect(() => { - loadData(); - }, []); - - return ( -
- -

- 📈 Long-Term Investment Dashboard -

- - {/* ✅ EXPLANATION */} -
-

📘 Strategy

- -
    -
  • ✅ Golden Cross = 50 EMA > 200 EMA
  • -
  • ✅ Support = Price above 200 EMA
  • -
  • ✅ Momentum = Breakout + Volume
  • -
- -

💡 Only strong trend reversal stocks are shown

-
- - {Object.entries(data).map(([sector, info]) => { - - -const filtered = info.stocks.filter( - s => s.golden_cross || s.support_strength -); - - - if (filtered.length === 0) return null; - - return ( -
- -

- {sector} ✅ Long-Term Strong -

- - - - - - - - - - - - - - - {filtered.map((s, i) => ( - - - - - - - - - - - - ))} - -
StockScoreRSIGolden CrossSupportSignal
{s.symbol}{s.score}{s.rsi} - ✅ - - ✅ - - {s.signal} -
- -
- ); - })} -
- ); -} - -export default LongTerm; +import React, { useEffect, useState } from "react"; +import axios from "axios"; + +function LongTerm() { + const [data, setData] = useState({}); + + const loadData = async () => { + try { + const res = await axios.get("/api/longterm"); + setData(res.data); + } catch (err) { + console.error(err); + } + }; + + useEffect(() => { + loadData(); + }, []); + + return ( +
+ +

+ 📈 Long-Term Investment Dashboard +

+ + {/* ✅ EXPLANATION */} +
+

📘 Strategy

+ +
    +
  • ✅ Golden Cross = 50 EMA > 200 EMA
  • +
  • ✅ Support = Price above 200 EMA
  • +
  • ✅ Momentum = Breakout + Volume
  • +
+ +

💡 Only strong trend reversal stocks are shown

+
+ + {Object.entries(data).map(([sector, info]) => { + + +const filtered = info.stocks.filter( + s => s.golden_cross || s.support_strength +); + + + if (filtered.length === 0) return null; + + return ( +
+ +

+ {sector} ✅ Long-Term Strong +

+ + + + + + + + + + + + + + + {filtered.map((s, i) => ( + + + + + + + + + + + + ))} + +
StockScoreRSIGolden CrossSupportSignal
{s.symbol}{s.score}{s.rsi} + ✅ + + ✅ + + {s.signal} +
+ +
+ ); + })} +
+ ); +} + +export default LongTerm; diff --git a/src/components/MainDashboard.js b/src/components/MainDashboard.js new file mode 100644 index 0000000..7300584 --- /dev/null +++ b/src/components/MainDashboard.js @@ -0,0 +1,155 @@ +import React, { useState, useRef, useEffect } from "react"; +import axios from "axios"; + +function MainDashboard({ userProfile }) { + const [searchQuery, setSearchQuery] = useState(""); + const [searchResults, setSearchResults] = useState([]); + const [searching, setSearching] = useState(false); + const searchTimeout = useRef(null); + + const formatMarketCap = (cap) => { + if (!cap) return "N/A"; + if (cap >= 1e12) return "₹" + (cap / 1e12).toFixed(2) + "T"; + if (cap >= 1e9) return "₹" + (cap / 1e9).toFixed(2) + "B"; + if (cap >= 1e7) return "₹" + (cap / 1e7).toFixed(2) + "Cr"; + return "₹" + cap.toLocaleString(); + }; + + const getMarketCapCategory = (cap) => { + if (!cap) return null; + const cr = cap / 1e7; + if (cr >= 20000) return "Large Cap"; + if (cr >= 5000) return "Mid Cap"; + return "Small Cap"; + }; + + const formatPrice = (price) => { + if (!price) return "N/A"; + return "₹" + price.toFixed(2); + }; + + const handleSearch = (e) => { + const query = e.target.value; + setSearchQuery(query); + + if (searchTimeout.current) clearTimeout(searchTimeout.current); + + if (query.trim().length < 2) { + setSearchResults([]); + return; + } + + searchTimeout.current = setTimeout(async () => { + setSearching(true); + try { + const res = await axios.get(`/api/scanner/search?q=${query}`); + setSearchResults(res.data); + } catch (err) { + console.error(err); + } + setSearching(false); + }, 500); + }; + + const openChart = (symbol) => { + window.open(`https://www.tradingview.com/chart/?symbol=NSE:${symbol}`, "_blank"); + }; + + return ( +
+ +
+
+ 🔍 + + {searching && Searching...} +
+ + {/* Search Results Dropdown */} + {searchResults.length > 0 && searchQuery.length >= 2 && ( +
+ {searchResults.map((result, i) => ( +
openChart(result.symbol)} + style={{ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '15px', + borderBottom: '1px solid var(--border-color)', + background: 'var(--bg-tertiary)', + marginBottom: '8px', + borderRadius: '12px', + cursor: 'pointer', + transition: 'background 0.2s' + }} + onMouseOver={(e) => e.currentTarget.style.background = 'rgba(59, 130, 246, 0.1)'} + onMouseOut={(e) => e.currentTarget.style.background = 'var(--bg-tertiary)'} + > +
+
+ {result.symbol} + {result.sector && ( + + {result.sector} + + )} + {result.marketCap && ( + + {getMarketCapCategory(result.marketCap)} + + )} +
+
+ {result.longname || result.shortname} + {result.currentPrice && ( + <> + + Price: {formatPrice(result.currentPrice)} + + )} + {result.marketCap && ( + <> + + {formatMarketCap(result.marketCap)} + + )} +
+
+
+ Chart + + + + + +
+
+ ))} +
+ )} +
+ +
+ ); +} + +export default MainDashboard; diff --git a/src/components/MarketIntel.js b/src/components/MarketIntel.js index 2a0e3f3..52eb37f 100644 --- a/src/components/MarketIntel.js +++ b/src/components/MarketIntel.js @@ -10,7 +10,7 @@ function MarketIntel() { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/api/scanner/marketintel"); + const res = await axios.get("/api/scanner/marketintel"); setData(res.data); } catch (err) { console.error(err); diff --git a/src/components/Popup.js b/src/components/Popup.js index 0005458..7ede032 100644 --- a/src/components/Popup.js +++ b/src/components/Popup.js @@ -1,22 +1,22 @@ -import React, { useEffect } from "react"; - -function Popup({ signal }) { - useEffect(() => { - const timer = setTimeout(() => { - const popup = document.getElementById("popup"); - if (popup) popup.style.display = "none"; - }, 4000); - - return () => clearTimeout(timer); - }, []); - - return ( - - ); -} - +import React, { useEffect } from "react"; + +function Popup({ signal }) { + useEffect(() => { + const timer = setTimeout(() => { + const popup = document.getElementById("popup"); + if (popup) popup.style.display = "none"; + }, 4000); + + return () => clearTimeout(timer); + }, []); + + return ( + + ); +} + export default Popup; \ No newline at end of file diff --git a/src/components/Profile.js b/src/components/Profile.js index 238a860..f73a454 100644 --- a/src/components/Profile.js +++ b/src/components/Profile.js @@ -43,7 +43,7 @@ function Profile({ userProfile, onProfileUpdated }) { if (payload.password) { // Fetch Public Key to encrypt new password - const resKey = await axios.get("http://127.0.0.1:8000/api/auth/public-key"); + const resKey = await axios.get("/api/auth/public-key"); const publicKey = forge.pki.publicKeyFromPem(resKey.data.public_key); const encrypted = publicKey.encrypt(payload.password, 'RSA-OAEP'); payload.password = forge.util.encode64(encrypted); @@ -51,7 +51,7 @@ function Profile({ userProfile, onProfileUpdated }) { delete payload.password; } - await axios.put("http://127.0.0.1:8000/api/auth/update-profile", payload); + await axios.put("/api/auth/update-profile", payload); setMessage("Profile updated successfully! ✅"); if (onProfileUpdated) onProfileUpdated(); setFormData(prev => ({ ...prev, password: "" })); // Clear password field diff --git a/src/components/Ribbon.js b/src/components/Ribbon.js index 266e1ce..a0d44eb 100644 --- a/src/components/Ribbon.js +++ b/src/components/Ribbon.js @@ -7,7 +7,7 @@ function Ribbon() { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/api/scanner/ribbon"); + const res = await axios.get("/api/scanner/ribbon"); setData(res.data || []); setLoading(false); } catch (err) { @@ -26,7 +26,18 @@ function Ribbon() { window.open(`https://www.tradingview.com/chart/?symbol=NSE:${symbol}`, "_blank"); }; - if (loading) return
Loading Ribbon...
; + const removeStockFromSector = async (symbol, sector) => { + try { + await axios.delete("/api/scanner/sectors", { + data: { sector, symbol } + }); + setData(prev => prev.filter(s => s.symbol !== symbol)); + } catch (err) { + console.error("Error removing stock", err); + } + }; + + if (loading) return
Loading Ribbon (this may take up to 60 seconds for large watchlists)...
; return (
@@ -65,9 +76,23 @@ function Ribbon() { - +
+ + {s.sector && ( + + )} +
))} diff --git a/src/components/Scanner.js b/src/components/Scanner.js index 9a988fd..dba4037 100644 --- a/src/components/Scanner.js +++ b/src/components/Scanner.js @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; +import AboutScanner from "./AboutScanner"; function Scanner({ onSelectStock, onSignal }) { const [data, setData] = useState(null); @@ -7,7 +8,7 @@ function Scanner({ onSelectStock, onSignal }) { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/api/scanner/scan"); + const res = await axios.get("/api/scanner/scan"); const newData = res.data; const best = newData.best_5 && newData.best_5.length > 0 ? newData.best_5[0] : null; @@ -33,10 +34,37 @@ function Scanner({ onSelectStock, onSignal }) { window.open(`https://www.tradingview.com/chart/?symbol=NSE:${symbol}`, "_blank"); }; - if (!data) return
Loading Scanner Data...
; + const removeStockFromSector = async (symbol, sector) => { + try { + await axios.delete("/api/scanner/sectors", { + data: { sector, symbol } + }); + setData(prev => { + const newData = JSON.parse(JSON.stringify(prev)); + if (newData.sectors && newData.sectors[sector]) { + newData.sectors[sector].stocks = newData.sectors[sector].stocks.filter(s => s.symbol !== symbol); + } + if (newData.best_5) { + newData.best_5 = newData.best_5.filter(s => s.symbol !== symbol); + } + return newData; + }); + } catch (err) { + console.error("Error removing stock", err); + } + }; + + if (!data) return ( + <> + +
Loading Scanner Data (this may take up to 60 seconds for large watchlists)...
+ + ); return (
+ + {/* Top 5 Trades */} {(data.best_5 || []).length > 0 && (
@@ -72,9 +100,23 @@ function Scanner({ onSelectStock, onSignal }) { {s.grade} {s.institutional ? 🔥 Yes : No} - +
+ + {s?.sector && ( + + )} +
))} @@ -125,9 +167,21 @@ function Scanner({ onSelectStock, onSignal }) { ₹{s?.target} {s?.ai_score} - +
+ + +
))} diff --git a/src/components/Signup.js b/src/components/Signup.js index dd4f7aa..2bbaab8 100644 --- a/src/components/Signup.js +++ b/src/components/Signup.js @@ -15,13 +15,13 @@ function Signup() { setLoading(true); setError(""); try { - const resKey = await axios.get("http://127.0.0.1:8000/api/auth/public-key"); + const resKey = await axios.get("/api/auth/public-key"); const publicKeyPem = resKey.data.public_key; const publicKey = forge.pki.publicKeyFromPem(publicKeyPem); const encrypted = publicKey.encrypt(password, 'RSA-OAEP'); const encryptedBase64 = forge.util.encode64(encrypted); - await axios.post("http://127.0.0.1:8000/api/auth/signup", { + await axios.post("/api/auth/signup", { username, password: encryptedBase64 }); diff --git a/src/components/SmartMoney.js b/src/components/SmartMoney.js index 19c9e2f..c5911e1 100644 --- a/src/components/SmartMoney.js +++ b/src/components/SmartMoney.js @@ -12,7 +12,7 @@ function SmartMoney() { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/api/scanner/smartmoney"); + const res = await axios.get("/api/scanner/smartmoney"); setData(res.data); } catch (err) { console.error(err); @@ -23,7 +23,30 @@ function SmartMoney() { window.open(`https://www.tradingview.com/chart/?symbol=NSE:${symbol}`, "_blank"); }; - if (!data) return
Loading Smart Money Data...
; + const removeStockFromSector = async (symbol, sector) => { + try { + await axios.delete("/api/scanner/sectors", { + data: { sector, symbol } + }); + setData(prev => { + const newData = JSON.parse(JSON.stringify(prev)); + if (newData.top_10) { + newData.top_10 = newData.top_10.filter(s => s.symbol !== symbol); + } + if (newData.all) { + newData.all = newData.all.filter(s => s.symbol !== symbol); + } + if (newData.trade_of_day && newData.trade_of_day.symbol === symbol) { + newData.trade_of_day = newData.top_10.length > 0 ? newData.top_10[0] : null; + } + return newData; + }); + } catch (err) { + console.error("Error removing stock", err); + } + }; + + if (!data) return
Loading Smart Money Data (this may take up to 60 seconds for large watchlists)...
; return (
@@ -80,7 +103,21 @@ function SmartMoney() { {s.breakout ? "🔥 YES" : "NO"} {s.reason} - +
+ + {s.sector && ( + + )} +
))} diff --git a/src/components/TrendScanner.js b/src/components/TrendScanner.js index d876751..3e7a1d1 100644 --- a/src/components/TrendScanner.js +++ b/src/components/TrendScanner.js @@ -12,7 +12,7 @@ function TrendScanner() { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/api/scanner/trend"); + const res = await axios.get("/api/scanner/trend"); setData(res.data); } catch (err) { console.error(err); @@ -23,7 +23,24 @@ function TrendScanner() { window.open(`https://www.tradingview.com/chart/?symbol=NSE:${symbol}`, "_blank"); }; - if (!data) return
Loading Trend Data...
; + const removeStockFromSector = async (symbol, sector) => { + try { + await axios.delete("/api/scanner/sectors", { + data: { sector, symbol } + }); + setData(prev => { + const newData = JSON.parse(JSON.stringify(prev)); + if (newData.top_20) { + newData.top_20 = newData.top_20.filter(s => s.symbol !== symbol); + } + return newData; + }); + } catch (err) { + console.error("Error removing stock", err); + } + }; + + if (!data) return
Loading Trend Data (this may take up to 60 seconds for large watchlists)...
; return (
@@ -63,7 +80,21 @@ function TrendScanner() { {s.trend_reason} - +
+ + {s.sector && ( + + )} +
))} diff --git a/src/components/WatchlistManager.js b/src/components/WatchlistManager.js new file mode 100644 index 0000000..a229162 --- /dev/null +++ b/src/components/WatchlistManager.js @@ -0,0 +1,279 @@ +import React, { useState, useEffect, useRef } from "react"; +import axios from "axios"; + +function WatchlistManager({ userProfile, onProfileUpdated }) { + const [sectorMap, setSectorMap] = useState({}); + const [newSectorName, setNewSectorName] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + const [searchResults, setSearchResults] = useState([]); + const [searching, setSearching] = useState(false); + const [selectedSector, setSelectedSector] = useState("Personal"); + const [saving, setSaving] = useState(false); + const [message, setMessage] = useState(""); + + const searchTimeout = useRef(null); + + const formatMarketCap = (cap) => { + if (!cap) return "N/A"; + if (cap >= 1e12) return "₹" + (cap / 1e12).toFixed(2) + "T"; + if (cap >= 1e9) return "₹" + (cap / 1e9).toFixed(2) + "B"; + if (cap >= 1e7) return "₹" + (cap / 1e7).toFixed(2) + "Cr"; + return "₹" + cap.toLocaleString(); + }; + + const getMarketCapCategory = (cap) => { + if (!cap) return null; + const cr = cap / 1e7; // Convert to Crores + if (cr >= 20000) return "Large Cap"; + if (cr >= 5000) return "Mid Cap"; + return "Small Cap"; + }; + + const formatPrice = (price) => { + if (!price) return "N/A"; + return "₹" + price.toFixed(2); + }; + + useEffect(() => { + if (userProfile && userProfile.sector_map) { + setSectorMap(userProfile.sector_map); + } + }, [userProfile]); + + const saveSectors = async (updatedMap) => { + setSaving(true); + setMessage(""); + try { + await axios.put("/api/auth/update-sectors", { sector_map: updatedMap }); + setSectorMap(updatedMap); + if (onProfileUpdated) onProfileUpdated(); + setMessage("Sectors saved! ✅"); + setTimeout(() => setMessage(""), 3000); + } catch (err) { + console.error(err); + setMessage("Failed to save sectors."); + } + setSaving(false); + }; + + const handleSearch = (e) => { + const query = e.target.value; + setSearchQuery(query); + + if (searchTimeout.current) clearTimeout(searchTimeout.current); + + if (query.trim().length < 2) { + setSearchResults([]); + return; + } + + searchTimeout.current = setTimeout(async () => { + setSearching(true); + try { + const res = await axios.get(`/api/scanner/search?q=${query}`); + setSearchResults(res.data); + } catch (err) { + console.error(err); + } + setSearching(false); + }, 500); + }; + + const addStockToSector = (symbol, targetSector) => { + const updatedMap = { ...sectorMap }; + if (!updatedMap[targetSector]) updatedMap[targetSector] = []; + if (!updatedMap[targetSector].includes(symbol)) { + updatedMap[targetSector].push(symbol); + saveSectors(updatedMap); + } + }; + + const removeStockFromSector = (symbol, targetSector) => { + const updatedMap = { ...sectorMap }; + if (updatedMap[targetSector]) { + updatedMap[targetSector] = updatedMap[targetSector].filter(s => s !== symbol); + saveSectors(updatedMap); + } + }; + + const createSector = () => { + const name = newSectorName.trim(); + if (name && !sectorMap[name]) { + const updatedMap = { ...sectorMap, [name]: [] }; + saveSectors(updatedMap); + setNewSectorName(""); + } + }; + + const deleteSector = (sector) => { + if (sector === "Personal") return; // Protect Personal + if (window.confirm(`Are you sure you want to delete the ${sector} watchlist?`)) { + const updatedMap = { ...sectorMap }; + delete updatedMap[sector]; + saveSectors(updatedMap); + if (selectedSector === sector) setSelectedSector("Personal"); + } + }; + + return ( +
+ + {/* LEFT PANEL: Sector List & Creator */} +
+

📋 Watchlists

+
+ {Object.keys(sectorMap).map(sector => ( +
setSelectedSector(sector)} + style={{ + padding: '10px 15px', + background: selectedSector === sector ? 'rgba(59, 130, 246, 0.2)' : 'var(--bg-tertiary)', + border: selectedSector === sector ? '1px solid var(--accent-blue)' : '1px solid var(--border-color)', + borderRadius: '8px', cursor: 'pointer', + display: 'flex', justifyContent: 'space-between', alignItems: 'center', + transition: 'all 0.2s' + }} + > + + {sector} ({sectorMap[sector].length}) + + {sector !== "Personal" && ( + + )} +
+ ))} +
+ +
+

Create new watchlist

+
+ setNewSectorName(e.target.value)} + placeholder="E.g., EV Stocks" + style={{ flex: 1, padding: '10px', borderRadius: '5px', border: '1px solid var(--border-color)', background: 'var(--bg-tertiary)', color: 'white' }} + /> + +
+
+
+ + {/* RIGHT PANEL: Search & Stock List */} +
+
+

+ {selectedSector} Watchlist +

+ {saving && Saving...} + {message && {message}} +
+ + {/* Search Bar */} +
+ + {searching && Searching...} + + {/* Search Results Dropdown */} + {searchResults.length > 0 && searchQuery.length >= 2 && ( +
+ {searchResults.map((result, i) => ( +
+
+
+ {result.symbol} + {result.sector && ( + + {result.sector} + + )} + {result.marketCap && ( + + {getMarketCapCategory(result.marketCap)} + + )} +
+
+ {result.longname || result.shortname} + {result.currentPrice && ( + <> + + Price: {formatPrice(result.currentPrice)} + + )} + {result.marketCap && ( + <> + + {formatMarketCap(result.marketCap)} + + )} +
+
+ +
+ ))} +
+ )} +
+ + {/* Stock List Grid */} + {sectorMap[selectedSector] && sectorMap[selectedSector].length === 0 ? ( +
No stocks in this watchlist. Search above to add some!
+ ) : ( +
+
+ {(sectorMap[selectedSector] || []).map(symbol => ( +
+ + {/* Top Row: Symbol and Trash */} +
+ {symbol} + +
+ + {/* Bottom Row: + Personal */} + {selectedSector !== "Personal" && ( + + )} +
+ ))} +
+
+ )} +
+
+ ); +} + +export default WatchlistManager; diff --git a/src/index.css b/src/index.css index e0205d5..012f9cb 100644 --- a/src/index.css +++ b/src/index.css @@ -83,9 +83,9 @@ h2 { background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border-color); - padding: 10px 20px; + padding: 8px 14px; border-radius: 8px; - font-size: 0.95rem; + font-size: 0.9rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease;