Research View

This commit is contained in:
2026-08-08 08:03:46 +05:30
parent e4ad2233b8
commit f33011b219
9 changed files with 208 additions and 35 deletions

View File

@@ -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}

View File

@@ -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 (
<div className="app-container">
{/* HEADER */}
@@ -119,6 +142,7 @@ function Dashboard() {
onSelectStock={setSelectedStock}
onSignal={setSignal}
onBuyClick={onBuyClick}
onResearchClick={onResearchClick}
/>
<div className="glass-card" style={{ marginTop: "20px" }}>
<h2>TradingView Chart</h2>
@@ -127,12 +151,12 @@ function Dashboard() {
{signal && <Popup signal={signal} />}
</>
)}
{view === "dashboard" && <MainDashboard userProfile={userProfile} onBuyClick={onBuyClick} />}
{view === "ribbon" && <Ribbon onBuyClick={onBuyClick} />}
{view === "smartmoney" && <SmartMoney onBuyClick={onBuyClick} />}
{view === "marketintel" && <MarketIntel onBuyClick={onBuyClick} />}
{view === "trend" && <TrendScanner onBuyClick={onBuyClick} />}
{view === "watchlists" && <WatchlistManager userProfile={userProfile} onProfileUpdated={fetchProfile} onBuyClick={onBuyClick} />}
{view === "dashboard" && <MainDashboard userProfile={userProfile} onBuyClick={onBuyClick} onResearchClick={onResearchClick} />}
{view === "ribbon" && <Ribbon onBuyClick={onBuyClick} onResearchClick={onResearchClick} />}
{view === "smartmoney" && <SmartMoney onBuyClick={onBuyClick} onResearchClick={onResearchClick} />}
{view === "marketintel" && <MarketIntel onBuyClick={onBuyClick} onResearchClick={onResearchClick} />}
{view === "trend" && <TrendScanner onBuyClick={onBuyClick} onResearchClick={onResearchClick} />}
{view === "watchlists" && <WatchlistManager userProfile={userProfile} onProfileUpdated={fetchProfile} onBuyClick={onBuyClick} onResearchClick={onResearchClick} />}
{view === "profile" && <Profile userProfile={userProfile} onProfileUpdated={fetchProfile} />}
</div>
@@ -181,6 +205,78 @@ function Dashboard() {
</div>
</div>
)}
{/* Research Modal */}
{researchModalData && (
<div style={{
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
backgroundColor: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(5px)',
display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999
}}>
<div style={{
background: 'var(--glass-bg)', padding: '30px', borderRadius: '15px',
border: '1px solid var(--border-color)', width: 'min(400px, 90vw)', textAlign: 'center',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)'
}}>
<h2 style={{ marginBottom: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px' }}>
<span>🔍</span> Analyst Research
</h2>
<h3 style={{ color: 'var(--accent-blue)', margin: '10px 0', fontSize: '1.8rem' }}>{researchModalData.symbol}</h3>
{/* Analyst Recommendations */}
<div style={{ margin: '20px 0', padding: '15px', background: 'var(--bg-secondary)', borderRadius: '10px', border: '1px solid var(--border-color)', textAlign: 'left' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
<span style={{ fontSize: '0.95rem', fontWeight: 'bold', color: 'var(--text-primary)' }}>Analyst Consensus</span>
{buyModalRecsLoading && <span style={{ fontSize: '0.8rem', color: 'var(--text-secondary)' }}>Analyzing...</span>}
{buyModalRecs && !buyModalRecs.error && (
<span className={`badge ${buyModalRecs.confidence >= 70 ? 'badge-success' : buyModalRecs.confidence >= 40 ? 'badge-warning' : 'badge-danger'}`} style={{ fontSize: '0.9rem' }}>
{buyModalRecs.confidence}% Confidence
</span>
)}
</div>
{buyModalRecs && buyModalRecs.error && (
<div style={{ fontSize: '0.9rem', color: 'var(--text-secondary)', textAlign: 'center', padding: '10px 0' }}>
No sufficient analyst coverage found.
</div>
)}
{buyModalRecs && !buyModalRecs.error && (
<div style={{ display: 'flex', gap: '8px', fontSize: '0.85rem', textAlign: 'center' }}>
{(buyModalRecs.strongBuy + buyModalRecs.buy) > 0 && (
<div style={{ flex: buyModalRecs.strongBuy + buyModalRecs.buy, background: 'var(--accent-green-bg)', color: 'var(--accent-green)', padding: '8px 4px', borderRadius: '6px', fontWeight: 'bold' }}>
Buy<br/>({buyModalRecs.strongBuy + buyModalRecs.buy})
</div>
)}
{buyModalRecs.hold > 0 && (
<div style={{ flex: buyModalRecs.hold, background: 'var(--accent-orange-bg)', color: 'var(--accent-orange)', padding: '8px 4px', borderRadius: '6px', fontWeight: 'bold' }}>
Hold<br/>({buyModalRecs.hold})
</div>
)}
{(buyModalRecs.sell + buyModalRecs.strongSell) > 0 && (
<div style={{ flex: buyModalRecs.sell + buyModalRecs.strongSell, background: 'var(--accent-red-bg)', color: 'var(--accent-red)', padding: '8px 4px', borderRadius: '6px', fontWeight: 'bold' }}>
Sell<br/>({buyModalRecs.sell + buyModalRecs.strongSell})
</div>
)}
</div>
)}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px', marginTop: '25px' }}>
<a
href={`https://trendlyne.com/research-reports/stock/${researchModalData.symbol.replace('.NS', '')}`}
target="_blank"
rel="noreferrer"
className="action-btn"
style={{ padding: '12px', background: 'var(--bg-tertiary)', color: 'var(--text-primary)', border: '1px solid var(--border-color)', fontWeight: 'bold', textDecoration: 'none' }}
>
Read Detailed Trendlyne Reports
</a>
<button className="nav-btn" style={{ padding: '12px', margin: 0, justifyContent: 'center' }} onClick={() => setResearchModalData(null)}>Close</button>
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -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 }) {
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '15px' }}>
<button
className="action-btn"
style={{ background: 'var(--accent-green)', color: '#1a1a2e', padding: '4px 10px', fontSize: '0.8rem', fontWeight: 'bold', borderRadius: '8px', border: 'none', cursor: 'pointer' }}
onClick={(e) => { e.stopPropagation(); onBuyClick(result.symbol, result.currentPrice); }}
style={{ background: 'linear-gradient(135deg, var(--accent-blue), var(--accent-green))', color: '#1a1a2e', padding: '6px 12px', borderRadius: '8px', border: 'none', fontWeight: 'bold', cursor: 'pointer' }}
>
Buy
</button>
<button
className="action-btn"
style={{ background: 'transparent', color: 'var(--accent-blue)', border: '1px solid var(--accent-blue)', padding: '4px 10px', fontSize: '0.8rem', fontWeight: 'bold', borderRadius: '8px', cursor: 'pointer' }}
onClick={(e) => { e.stopPropagation(); onResearchClick(result.symbol); }}
>
Research
</button>
<div style={{ color: 'var(--accent-green)', display: 'flex', alignItems: 'center', gap: '5px' }}>
<span>Chart</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">

View File

@@ -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 }) {
<td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<button
className="action-btn"
style={{ background: 'var(--accent-green)', color: '#1a1a2e', padding: '4px 8px', fontSize: '0.8rem' }}
onClick={() => onBuyClick(s.symbol, s.price)}
style={{ background: 'var(--accent-green)', color: '#1a1a2e', border: 'none', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Buy
</button>
<button
onClick={() => onResearchClick(s.symbol)}
style={{ background: 'transparent', color: 'var(--accent-blue)', border: '1px solid var(--accent-blue)', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Research
</button>
<button className="action-btn" onClick={() => openChart(s.symbol)}>
Chart
</button>

View File

@@ -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
</button>
<button
onClick={() => onResearchClick(s?.symbol)}
style={{ background: 'transparent', color: 'var(--accent-blue)', border: '1px solid var(--accent-blue)', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Research
</button>
{s?.sector && (
<button
onClick={() => removeStockFromSector(s?.symbol, s.sector)}

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
function SmartMoney({ onBuyClick }) {
function SmartMoney({ onBuyClick, onResearchClick }) {
const [data, setData] = useState(null);
useEffect(() => {
@@ -104,13 +104,18 @@ function SmartMoney({ onBuyClick }) {
<td>{s.reason}</td>
<td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<button
className="action-btn"
style={{ background: 'var(--accent-green)', color: '#1a1a2e', padding: '4px 8px', fontSize: '0.8rem' }}
onClick={() => onBuyClick(s.symbol, s.price || s.current_price || 0)}
>
Buy
</button>
<button
onClick={() => onBuyClick(s.symbol, s.price || s.current_price || 0)}
style={{ background: 'var(--accent-green)', color: '#1a1a2e', border: 'none', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Buy
</button>
<button
onClick={() => onResearchClick(s.symbol)}
style={{ background: 'transparent', color: 'var(--accent-blue)', border: '1px solid var(--accent-blue)', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Research
</button>
<button className="action-btn" onClick={() => openChart(s.symbol)}>Chart</button>
{s.sector && (
<button

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
function TrendScanner({ onBuyClick }) {
function TrendScanner({ onBuyClick, onResearchClick }) {
const [data, setData] = useState(null);
useEffect(() => {
@@ -81,13 +81,18 @@ function TrendScanner({ onBuyClick }) {
<td>{s.trend_reason}</td>
<td>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<button
className="action-btn"
style={{ background: 'var(--accent-green)', color: '#1a1a2e', padding: '4px 8px', fontSize: '0.8rem' }}
onClick={() => onBuyClick(s.symbol, s.price || s.current_price || 0)}
>
Buy
</button>
<button
onClick={() => onBuyClick(s.symbol, s.price || s.current_price || 0)}
style={{ background: 'var(--accent-green)', color: '#1a1a2e', border: 'none', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Buy
</button>
<button
onClick={() => onResearchClick(s.symbol)}
style={{ background: 'transparent', color: 'var(--accent-blue)', border: '1px solid var(--accent-blue)', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Research
</button>
<button className="action-btn" onClick={() => openChart(s.symbol)}>Chart</button>
{s.sector && (
<button

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
function WatchlistManager({ userProfile, onProfileUpdated, onBuyClick }) {
function WatchlistManager({ userProfile, onProfileUpdated, onBuyClick, onResearchClick }) {
const [sectorMap, setSectorMap] = useState({});
const [newSectorName, setNewSectorName] = useState("");
const [searchQuery, setSearchQuery] = useState("");
@@ -267,12 +267,18 @@ function WatchlistManager({ userProfile, onProfileUpdated, onBuyClick }) {
+ Personal
</button>
)}
<button
onClick={() => onBuyClick(symbol, 0)}
style={{ flex: 1, background: 'var(--accent-green)', color: '#1a1a2e', border: 'none', borderRadius: '4px', cursor: 'pointer', padding: '6px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Buy
</button>
<button
onClick={() => onBuyClick(symbol, 0)}
style={{ background: 'var(--accent-green)', color: '#1a1a2e', border: 'none', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Buy
</button>
<button
onClick={() => onResearchClick(symbol)}
style={{ background: 'transparent', color: 'var(--accent-blue)', border: '1px solid var(--accent-blue)', borderRadius: '4px', cursor: 'pointer', padding: '6px 12px', fontSize: '0.85rem', fontWeight: 'bold' }}
>
Research
</button>
</div>
</div>
))}