diff --git a/backend/app/api/__pycache__/portfolio.cpython-313.pyc b/backend/app/api/__pycache__/portfolio.cpython-313.pyc index 4e2d38e..51165e0 100644 Binary files a/backend/app/api/__pycache__/portfolio.cpython-313.pyc and b/backend/app/api/__pycache__/portfolio.cpython-313.pyc differ diff --git a/backend/app/api/portfolio.py b/backend/app/api/portfolio.py index 1dfc880..7d2eeaf 100644 --- a/backend/app/api/portfolio.py +++ b/backend/app/api/portfolio.py @@ -100,3 +100,45 @@ def delete_portfolio_item(item_id: int, db: Session = Depends(get_db), current_u db.delete(item) db.commit() return {"message": "Stock sold/removed successfully"} + +@router.get("/recommendations/{symbol}") +def get_recommendations(symbol: str, current_user: User = Depends(get_current_user)): + try: + ticker = yf.Ticker(symbol) + recs = ticker.recommendations + + if recs is None or recs.empty: + return {"error": "No recommendation data available", "confidence": 0} + + # Get the most recent month's data (period '0m' is usually index 0) + latest = recs.iloc[0] + + strong_buy = int(latest.get("strongBuy", 0)) + buy = int(latest.get("buy", 0)) + hold = int(latest.get("hold", 0)) + sell = int(latest.get("sell", 0)) + strong_sell = int(latest.get("strongSell", 0)) + + total = strong_buy + buy + hold + sell + strong_sell + + if total == 0: + return {"error": "No recommendations found", "confidence": 0} + + # Calculate confidence score + # Strong Buy = 100, Buy = 75, Hold = 50, Sell = 25, Strong Sell = 0 + score = (strong_buy * 100 + buy * 75 + hold * 50 + sell * 25) / total + + return { + "symbol": symbol, + "period": str(latest.get("period", "0m")), + "strongBuy": strong_buy, + "buy": buy, + "hold": hold, + "sell": sell, + "strongSell": strong_sell, + "total": total, + "confidence": round(score, 1) + } + except Exception as e: + print(f"Error fetching recommendations for {symbol}: {e}") + return {"error": str(e), "confidence": 0} diff --git a/src/App.js b/src/App.js index fd2da1b..00c9a14 100644 --- a/src/App.js +++ b/src/App.js @@ -25,6 +25,9 @@ function Dashboard() { const [dropdownOpen, setDropdownOpen] = useState(false); const [scannerLoaded, setScannerLoaded] = useState(false); const [buyModalData, setBuyModalData] = useState(null); + const [researchModalData, setResearchModalData] = useState(null); + const [buyModalRecs, setBuyModalRecs] = useState(null); + const [buyModalRecsLoading, setBuyModalRecsLoading] = useState(false); const [buyQuantity, setBuyQuantity] = useState(1); const navigate = useNavigate(); @@ -45,6 +48,22 @@ function Dashboard() { navigate("/login"); }; + useEffect(() => { + if (researchModalData) { + setBuyModalRecs(null); + setBuyModalRecsLoading(true); + axios.get(`/api/portfolio/recommendations/${researchModalData.symbol}`) + .then(res => { + setBuyModalRecs(res.data); + setBuyModalRecsLoading(false); + }) + .catch(err => { + console.error("Failed to fetch recs", err); + setBuyModalRecsLoading(false); + }); + } + }, [researchModalData]); + const handleBuySubmit = async () => { if (!buyModalData || buyQuantity < 1) return; try { @@ -66,6 +85,10 @@ function Dashboard() { setBuyQuantity(1); }; + const onResearchClick = (symbol) => { + setResearchModalData({ symbol }); + }; + return (
{/* HEADER */} @@ -119,6 +142,7 @@ function Dashboard() { onSelectStock={setSelectedStock} onSignal={setSignal} onBuyClick={onBuyClick} + onResearchClick={onResearchClick} />

TradingView Chart

@@ -127,12 +151,12 @@ function Dashboard() { {signal && } )} - {view === "dashboard" && } - {view === "ribbon" && } - {view === "smartmoney" && } - {view === "marketintel" && } - {view === "trend" && } - {view === "watchlists" && } + {view === "dashboard" && } + {view === "ribbon" && } + {view === "smartmoney" && } + {view === "marketintel" && } + {view === "trend" && } + {view === "watchlists" && } {view === "profile" && }
@@ -181,6 +205,78 @@ function Dashboard() {
)} + + {/* Research Modal */} + {researchModalData && ( +
+
+

+ 🔍 Analyst Research +

+

{researchModalData.symbol}

+ + {/* Analyst Recommendations */} +
+
+ Analyst Consensus + {buyModalRecsLoading && Analyzing...} + {buyModalRecs && !buyModalRecs.error && ( + = 70 ? 'badge-success' : buyModalRecs.confidence >= 40 ? 'badge-warning' : 'badge-danger'}`} style={{ fontSize: '0.9rem' }}> + {buyModalRecs.confidence}% Confidence + + )} +
+ + {buyModalRecs && buyModalRecs.error && ( +
+ No sufficient analyst coverage found. +
+ )} + + {buyModalRecs && !buyModalRecs.error && ( +
+ {(buyModalRecs.strongBuy + buyModalRecs.buy) > 0 && ( +
+ Buy
({buyModalRecs.strongBuy + buyModalRecs.buy}) +
+ )} + {buyModalRecs.hold > 0 && ( +
+ Hold
({buyModalRecs.hold}) +
+ )} + {(buyModalRecs.sell + buyModalRecs.strongSell) > 0 && ( +
+ Sell
({buyModalRecs.sell + buyModalRecs.strongSell}) +
+ )} +
+ )} +
+ +
+ + Read Detailed Trendlyne Reports ↗ + + +
+
+
+ )} ); } diff --git a/src/components/MainDashboard.js b/src/components/MainDashboard.js index 54395c2..3dc3903 100644 --- a/src/components/MainDashboard.js +++ b/src/components/MainDashboard.js @@ -1,7 +1,7 @@ import React, { useState, useRef, useEffect } from "react"; import axios from "axios"; -function MainDashboard({ userProfile, onBuyClick }) { +function MainDashboard({ userProfile, onBuyClick, onResearchClick }) { const [searchQuery, setSearchQuery] = useState(""); const [searchResults, setSearchResults] = useState([]); const [searching, setSearching] = useState(false); @@ -164,11 +164,19 @@ function MainDashboard({ userProfile, onBuyClick }) {
+
Chart diff --git a/src/components/Ribbon.js b/src/components/Ribbon.js index e1b1296..c83864e 100644 --- a/src/components/Ribbon.js +++ b/src/components/Ribbon.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; -function Ribbon({ onBuyClick }) { +function Ribbon({ onBuyClick, onResearchClick }) { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); @@ -78,12 +78,17 @@ function Ribbon({ onBuyClick }) {
+ diff --git a/src/components/Scanner.js b/src/components/Scanner.js index 623f57d..25d5380 100644 --- a/src/components/Scanner.js +++ b/src/components/Scanner.js @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; import AboutScanner from "./AboutScanner"; -function Scanner({ onSelectStock, onSignal, onBuyClick }) { +function Scanner({ onSelectStock, onSignal, onBuyClick, onResearchClick }) { const [data, setData] = useState(null); const [lastBest, setLastBest] = useState(null); @@ -110,6 +110,12 @@ function Scanner({ onSelectStock, onSignal, onBuyClick }) { > Buy + {s?.sector && ( + + {s.sector && ( + + {s.sector && ( + +
))}