import React, { useState, useEffect, useMemo } from 'react'; import { LineChart, Wallet, Activity, AlertTriangle, CheckCircle, XCircle, Settings, FileText, Play, Pause, RefreshCw, Bell, ArrowRight, TrendingUp, Download, ShieldAlert } from 'lucide-react'; // --- Types & Interfaces --- interface StockData { symbol: string; name: string; tier: 'A' | 'B' | 'C'; price: number; sma200: number; ema50: number; rsi: number; active: boolean; prevDayHigh: number; lastUpdated: string; } interface TradeOrder { id: string; symbol: string; entryPrice: number; stopLoss: number; target: number; quantity: number; riskAmount: number; status: 'PENDING_CONFIRMATION' | 'OPEN' | 'CLOSED' | 'REJECTED'; dateCreated: string; exitPrice?: number; pnl?: number; notes?: string; rulesFollowed?: boolean; } interface AppSettings { capital: number; riskPerTradePercent: number; maxTradesPerWeek: number; stopTradingTrigger: number; // consecutive losses } // --- Mock Initial Data (Indian Market Context) --- // Helper to generate mock data for the 50 stocks const generateMockStock = (symbol: string, name: string, tier: 'A' | 'B' | 'C') => { const basePrice = Math.random() * 3000 + 500; // Randomize conditions to create a mix of eligible/ineligible for demo const isEligibleCandidate = Math.random() > 0.7; const sma200 = basePrice; const price = isEligibleCandidate ? basePrice * (1 + (Math.random() * 0.015)) // 0-1.5% above SMA200 (Eligible condition) : basePrice * (1 + (Math.random() * 0.1 - 0.05)); // Random +/- 5% const rsi = isEligibleCandidate ? 40 + Math.random() * 5 // 40-45 (Eligible condition) : 30 + Math.random() * 40; // 30-70 return { symbol, name, tier, price: parseFloat(price.toFixed(2)), sma200: parseFloat(sma200.toFixed(2)), ema50: parseFloat((price * 0.98).toFixed(2)), rsi: parseFloat(rsi.toFixed(1)), active: Math.random() > 0.5, prevDayHigh: parseFloat((price * 1.01).toFixed(2)), lastUpdated: new Date().toISOString() }; }; const INITIAL_STOCKS: StockData[] = [ generateMockStock('ADANIENT', 'Adani Enterprises', 'B'), generateMockStock('ADANIPORTS', 'Adani Ports', 'B'), generateMockStock('APOLLOHOSP', 'Apollo Hospitals', 'C'), generateMockStock('ASIANPAINT', 'Asian Paints', 'A'), generateMockStock('AXISBANK', 'Axis Bank', 'A'), generateMockStock('BAJAJ-AUTO', 'Bajaj Auto', 'B'), generateMockStock('BAJFINANCE', 'Bajaj Finance', 'A'), generateMockStock('BAJAJFINSV', 'Bajaj Finserv', 'B'), generateMockStock('BEL', 'Bharat Electronics', 'B'), generateMockStock('BPCL', 'BPCL', 'C'), generateMockStock('BHARTIARTL', 'Bharti Airtel', 'A'), generateMockStock('BRITANNIA', 'Britannia', 'C'), generateMockStock('CIPLA', 'Cipla', 'B'), generateMockStock('COALINDIA', 'Coal India', 'C'), generateMockStock('DIVISLAB', 'Divis Labs', 'C'), generateMockStock('DRREDDY', 'Dr Reddys Labs', 'B'), generateMockStock('EICHERMOT', 'Eicher Motors', 'B'), generateMockStock('GRASIM', 'Grasim Industries', 'B'), generateMockStock('HCLTECH', 'HCL Technologies', 'A'), generateMockStock('HDFCBANK', 'HDFC Bank', 'A'), generateMockStock('HDFCLIFE', 'HDFC Life', 'B'), generateMockStock('HEROMOTOCO', 'Hero MotoCorp', 'B'), generateMockStock('HINDALCO', 'Hindalco', 'C'), generateMockStock('HINDUNILVR', 'Hindustan Unilever', 'A'), generateMockStock('ICICIBANK', 'ICICI Bank', 'A'), generateMockStock('INDUSINDBK', 'IndusInd Bank', 'B'), generateMockStock('INFY', 'Infosys', 'A'), generateMockStock('ITC', 'ITC Ltd', 'A'), generateMockStock('JSWSTEEL', 'JSW Steel', 'B'), generateMockStock('KOTAKBANK', 'Kotak Mahindra Bank', 'A'), generateMockStock('LT', 'Larsen & Toubro', 'A'), generateMockStock('LTIM', 'LTIMindtree', 'B'), generateMockStock('M&M', 'Mahindra & Mahindra', 'A'), generateMockStock('MARUTI', 'Maruti Suzuki', 'A'), generateMockStock('NESTLEIND', 'Nestle India', 'B'), generateMockStock('NTPC', 'NTPC', 'B'), generateMockStock('ONGC', 'ONGC', 'C'), generateMockStock('POWERGRID', 'Power Grid Corp', 'C'), generateMockStock('RELIANCE', 'Reliance Industries', 'A'), generateMockStock('SBILIFE', 'SBI Life Insurance', 'B'), generateMockStock('SBIN', 'SBI', 'A'), generateMockStock('SHRIRAMFIN', 'Shriram Finance', 'C'), generateMockStock('SUNPHARMA', 'Sun Pharma', 'B'), generateMockStock('TATACONSUM', 'Tata Consumer', 'B'), generateMockStock('TATAMOTORS', 'Tata Motors', 'A'), generateMockStock('TATASTEEL', 'Tata Steel', 'B'), generateMockStock('TCS', 'TCS', 'A'), generateMockStock('TECHM', 'Tech Mahindra', 'B'), generateMockStock('TITAN', 'Titan Company', 'A'), generateMockStock('TRENT', 'Trent', 'B'), generateMockStock('ULTRACEMCO', 'UltraTech Cement', 'A'), generateMockStock('WIPRO', 'Wipro', 'B'), ]; const INITIAL_SETTINGS: AppSettings = { capital: 1500000, riskPerTradePercent: 0.6, maxTradesPerWeek: 2, stopTradingTrigger: 2 }; // --- Helper Functions --- const calculateEligibility = (stock: StockData): { eligible: boolean; reasons: string[] } => { const reasons: string[] = []; if (!stock.active) reasons.push("Not Active this week"); // 1. Price > SMA 200 if (stock.price <= stock.sma200) reasons.push("Price below SMA200"); // 2. Distance from SMA200 <= 2% const dist = Math.abs((stock.price - stock.sma200) / stock.sma200 * 100); if (dist > 2) reasons.push(`SMA200 Distance > 2% (${dist.toFixed(2)}%)`); // 3. RSI between 40 and 45 if (stock.rsi < 40 || stock.rsi > 45) reasons.push(`RSI out of range (${stock.rsi.toFixed(1)})`); return { eligible: reasons.length === 0, reasons }; }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', maximumFractionDigits: 0 }).format(amount); }; // --- Components --- const Badge = ({ children, color }: { children: React.ReactNode, color: string }) => ( {children} ); export default function SwingTraderApp() { const [view, setView] = useState<'dashboard' | 'universe' | 'journal' | 'settings'>('dashboard'); const [stocks, setStocks] = useState(INITIAL_STOCKS); const [orders, setOrders] = useState([]); const [settings, setSettings] = useState(INITIAL_SETTINGS); const [pendingAlert, setPendingAlert] = useState(null); const [emergencyStop, setEmergencyStop] = useState(false); // --- Derived State --- const eligibleStocks = useMemo(() => { return stocks.filter(s => calculateEligibility(s).eligible); }, [stocks]); const activeTradesCount = orders.filter(o => o.status === 'OPEN').length; const closedTradesThisWeek = orders.filter(o => o.status === 'CLOSED').length; // Simplified weekly check const lossCount = orders.filter(o => o.status === 'CLOSED' && (o.pnl || 0) < 0).length; // --- Actions --- const toggleActive = (symbol: string) => { setStocks(prev => prev.map(s => s.symbol === symbol ? { ...s, active: !s.active } : s)); }; const simulateDataUpdate = () => { // Simulates a daily data fetch update const updated = stocks.map(s => ({ ...s, price: s.price * (1 + (Math.random() * 0.04 - 0.02)), // +/- 2% move rsi: Math.max(30, Math.min(70, s.rsi + (Math.random() * 10 - 5))), // Random RSI shift lastUpdated: new Date().toISOString() })); setStocks(updated); }; const simulateWebhookAlert = () => { if (emergencyStop) { alert("Trading is HALTED. Cannot process alerts."); return; } if (eligibleStocks.length === 0) { alert("No stocks are currently eligible to generate an alert."); return; } // Pick a random eligible stock const stock = eligibleStocks[Math.floor(Math.random() * eligibleStocks.length)]; // Calculate Position Sizing const riskAmount = settings.capital * (settings.riskPerTradePercent / 100); const entryPrice = stock.price; // Simulating entry at current price const stopLoss = entryPrice * 0.992; // 0.8% SL default const target = entryPrice * 1.015; // 1.5% Target default const lossPerShare = entryPrice - stopLoss; const quantity = Math.floor(riskAmount / lossPerShare); const newOrder: TradeOrder = { id: `ORD-${Date.now()}`, symbol: stock.symbol, entryPrice, stopLoss, target, quantity, riskAmount, status: 'PENDING_CONFIRMATION', dateCreated: new Date().toISOString() }; setPendingAlert(newOrder); }; const confirmOrder = (order: TradeOrder) => { setOrders(prev => [order, ...prev]); setPendingAlert(null); // In a real app, this would trigger the broker API or display the copy-paste format }; const closeTrade = (id: string, exitPrice: number) => { setOrders(prev => prev.map(o => { if (o.id === id) { const pnl = (exitPrice - o.entryPrice) * o.quantity; return { ...o, status: 'CLOSED', exitPrice, pnl }; } return o; })); }; // --- Views --- const Dashboard = () => (
{/* Top Stats */}
Weekly Trades
{activeTradesCount + closedTradesThisWeek} / {settings.maxTradesPerWeek}
Eligible Candidates
{eligibleStocks.length}
Market Filter (Nifty)
Bullish
Index > 200 SMA
= settings.stopTradingTrigger ? 'bg-red-50 border-red-200' : 'bg-white border-slate-200'}`}>
Stop Trading Check
= settings.stopTradingTrigger ? 'text-red-600' : 'text-slate-800'}`}> {lossCount} SL Hits {lossCount >= settings.stopTradingTrigger && }
{/* Trade Eligible List */}

