211 lines
8.8 KiB
JavaScript
211 lines
8.8 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
|
import axios from "axios";
|
|
import AboutScanner from "./AboutScanner";
|
|
|
|
function Scanner({ onSelectStock, onSignal, onBuyClick }) {
|
|
const [data, setData] = useState(null);
|
|
const [lastBest, setLastBest] = useState(null);
|
|
|
|
const loadData = async () => {
|
|
try {
|
|
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;
|
|
|
|
if (best && best.symbol !== lastBest) {
|
|
if (onSignal) onSignal(`New Best Trade: ${best.symbol}`);
|
|
setLastBest(best.symbol);
|
|
}
|
|
|
|
setData(newData);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
const interval = setInterval(loadData, 100000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const openChart = (symbol) => {
|
|
window.open(`https://www.tradingview.com/chart/?symbol=NSE:${symbol}`, "_blank");
|
|
};
|
|
|
|
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 (
|
|
<>
|
|
<AboutScanner isLoaded={false} />
|
|
<div className="glass-card" style={{ textAlign: "center" }}>Loading Scanner Data (this may take up to 60 seconds for large watchlists)...</div>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
<AboutScanner isLoaded={true} />
|
|
|
|
{/* Top 5 Trades */}
|
|
{(data.best_5 || []).length > 0 && (
|
|
<div className="glass-card animate-fade-in" style={{ marginBottom: "30px", borderLeft: "4px solid var(--accent-green)" }}>
|
|
<h2 className="text-green">🔥 Top 10 Momentum Picks</h2>
|
|
<div className="data-table-container">
|
|
<table className="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Stock</th>
|
|
<th>Price</th>
|
|
<th>RSI</th>
|
|
<th>MACD</th>
|
|
<th>Signal</th>
|
|
<th>AI Score</th>
|
|
<th>Grade</th>
|
|
<th>Institutional</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{(data.best_5 || []).filter(s => s).map((s, i) => (
|
|
<tr key={i}>
|
|
<td style={{ fontWeight: 600 }}>{s?.symbol}</td>
|
|
<td>₹{s?.price}</td>
|
|
<td><span className={`badge ${s?.rsi > 60 ? 'badge-success' : 'badge-neutral'}`}>{s?.rsi}</span></td>
|
|
<td><span className={`badge ${s?.macd_hist > 0 ? 'badge-success' : 'badge-danger'}`}>{s?.macd_hist}</span></td>
|
|
<td>
|
|
<span className={`badge ${s?.signal?.includes("BUY") ? 'badge-success' : 'badge-danger'}`}>
|
|
{s?.signal}
|
|
</span>
|
|
</td>
|
|
<td>{s.ai_score} / 100</td>
|
|
<td>{s.grade}</td>
|
|
<td>{s.institutional ? <span className="text-orange">🔥 Yes</span> : <span className="text-muted">No</span>}</td>
|
|
<td>
|
|
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
<button className="action-btn" onClick={() => { openChart(s?.symbol); onSelectStock(s?.symbol); }}>
|
|
View Chart
|
|
</button>
|
|
<button
|
|
onClick={() => onBuyClick(s?.symbol, s?.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>
|
|
{s?.sector && (
|
|
<button
|
|
onClick={() => removeStockFromSector(s?.symbol, s.sector)}
|
|
title={`Remove from ${s.sector}`}
|
|
style={{ background: 'none', border: 'none', color: 'var(--accent-red)', cursor: 'pointer', padding: '0', display: 'flex', alignItems: 'center' }}
|
|
>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M3 6h18"></path>
|
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
</svg>
|
|
</button>
|
|
)}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Sector Tables */}
|
|
{Object.entries(data.sectors || {}).map(([sector, info]) => {
|
|
const best = info.top_stock;
|
|
return (
|
|
<div key={sector} className="glass-card animate-fade-in" style={{ marginBottom: "20px" }}>
|
|
<h2 className="text-blue">{sector} ({info.stocks.length} Stocks)</h2>
|
|
{best && (
|
|
<p className="text-orange" style={{ marginBottom: "20px" }}>
|
|
<strong>Top Pick:</strong> {best.symbol} (Score: {best.score})
|
|
</p>
|
|
)}
|
|
|
|
<div className="data-table-container">
|
|
<table className="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Stock</th>
|
|
<th>Price</th>
|
|
<th>MACD</th>
|
|
<th>Signal</th>
|
|
<th>Volume</th>
|
|
<th>Target</th>
|
|
<th>AI Score</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{(info.stocks || []).map((s, i) => (
|
|
<tr key={i}>
|
|
<td style={{ fontWeight: 500 }}>{s?.symbol}</td>
|
|
<td>₹{s?.price}</td>
|
|
<td><span className={`badge ${s?.macd_hist > 0 ? 'badge-success' : 'badge-danger'}`}>{s?.macd_hist}</span></td>
|
|
<td>
|
|
<span className={`badge ${s?.signal?.includes("BUY") ? 'badge-success' : 'badge-danger'}`}>
|
|
{s?.signal}
|
|
</span>
|
|
</td>
|
|
<td>{s?.volume?.toLocaleString()}</td>
|
|
<td className="text-green">₹{s?.target}</td>
|
|
<td>{s?.ai_score}</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)}
|
|
>
|
|
Buy
|
|
</button>
|
|
<button className="action-btn" onClick={() => { openChart(s?.symbol); onSelectStock(s?.symbol); }}>
|
|
Chart
|
|
</button>
|
|
<button
|
|
onClick={() => removeStockFromSector(s?.symbol, sector)}
|
|
title={`Remove from ${sector}`}
|
|
style={{ background: 'none', border: 'none', color: 'var(--accent-red)', cursor: 'pointer', padding: '0', display: 'flex', alignItems: 'center' }}
|
|
>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M3 6h18"></path>
|
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Scanner; |