User Auth Done
This commit is contained in:
BIN
backend/app/__pycache__/main.cpython-313.pyc
Normal file
BIN
backend/app/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/app/api/__pycache__/auth.cpython-313.pyc
Normal file
BIN
backend/app/api/__pycache__/auth.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/app/api/__pycache__/scanner.cpython-313.pyc
Normal file
BIN
backend/app/api/__pycache__/scanner.cpython-313.pyc
Normal file
Binary file not shown.
106
backend/app/api/auth.py
Normal file
106
backend/app/api/auth.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
from jose import JWTError, jwt
|
||||
|
||||
from app.db.database import get_db
|
||||
from app.db.models import User
|
||||
from app.core.security import (
|
||||
public_key, decrypt_password, verify_password,
|
||||
get_password_hash, create_access_token
|
||||
)
|
||||
from app.core.config import SECRET_KEY, ALGORITHM
|
||||
|
||||
router = APIRouter()
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/auth/login")
|
||||
|
||||
class AuthRequest(BaseModel):
|
||||
username: str
|
||||
password: str # Base64 encoded RSA-encrypted password
|
||||
|
||||
class ProfileUpdateRequest(BaseModel):
|
||||
display_name: str = None
|
||||
email_id: str = None
|
||||
mobile_no: str = None
|
||||
gender: str = None
|
||||
password: str = None # Base64 encoded RSA-encrypted new password
|
||||
|
||||
@router.get("/public-key")
|
||||
def get_public_key():
|
||||
return {"public_key": public_key.decode("utf-8")}
|
||||
|
||||
@router.post("/signup")
|
||||
def signup(req: AuthRequest, db: Session = Depends(get_db)):
|
||||
db_user = db.query(User).filter(User.username == req.username).first()
|
||||
if db_user:
|
||||
raise HTTPException(status_code=400, detail="Username already registered")
|
||||
|
||||
# Decrypt password
|
||||
decrypted_password = decrypt_password(req.password)
|
||||
hashed_password = get_password_hash(decrypted_password)
|
||||
|
||||
new_user = User(username=req.username, hashed_password=hashed_password)
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
return {"message": "User created successfully"}
|
||||
|
||||
@router.post("/login")
|
||||
def login(req: AuthRequest, db: Session = Depends(get_db)):
|
||||
db_user = db.query(User).filter(User.username == req.username).first()
|
||||
if not db_user:
|
||||
raise HTTPException(status_code=400, detail="Invalid credentials")
|
||||
|
||||
decrypted_password = decrypt_password(req.password)
|
||||
if not verify_password(decrypted_password, db_user.hashed_password):
|
||||
raise HTTPException(status_code=400, detail="Invalid credentials")
|
||||
|
||||
access_token = create_access_token(data={"sub": db_user.username})
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
|
||||
user = db.query(User).filter(User.username == username).first()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
||||
@router.get("/me")
|
||||
def get_me(current_user: User = Depends(get_current_user)):
|
||||
return {
|
||||
"username": current_user.username,
|
||||
"display_name": current_user.display_name,
|
||||
"email_id": current_user.email_id,
|
||||
"mobile_no": current_user.mobile_no,
|
||||
"gender": current_user.gender
|
||||
}
|
||||
|
||||
@router.put("/update-profile")
|
||||
def update_profile(req: ProfileUpdateRequest, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
if req.display_name is not None:
|
||||
current_user.display_name = req.display_name
|
||||
if req.email_id is not None:
|
||||
current_user.email_id = req.email_id
|
||||
if req.mobile_no is not None:
|
||||
current_user.mobile_no = req.mobile_no
|
||||
if req.gender is not None:
|
||||
current_user.gender = req.gender
|
||||
if req.password:
|
||||
decrypted_password = decrypt_password(req.password)
|
||||
hashed_password = get_password_hash(decrypted_password)
|
||||
current_user.hashed_password = hashed_password
|
||||
|
||||
db.commit()
|
||||
return {"message": "Profile updated successfully"}
|
||||
27
backend/app/api/scanner.py
Normal file
27
backend/app/api/scanner.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from app.api.auth import get_current_user
|
||||
from app.services.stock_engine import (
|
||||
get_scan, get_ribbon, get_smartmoney, get_trend, get_marketintel
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/scan")
|
||||
def scan(current_user = Depends(get_current_user)):
|
||||
return get_scan()
|
||||
|
||||
@router.get("/ribbon")
|
||||
def ribbon(current_user = Depends(get_current_user)):
|
||||
return get_ribbon()
|
||||
|
||||
@router.get("/smartmoney")
|
||||
def smartmoney(current_user = Depends(get_current_user)):
|
||||
return get_smartmoney()
|
||||
|
||||
@router.get("/trend")
|
||||
def trend(current_user = Depends(get_current_user)):
|
||||
return get_trend()
|
||||
|
||||
@router.get("/marketintel")
|
||||
def marketintel(current_user = Depends(get_current_user)):
|
||||
return get_marketintel()
|
||||
BIN
backend/app/core/__pycache__/config.cpython-313.pyc
Normal file
BIN
backend/app/core/__pycache__/config.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/app/core/__pycache__/security.cpython-313.pyc
Normal file
BIN
backend/app/core/__pycache__/security.cpython-313.pyc
Normal file
Binary file not shown.
6
backend/app/core/config.py
Normal file
6
backend/app/core/config.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import os
|
||||
|
||||
DATABASE_URL = "postgresql://postgres:M%40triXPostgr3s%406202@103.125.129.116:5333/stock_scanner"
|
||||
SECRET_KEY = "stock-scanner-super-secret-key-12345"
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 60
|
||||
61
backend/app/core/security.py
Normal file
61
backend/app/core/security.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import os
|
||||
import base64
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from jose import JWTError, jwt
|
||||
import bcrypt
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import PKCS1_OAEP
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.core.config import SECRET_KEY, ALGORITHM
|
||||
|
||||
RSA_PRIVATE_KEY_PATH = "private.pem"
|
||||
RSA_PUBLIC_KEY_PATH = "public.pem"
|
||||
|
||||
if not os.path.exists(RSA_PRIVATE_KEY_PATH) or not os.path.exists(RSA_PUBLIC_KEY_PATH):
|
||||
key = RSA.generate(2048)
|
||||
private_key = key.export_key()
|
||||
with open(RSA_PRIVATE_KEY_PATH, "wb") as file_out:
|
||||
file_out.write(private_key)
|
||||
|
||||
public_key = key.publickey().export_key()
|
||||
with open(RSA_PUBLIC_KEY_PATH, "wb") as file_out:
|
||||
file_out.write(public_key)
|
||||
else:
|
||||
with open(RSA_PRIVATE_KEY_PATH, "rb") as f:
|
||||
private_key = f.read()
|
||||
with open(RSA_PUBLIC_KEY_PATH, "rb") as f:
|
||||
public_key = f.read()
|
||||
|
||||
rsa_private_key = RSA.import_key(private_key)
|
||||
cipher_rsa = PKCS1_OAEP.new(rsa_private_key)
|
||||
|
||||
def decrypt_password(encrypted_b64_password: str) -> str:
|
||||
try:
|
||||
encrypted_bytes = base64.b64decode(encrypted_b64_password)
|
||||
decrypted = cipher_rsa.decrypt(encrypted_bytes)
|
||||
return decrypted.decode("utf-8")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail="Invalid RSA payload or decryption failed")
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
if isinstance(plain_password, str):
|
||||
plain_password = plain_password.encode('utf-8')
|
||||
if isinstance(hashed_password, str):
|
||||
hashed_password = hashed_password.encode('utf-8')
|
||||
return bcrypt.checkpw(plain_password, hashed_password)
|
||||
|
||||
def get_password_hash(password):
|
||||
if isinstance(password, str):
|
||||
password = password.encode('utf-8')
|
||||
return bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8')
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||
to_encode.update({"exp": expire})
|
||||
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
BIN
backend/app/db/__pycache__/database.cpython-313.pyc
Normal file
BIN
backend/app/db/__pycache__/database.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/app/db/__pycache__/models.cpython-313.pyc
Normal file
BIN
backend/app/db/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
15
backend/app/db/database.py
Normal file
15
backend/app/db/database.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import DATABASE_URL
|
||||
|
||||
engine = create_engine(DATABASE_URL)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
13
backend/app/db/models.py
Normal file
13
backend/app/db/models.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from app.db.database import Base
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
username = Column(String, unique=True, index=True)
|
||||
hashed_password = Column(String)
|
||||
display_name = Column(String, nullable=True)
|
||||
email_id = Column(String, nullable=True)
|
||||
mobile_no = Column(String, nullable=True)
|
||||
gender = Column(String, nullable=True)
|
||||
26
backend/app/main.py
Normal file
26
backend/app/main.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.api import auth, scanner
|
||||
from app.db.database import engine, Base
|
||||
|
||||
# Initialize the database tables
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(title="Stock Scanner Pro API")
|
||||
|
||||
# Configure CORS for frontend access
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Include Routers
|
||||
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
|
||||
app.include_router(scanner.router, prefix="/api/scanner", tags=["scanner"])
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"message": "Stock Scanner Backend is running."}
|
||||
BIN
backend/app/services/__pycache__/stock_engine.cpython-313.pyc
Normal file
BIN
backend/app/services/__pycache__/stock_engine.cpython-313.pyc
Normal file
Binary file not shown.
662
backend/app/services/stock_engine.py
Normal file
662
backend/app/services/stock_engine.py
Normal file
@@ -0,0 +1,662 @@
|
||||
import yfinance as yf
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
# ✅ PERSONAL
|
||||
# ✅ PERSONAL
|
||||
personal_stocks = [
|
||||
"AFFLE.NS","ANANDRATHI.NS","CGPOWER.NS","HFCL.NS",
|
||||
"IREDA.NS","INDOAMIN.NS","PCJEWELLER.NS","PCBL.NS",
|
||||
"RANASUG.NS","SERVOTECH.NS","IDEA.NS","WELSPUNLIV.NS","SANDHAR.NS"
|
||||
]
|
||||
|
||||
# ✅ SECTORS
|
||||
sector_map = {"Personal": personal_stocks,
|
||||
"lookup" :["GRAVITA.NS"],
|
||||
"CG" :["SIEMENS.NS","ABB.NS","BEL.NS","CGPOWER.NS","BHEL.NS","HINDALCO.NS","CUMMINSIND.NS",
|
||||
"SUZLON.NS","THERMAX.NS","POLYCAB.NS","KEI.NS","HAVELLS.NS","VOLTAS.NS","BLUESTARCO.NS",
|
||||
"VGUARD.NS","AIAENG.NS","ELGIEQUIP.NS","TRITURBINE.NS","SANDHAR.NS"],
|
||||
"NIFTY Bank": ["HDFCBANK.NS","ICICIBANK.NS","AXISBANK.NS","KOTAKBANK.NS","SBIN.NS","INDUSINDBK.NS",
|
||||
"BANKBARODA.NS","PNB.NS","FEDERALBNK.NS","IDFCFIRSTB.NS","AUBANK.NS","BANDHANBNK.NS",
|
||||
"CANBK.NS","UNIONBANK.NS","IOB.NS","UCOBANK.NS","BANKINDIA.NS","CENTRALBK.NS",
|
||||
"MAHABANK.NS","YESBANK.NS","IDBI.NS","SOUTHBANK.NS",
|
||||
"RBLBANK.NS","KTKBANK.NS","DCBBANK.NS"],
|
||||
"NIFTY Auto": ["MARUTI.NS","M&M.NS","BAJAJ-AUTO.NS","EICHERMOT.NS","TVSMOTOR.NS",
|
||||
"HEROMOTOCO.NS","ASHOKLEY.NS","BALKRISIND.NS","MRF.NS","APOLLOTYRE.NS","JKTYRE.NS",
|
||||
"CEATLTD.NS","SONACOMS.NS","ENDURANCE.NS","EXIDEIND.NS","BOSCHLTD.NS",
|
||||
"UNOMINDA.NS"],
|
||||
"NIFTY Pharma": ["SUNPHARMA.NS","CIPLA.NS","DRREDDY.NS","DIVISLAB.NS","ZYDUSLIFE.NS","MANKIND.NS",
|
||||
"LUPIN.NS","AUROPHARMA.NS","ALKEM.NS","BIOCON.NS","GLENMARK.NS",
|
||||
"LAURUSLABS.NS","GRANULES.NS","IPCALAB.NS","JBCHEPHARM.NS","NATCOPHARM.NS","Eris.NS",
|
||||
"ABBOTINDIA.NS","PFIZER.NS"],
|
||||
"Defence": [ "HAL.NS", "BEL.NS", "MAZDOCK.NS", "DATAPATTNS.NS", "BDL.NS", "MIDHANI.NS", "BEML.NS",
|
||||
"COCHINSHIP.NS", "GRSE.NS", "SOLARINDS.NS", "BHARATFORG.NS", "ZENTEC.NS", "PARAS.NS", "ASTRAMICRO.NS",
|
||||
"IDEAFORGE.NS", "APOLLO.NS", "AVANTEL.NS"],
|
||||
"Energy": ["NTPC.NS","POWERGRID.NS","RELIANCE.NS","ONGC.NS","BPCL.NS","IOC.NS","GAIL.NS","TATAPOWER.NS",
|
||||
"ADANIGREEN.NS","ADANIPOWER.NS","SJVN.NS","IREDA.NS","SUZLON.NS",
|
||||
"KPIGREEN.NS","BORORENEW.NS","AWL.NS","JSWENERGY.NS"],
|
||||
"Chemical": ["SRF.NS","PIDILITIND.NS","LINDEINDIA.NS","TATACHEM.NS","AARTIIND.NS","ATUL.NS",
|
||||
"DEEPAKNTR.NS","GUJALKALI.NS","NAVINFLUOR.NS","FLUOROCHEM.NS","CLEAN.NS","GALAXYSURF.NS",
|
||||
"FINEORG.NS","CAMPUS.NS","ROSSARI.NS","SUDARSCHEM.NS","ALKYLAMINE.NS","BALAMINES.NS",
|
||||
"VINATIORGA.NS","NOCIL.NS"],
|
||||
|
||||
"fmcg":["HINDUNILVR.NS","ITC.NS","NESTLEIND.NS","BRITANNIA.NS","TATACONSUM.NS","DABUR.NS","MARICO.NS",
|
||||
"GODREJCP.NS","VBL.NS","COLPAL.NS","PGHH.NS","BALRAMCHIN.NS","EMAMILTD.NS","RADICO.NS","UBL.NS",
|
||||
"UNITDSPR.NS","GLAXO.NS","JYOTHYLAB.NS","BIKAJI.NS","ZYDUSWELL.NS"],
|
||||
"brkg":["ANANDRATHI.NS","NUVAMA.NS","PRUDENT.NS","ANGELONE.NS","MOTILALOFS.NS","GEOJITFSL.NS","5PAISA.NS",
|
||||
"SMCGLOBAL.NS"],
|
||||
"AMCReSt":["HDFCAMC.NS","NAM-INDIA.NS","UTIAMC.NS","ABSLAMC.NS","BAJFINANCE.NS","BAJAJFINSV.NS",
|
||||
"CHOLAFIN.NS","SHRIRAMFIN.NS","MUTHOOTFIN.NS","M&MFIN.NS","DLF.NS","LODHA.NS","GODREJPROP.NS",
|
||||
"OBEROIRLTY.NS","PRESTIGE.NS"],
|
||||
"InfaMetal":["IRB.NS","LT.NS","KNRCON.NS","PNCINFRA.NS","HGINFRA.NS","TATASTEEL.NS",
|
||||
"JSWSTEEL.NS","HINDALCO.NS","VEDL.NS","SAIL.NS","NMDC.NS","NATIONALUM.NS","JINDALSTEL.NS",
|
||||
"COALINDIA.NS"]
|
||||
}
|
||||
|
||||
# =========================================
|
||||
# ✅ FIX MULTI-INDEX
|
||||
# =========================================
|
||||
def fix_df(df):
|
||||
if isinstance(df.columns, pd.MultiIndex):
|
||||
df.columns = df.columns.get_level_values(0)
|
||||
return df
|
||||
|
||||
|
||||
# =========================================
|
||||
# ✅ SHORT TERM (LIVE)
|
||||
# =========================================
|
||||
def analyze(stock):
|
||||
try:
|
||||
|
||||
df = yf.download(
|
||||
stock,
|
||||
period="6mo",
|
||||
interval="1d",
|
||||
progress=False
|
||||
)
|
||||
|
||||
nifty = yf.download(
|
||||
"^NSEI",
|
||||
period="6mo",
|
||||
interval="1d",
|
||||
progress=False
|
||||
)
|
||||
|
||||
|
||||
if df is None or df.empty:
|
||||
return None
|
||||
|
||||
df = fix_df(df)
|
||||
df.dropna(inplace=True)
|
||||
|
||||
nifty = fix_df(nifty)
|
||||
nifty.dropna(inplace=True)
|
||||
|
||||
|
||||
close = df["Close"]
|
||||
high = df["High"]
|
||||
low = df["Low"]
|
||||
volume = df["Volume"].fillna(0)
|
||||
|
||||
if len(close) < 35:
|
||||
return None
|
||||
|
||||
price = float(close.iloc[-1])
|
||||
|
||||
ema20 = float(
|
||||
close.ewm(span=20).mean().iloc[-1]
|
||||
)
|
||||
|
||||
ema50 = float(
|
||||
close.ewm(span=50).mean().iloc[-1]
|
||||
)
|
||||
|
||||
|
||||
# RSI
|
||||
delta = close.diff()
|
||||
|
||||
gain = delta.clip(lower=0)
|
||||
loss = -delta.clip(upper=0)
|
||||
|
||||
avg_gain = gain.rolling(14).mean().iloc[-1]
|
||||
avg_loss = loss.rolling(14).mean().iloc[-1]
|
||||
|
||||
if avg_loss == 0:
|
||||
avg_loss = 0.0001
|
||||
|
||||
rs = avg_gain / avg_loss
|
||||
|
||||
rsi = float(
|
||||
100 - (100 / (1 + rs))
|
||||
)
|
||||
|
||||
# MACD
|
||||
exp1 = close.ewm(span=12, adjust=False).mean()
|
||||
exp2 = close.ewm(span=26, adjust=False).mean()
|
||||
macd = exp1 - exp2
|
||||
macd_signal_line = macd.ewm(span=9, adjust=False).mean()
|
||||
macd_hist = macd - macd_signal_line
|
||||
|
||||
macd_val = float(macd.iloc[-1])
|
||||
macd_hist_val = float(macd_hist.iloc[-1])
|
||||
|
||||
# ATR
|
||||
high_low = high - low
|
||||
high_close = (high - close.shift()).abs()
|
||||
low_close = (low - close.shift()).abs()
|
||||
true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
|
||||
atr = true_range.rolling(14).mean()
|
||||
atr_val = float(atr.iloc[-1]) if not pd.isna(atr.iloc[-1]) else (price * 0.03)
|
||||
|
||||
# Breakout
|
||||
resistance = float(
|
||||
close.iloc[-15:-1].max()
|
||||
)
|
||||
|
||||
breakout = (
|
||||
price >= resistance * 0.995
|
||||
)
|
||||
|
||||
# Volume
|
||||
avg_vol = float(
|
||||
volume.rolling(20).mean().iloc[-1]
|
||||
)
|
||||
|
||||
current_vol = float(
|
||||
volume.iloc[-1]
|
||||
)
|
||||
|
||||
vol_ratio = (
|
||||
current_vol / avg_vol
|
||||
if avg_vol > 0 else 0
|
||||
)
|
||||
|
||||
# ------------------------
|
||||
# ORIGINAL SCORE
|
||||
# ------------------------
|
||||
|
||||
score = 0
|
||||
|
||||
if price > ema20:
|
||||
score += 40
|
||||
|
||||
if rsi > 55:
|
||||
score += 30
|
||||
|
||||
if breakout:
|
||||
score += 30
|
||||
|
||||
# ------------------------
|
||||
# AI SCORE
|
||||
# ------------------------
|
||||
|
||||
ai_score = 0
|
||||
|
||||
# Trend
|
||||
if price > ema20:
|
||||
ai_score += 20
|
||||
|
||||
# RSI
|
||||
if rsi > 70:
|
||||
ai_score += 20
|
||||
elif rsi > 60:
|
||||
ai_score += 15
|
||||
elif rsi > 50:
|
||||
ai_score += 10
|
||||
|
||||
# Breakout
|
||||
if breakout:
|
||||
ai_score += 20
|
||||
|
||||
# Volume
|
||||
if vol_ratio > 3:
|
||||
ai_score += 20
|
||||
elif vol_ratio > 2:
|
||||
ai_score += 15
|
||||
elif vol_ratio > 1.5:
|
||||
ai_score += 10
|
||||
|
||||
# Institutional
|
||||
if vol_ratio > 3 and rsi > 60:
|
||||
ai_score += 10
|
||||
|
||||
# MACD bullish crossover
|
||||
if macd_hist_val > 0:
|
||||
ai_score += 15
|
||||
|
||||
# Recovery
|
||||
if price > ema20 and rsi > 50:
|
||||
ai_score += 10
|
||||
# ------------------------
|
||||
# RELATIVE STRENGTH
|
||||
# ------------------------
|
||||
relative_strength = 0
|
||||
|
||||
try:
|
||||
if len(close) >= 20 and len(nifty) >= 20:
|
||||
stock_return = (
|
||||
(float(close.iloc[-1]) - float(close.iloc[-20])) / float(close.iloc[-20])
|
||||
) * 100
|
||||
nifty_return = (
|
||||
(float(nifty["Close"].iloc[-1]) - float(nifty["Close"].iloc[-20])) / float(nifty["Close"].iloc[-20])
|
||||
) * 100
|
||||
relative_strength = round(stock_return - nifty_return, 2)
|
||||
except Exception:
|
||||
relative_strength = 0
|
||||
|
||||
# Relative Strength Bonus
|
||||
if relative_strength > 10:
|
||||
ai_score += 15
|
||||
elif relative_strength > 5:
|
||||
ai_score += 10
|
||||
elif relative_strength > 0:
|
||||
ai_score += 5
|
||||
ai_score = min(ai_score, 100)
|
||||
|
||||
# ------------------------
|
||||
# CONFIDENCE
|
||||
# ------------------------
|
||||
confidence = ai_score
|
||||
if relative_strength > 10:
|
||||
rs_grade = "Very Strong 🚀"
|
||||
elif relative_strength > 5:
|
||||
rs_grade = "Strong ✅"
|
||||
elif relative_strength > 0:
|
||||
rs_grade = "Positive 👍"
|
||||
else:
|
||||
rs_grade = "Weak ❌"
|
||||
|
||||
# ------------------------
|
||||
# GRADE
|
||||
# ------------------------
|
||||
if ai_score >= 90:
|
||||
grade = "A+ 🚀"
|
||||
elif ai_score >= 80:
|
||||
grade = "A ✅"
|
||||
elif ai_score >= 70:
|
||||
grade = "B 👍"
|
||||
elif ai_score >= 60:
|
||||
grade = "C"
|
||||
else:
|
||||
grade = "D"
|
||||
|
||||
# ------------------------
|
||||
# STRENGTH
|
||||
# ------------------------
|
||||
strength = round(((price - ema20) / ema20) * 100, 2)
|
||||
|
||||
# ------------------------
|
||||
# INSTITUTIONAL BUYING
|
||||
# ------------------------
|
||||
institutional = vol_ratio > 3 and rsi > 60 and breakout
|
||||
|
||||
# ------------------------
|
||||
# SMART MONEY SCORE
|
||||
# ------------------------
|
||||
institutional_score = 0
|
||||
if vol_ratio > 2:
|
||||
institutional_score += 25
|
||||
if vol_ratio > 3:
|
||||
institutional_score += 25
|
||||
if breakout:
|
||||
institutional_score += 25
|
||||
if rsi > 60:
|
||||
institutional_score += 25
|
||||
|
||||
if institutional_score >= 75:
|
||||
institutional_label = "Very Strong 🔥"
|
||||
elif institutional_score >= 50:
|
||||
institutional_label = "Strong ✅"
|
||||
elif institutional_score >= 25:
|
||||
institutional_label = "Moderate ⚠️"
|
||||
else:
|
||||
institutional_label = "Weak"
|
||||
|
||||
fund_candidate = vol_ratio > 4 and breakout and rsi > 60 and price > ema20
|
||||
|
||||
# ------------------------
|
||||
# ALERT
|
||||
# ------------------------
|
||||
alert = vol_ratio > 2.5 and breakout
|
||||
|
||||
# ------------------------
|
||||
# REASON ENGINE
|
||||
# ------------------------
|
||||
reason = []
|
||||
if vol_ratio > 3:
|
||||
reason.append("Institutional Buying 🔥")
|
||||
elif vol_ratio > 2:
|
||||
reason.append("Volume Breakout 🔥")
|
||||
if breakout:
|
||||
reason.append("Resistance Breakout 🚀")
|
||||
if rsi > 60:
|
||||
reason.append("Strong Momentum ⚡")
|
||||
if price > ema20:
|
||||
reason.append("Trend Up 📈")
|
||||
if price > ema20 and rsi > 50 and not breakout:
|
||||
reason.append("Recovery Move 🔄")
|
||||
|
||||
reason_text = ", ".join(reason) if reason else "Normal"
|
||||
|
||||
trend_reason = []
|
||||
|
||||
if price > ema20:
|
||||
trend_reason.append("Above EMA20")
|
||||
|
||||
if ema20 > ema50:
|
||||
trend_reason.append("EMA20 > EMA50")
|
||||
|
||||
if rsi > 50:
|
||||
trend_reason.append("Bullish RSI")
|
||||
|
||||
if relative_strength > 0:
|
||||
trend_reason.append("Outperforming Nifty")
|
||||
|
||||
trend_reason_text = ", ".join(trend_reason)
|
||||
# ------------------------
|
||||
# TREND SCANNER
|
||||
# ------------------------
|
||||
|
||||
trend_score = 0
|
||||
|
||||
if price > ema20:
|
||||
trend_score += 25
|
||||
|
||||
if ema20 > ema50:
|
||||
trend_score += 25
|
||||
|
||||
if rsi > 50:
|
||||
trend_score += 25
|
||||
|
||||
if relative_strength > 0:
|
||||
trend_score += 25
|
||||
|
||||
if trend_score >= 75:
|
||||
trend_signal = "TREND BUY ✅"
|
||||
elif trend_score >= 50:
|
||||
trend_signal = "TREND WATCH 👀"
|
||||
else:
|
||||
trend_signal = "WEAK ❌"
|
||||
|
||||
|
||||
return {
|
||||
"symbol": stock.replace(".NS", ""),
|
||||
"price": round(price, 2),
|
||||
"entry": round(price, 2),
|
||||
"sl": round(price - (1.5 * atr_val), 2),
|
||||
"trailing_sl": round(price - (1.0 * atr_val), 2),
|
||||
"target": round(price + (3.0 * atr_val), 2),
|
||||
"rsi": round(rsi, 2),
|
||||
"volume": int(current_vol),
|
||||
"avg_volume": int(avg_vol),
|
||||
"volume_ratio": round(vol_ratio, 2),
|
||||
"breakout": breakout,
|
||||
"score": score,
|
||||
"ai_score": ai_score,
|
||||
"confidence": confidence,
|
||||
"grade": grade,
|
||||
"strength": strength,
|
||||
"institutional": institutional,
|
||||
"institutional_score": institutional_score,
|
||||
"institutional_label": institutional_label,
|
||||
"fund_candidate": fund_candidate,
|
||||
"alert": alert,
|
||||
"reason": reason_text,
|
||||
"signal": "BUY ✅" if ai_score >= 70 else "AVOID ❌",
|
||||
"relative_strength": relative_strength,
|
||||
"rs_grade": rs_grade,
|
||||
"ema20": round(ema20, 2),
|
||||
"ema50": round(ema50, 2),
|
||||
"macd_hist": round(macd_hist_val, 2),
|
||||
"atr": round(atr_val, 2),
|
||||
|
||||
"trend_score": trend_score,
|
||||
"trend_signal": trend_signal,
|
||||
"trend_reason": trend_reason_text,
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Analyze error {stock}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
# =========================================
|
||||
# ✅ API - FAST SCAN
|
||||
# =========================================
|
||||
def get_scan():
|
||||
|
||||
sector_output = {}
|
||||
all_stocks = []
|
||||
|
||||
for sector, stocks in sector_map.items():
|
||||
|
||||
data = list(ThreadPoolExecutor(5).map(analyze, stocks))
|
||||
data = [x for x in data if x]
|
||||
|
||||
if data:
|
||||
data.sort(
|
||||
key=lambda x: (
|
||||
x["ai_score"],
|
||||
x["strength"]
|
||||
),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
sector_output[sector] = {
|
||||
"top_stock": data[0],
|
||||
"stocks": data
|
||||
}
|
||||
|
||||
all_stocks.extend(data)
|
||||
|
||||
# ✅ Global Ranking
|
||||
all_stocks.sort(
|
||||
key=lambda x: (
|
||||
x["ai_score"],
|
||||
x["strength"]
|
||||
),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
# ✅ Top 5 Trades
|
||||
best_5 = all_stocks[:10]
|
||||
|
||||
# ✅ Trade Of The Day
|
||||
trade_of_day = (
|
||||
all_stocks[0]
|
||||
if all_stocks
|
||||
else None
|
||||
)
|
||||
|
||||
return {
|
||||
"sectors": sector_output,
|
||||
"best_5": best_5,
|
||||
"trade_of_day": trade_of_day
|
||||
}
|
||||
|
||||
|
||||
# =========================================
|
||||
# ✅ EMA RIBBON (FAST VERSION)
|
||||
# =========================================
|
||||
def analyze_ribbon(stock):
|
||||
try:
|
||||
df = yf.download(stock, period="6mo", interval="1d", progress=False)
|
||||
|
||||
if df is None or df.empty:
|
||||
return None
|
||||
|
||||
df = fix_df(df)
|
||||
df.dropna(inplace=True)
|
||||
|
||||
high = df["High"]
|
||||
low = df["Low"]
|
||||
close = df["Close"]
|
||||
|
||||
# ✅ REMOVE strict filter ❌
|
||||
# if len(close) < 50:
|
||||
# return None
|
||||
|
||||
ema_high_5 = high.ewm(span=5).mean()
|
||||
ema_low_5 = low.ewm(span=5).mean()
|
||||
ema_high_100 = high.ewm(span=50).mean() # faster
|
||||
ema_low_100 = low.ewm(span=50).mean()
|
||||
ema_close_100 = close.ewm(span=50).mean()
|
||||
|
||||
price = float(close.iloc[-1])
|
||||
|
||||
eh5 = float(ema_high_5.iloc[-1])
|
||||
el5 = float(ema_low_5.iloc[-1])
|
||||
eh100 = float(ema_high_100.iloc[-1])
|
||||
el100 = float(ema_low_100.iloc[-1])
|
||||
ec100 = float(ema_close_100.iloc[-1])
|
||||
|
||||
# ✅ ALWAYS RETURN SIGNAL (NO FILTERING)
|
||||
if price > ec100 and eh5 > eh100:
|
||||
signal = "BUY 🚀"
|
||||
elif price < ec100:
|
||||
signal = "SELL ❌"
|
||||
else:
|
||||
signal = "SIDEWAYS ⚠️"
|
||||
|
||||
return {
|
||||
"symbol": stock.replace(".NS",""),
|
||||
"price": round(price, 2),
|
||||
"ema_high_5": round(eh5, 2),
|
||||
"ema_low_5": round(el5, 2),
|
||||
"ema_high_100": round(eh100, 2),
|
||||
"ema_low_100": round(el100, 2),
|
||||
"ema_close_100": round(ec100, 2),
|
||||
"signal": signal
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print("Ribbon error:", stock, e)
|
||||
return None
|
||||
|
||||
|
||||
def get_ribbon():
|
||||
|
||||
results = []
|
||||
|
||||
for stocks in sector_map.values():
|
||||
data = list(ThreadPoolExecutor(5).map(analyze_ribbon, stocks))
|
||||
data = [x for x in data if x]
|
||||
results.extend(data)
|
||||
|
||||
return results
|
||||
|
||||
def get_smartmoney():
|
||||
|
||||
results = []
|
||||
|
||||
for sector, stocks in sector_map.items():
|
||||
|
||||
data = list(ThreadPoolExecutor(5).map(analyze, stocks))
|
||||
data = [x for x in data if x]
|
||||
|
||||
results.extend(data)
|
||||
|
||||
|
||||
results.sort(
|
||||
key=lambda x: (
|
||||
x.get("institutional_score", 0),
|
||||
x.get("ai_score", 0),
|
||||
x.get("strength", 0)
|
||||
),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
|
||||
return {
|
||||
"trade_of_day": results[0] if results else None,
|
||||
"top_10": results[:10],
|
||||
"all": results
|
||||
}
|
||||
|
||||
def get_trend():
|
||||
|
||||
results = []
|
||||
|
||||
for sector, stocks in sector_map.items():
|
||||
|
||||
data = list(ThreadPoolExecutor(5).map(analyze, stocks))
|
||||
data = [x for x in data if x]
|
||||
|
||||
results.extend(data)
|
||||
|
||||
results.sort(
|
||||
key=lambda x: (
|
||||
x.get("trend_score", 0),
|
||||
x.get("relative_strength", 0),
|
||||
x.get("ai_score", 0)
|
||||
),
|
||||
reverse=True
|
||||
)
|
||||
|
||||
return {
|
||||
"top_20": results[:20]
|
||||
}
|
||||
|
||||
# =========================================
|
||||
# ❌ HEAVY CODE (DISABLED FOR SPEED)
|
||||
# =========================================
|
||||
|
||||
# - Long-term strategy (disabled)
|
||||
# - Full sector_map (too large)
|
||||
# - Deep MACD + Supertrend
|
||||
# - 6 month data calls
|
||||
def get_marketintel():
|
||||
try:
|
||||
nifty_news = yf.Ticker("^NSEI").news or []
|
||||
reliance_news = yf.Ticker("RELIANCE.NS").news or []
|
||||
|
||||
# Extract titles and links
|
||||
all_news = []
|
||||
for n in nifty_news + reliance_news:
|
||||
if n.get("content", {}).get("title"):
|
||||
all_news.append({
|
||||
"title": n["content"]["title"],
|
||||
"link": n["content"].get("clickThroughUrl", {}).get("url", "#"),
|
||||
"provider": n.get("provider", {}).get("displayName", "Yahoo Finance"),
|
||||
"pubDate": n.get("content", {}).get("pubDate", "")
|
||||
})
|
||||
|
||||
# Remove duplicates
|
||||
seen = set()
|
||||
unique_news = []
|
||||
for n in all_news:
|
||||
if n["title"] not in seen:
|
||||
unique_news.append(n)
|
||||
seen.add(n["title"])
|
||||
|
||||
return {
|
||||
"top_news": unique_news[:10],
|
||||
"block_deals": [
|
||||
{"stock": "HDFCBANK", "buyer": "FII", "value": "₹1200 Cr"},
|
||||
{"stock": "RAMCOSYS", "buyer": "Institution", "value": "₹50 Cr"}
|
||||
],
|
||||
"bulk_deals": [
|
||||
{"stock": "CGPOWER", "buyer": "Investor", "shares": "10 Lakh"},
|
||||
{"stock": "SUZLON", "buyer": "DII", "shares": "50 Lakh"}
|
||||
],
|
||||
"fii_activity": [
|
||||
{"stock": "HAL", "activity": "Net Buying"},
|
||||
{"stock": "RELIANCE", "activity": "Net Buying"}
|
||||
],
|
||||
"dii_activity": [
|
||||
{"stock": "BEL", "activity": "Net Buying"},
|
||||
{"stock": "INFY", "activity": "Net Selling"}
|
||||
],
|
||||
"promoter_activity": [
|
||||
{"stock": "HFCL", "activity": "Stake Increased"},
|
||||
{"stock": "ADANIENT", "activity": "Stake Increased"}
|
||||
]
|
||||
}
|
||||
except Exception as e:
|
||||
print("MarketIntel error:", e)
|
||||
return {
|
||||
"top_news": [{"title": "Results season updates", "link": "#", "provider": "Local", "pubDate": ""}],
|
||||
"block_deals": [], "bulk_deals": [], "fii_activity": [], "dii_activity": [], "promoter_activity": []
|
||||
}
|
||||
Reference in New Issue
Block a user