Ready for Entry (Eligible)

{eligibleStocks.length === 0 ? ( ) : ( eligibleStocks.map(stock => { const dist = ((stock.price - stock.sma200) / stock.sma200 * 100).toFixed(2); return ( ); }) )}
Symbol Price RSI % SMA Dist Prev High Action
No stocks meet the criteria right now (Price > SMA200, Dist < 2%, RSI 40-45).
{stock.symbol} ({stock.tier}) ₹{stock.price.toFixed(1)} {stock.rsi.toFixed(1)} {dist}% ₹{stock.prevDayHigh}
{/* Weekly Checklist & Active Trades */}

Active Positions

{orders.filter(o => o.status === 'OPEN').length === 0 ? (
No active trades.
) : ( orders.filter(o => o.status === 'OPEN').map(order => (
{order.symbol} Open
Qty: {order.quantity} Entry: ₹{order.entryPrice.toFixed(1)}
)) )}

Weekly Protocol

  • Check Nifty > 200 SMA
  • Review Active Universe
  • No Earnings this week
); const UniverseManager = () => (

Universe Manager ({stocks.length})

Toggle 'Active' to include in weekly plan
{stocks.map(stock => { const { eligible, reasons } = calculateEligibility(stock); return ( ); })}
Symbol Tier Price Indicators Active This Week Status
{stock.symbol} {stock.tier} ₹{stock.price.toFixed(1)}
RSI: {stock.rsi.toFixed(1)}
SMA: {stock.sma200.toFixed(0)}
{eligible ? ( Eligible ) : ( Ineligible
{reasons.map(r =>
• {r}
)}
)}
); const Journal = () => (

Trade Journal & Performance

{orders.length === 0 ? ( ) : ( orders.map(order => ( )) )}
Date Symbol Entry / Exit Qty P/L Status
No trades recorded yet.
{new Date(order.dateCreated).toLocaleDateString()} {order.symbol}
Ent: ₹{order.entryPrice.toFixed(1)}
{order.exitPrice &&
Ext: ₹{order.exitPrice.toFixed(1)}
}
{order.quantity} = 0 ? 'text-emerald-600' : 'text-red-600'}`}> {order.pnl ? formatCurrency(order.pnl) : '-'} 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}> {order.status}
); return (
{/* Top Navigation */}

SwingTrader Pro

1% Weekly Low-Risk Strategy

{/* Global Controls */}
{/* Sidebar */} {/* Main Content */}
{view === 'dashboard' && } {view === 'universe' && } {view === 'journal' && } {view === 'settings' && (

Risk & Strategy Settings

setSettings({...settings, capital: Number(e.target.value)})} />
setSettings({...settings, riskPerTradePercent: Number(e.target.value)})} />
setSettings({...settings, maxTradesPerWeek: Number(e.target.value)})} />
)}
{/* Alert / Order Modal */} {pendingAlert && (

Entry Signal Detected

{pendingAlert.symbol}
Tier A • Alerted at {new Date(pendingAlert.dateCreated).toLocaleTimeString()}
Current Price
₹{pendingAlert.entryPrice.toFixed(2)}
Quantity {pendingAlert.quantity}
Risk Amount ({(pendingAlert.riskAmount).toFixed(0)}) ₹{formatCurrency(pendingAlert.quantity * (pendingAlert.entryPrice - pendingAlert.stopLoss))}

setPendingAlert({...pendingAlert, stopLoss: Number(e.target.value), quantity: Math.floor(pendingAlert.riskAmount / (pendingAlert.entryPrice - Number(e.target.value))) })} className="w-full text-sm border-red-300 border rounded p-1.5 text-red-700 bg-red-50 font-semibold" />
setPendingAlert({...pendingAlert, target: Number(e.target.value)})} className="w-full text-sm border-emerald-300 border rounded p-1.5 text-emerald-700 bg-emerald-50 font-semibold" />

By clicking Confirm, you acknowledge this trade adheres to the 1% strategy rules.
No order is sent to broker automatically.

)}
); }