vvvv

Enter your text here...

/* HeroHero Analytický Nástroj – špecifikácia (stručne) ==================================================== Cieľ: Jednostránkový komponent, kde follower zadá ticker (napr. AAPL) a okamžite uvidí: - prehľad firmy (cena, mkt cap, sektor, beta, P/E, EPS, ROE, ROIC, marže, dlh, cash, FCF, dividendy) - graf ceny a základných fundamentov - okamžitý odhad vnútornej hodnoty (DCF, EV/EBITDA, P/E, Graham) - kvalitativné skóre + krátke zhrnutie BUY/HOLD/SELL (nefinančné odporúčanie) - možnosť doladiť vstupy (WACC, rast, marže, multiplikátory) a uložiť stav do URL - pripravený hook na prepojenie s backend endpointom: GET /api/analyze?ticker=XYZ Architektúra (MVP): - Frontend: React + Tailwind + shadcn/ui + recharts (tento súbor). Dá sa embednúť do HeroHero cez */ import React, { useEffect, useMemo, useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Slider } from "@/components/ui/slider"; import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "@/components/ui/tooltip"; import { AlertCircle, BarChart2, Calculator, Download, ExternalLink, LineChart, Shield, Sparkles, TrendingUp } from "lucide-react"; import { Area, AreaChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip as RTooltip, XAxis, YAxis } from "recharts"; // ---------- Helper types ---------- type Fundamentals = { pe: number | null; eps: number | null; roe: number | null; roic: number | null; grossMargin: number | null; opMargin: number | null; netMargin: number | null; debt: number | null; cash: number | null; fcf: number | null; divYield: number | null; beta: number | null; }; type AnalyzeResponse = { ticker: string; price: number | null; profile: { name: string; sector: string; exchange?: string } | null; fundamentals: Fundamentals | null; timeseries?: { price?: { date: string; value: number }[]; revenue?: { date: string; value: number }[]; eps?: { date: string; value: number }[] } | null; sharesOutstanding: number | null; }; // ---------- Mock data (AAPL-like) ---------- const mockData: AnalyzeResponse = { ticker: "AAPL", price: 210.34, profile: { name: "Apple Inc.", sector: "Technology", exchange: "NASDAQ" }, fundamentals: { pe: 31.2, eps: 6.74, roe: 147, roic: 34, grossMargin: 45.2, opMargin: 30.1, netMargin: 25.0, debt: 109_000_000_000, cash: 72_000_000_000, fcf: 94_000_000_000, divYield: 0.55, beta: 1.25 }, timeseries: { price: Array.from({ length: 36 }, (_, i) => ({ date: `M${i+1}`, value: 150 + i * 2 + Math.sin(i/2)*5 })), revenue: Array.from({ length: 8 }, (_, i) => ({ date: `${2017 + i}`, value: 220 + i * 20 })), eps: Array.from({ length: 8 }, (_, i) => ({ date: `${2017 + i}`, value: 2.2 + i * 0.55 })) }, sharesOutstanding: 15.5e9 }; // ---------- Utility math ---------- function round(n: number | null | undefined, d = 2) { if (n == null || isNaN(n as any)) return null; const p = Math.pow(10, d); return Math.round((n as number) * p) / p; } function formatCurrency(n: number | null | undefined, currency = "$") { if (n == null) return "–"; const abs = Math.abs(n); if (abs >= 1e12) return `${currency}${round(n/1e12,2)}T`; if (abs >= 1e9) return `${currency}${round(n/1e9,2)}B`; if (abs >= 1e6) return `${currency}${round(n/1e6,2)}M`; return `${currency}${round(n,2)}`; } // Simplified DCF function dcfIntrinsic({ fcf0, g, years = 5, wacc, gTerm, shares }: { fcf0: number; g: number; years?: number; wacc: number; gTerm: number; shares: number; }) { const cashflows: number[] = []; for (let t = 1; t <= years; t++) cashflows.push(fcf0 * Math.pow(1 + g, t)); const npv = cashflows.reduce((acc, cf, i) => acc + cf / Math.pow(1 + wacc, i + 1), 0); const tv = (cashflows[years - 1] * (1 + gTerm)) / (wacc - gTerm); const tvpv = tv / Math.pow(1 + wacc, years); const equityValue = npv + tvpv; if (!shares || shares <= 0) return null; return equityValue / shares; } function grahamIntrinsic({ eps, g, aaaYield = 0.045 }: { eps: number; g: number; aaaYield?: number; }) { return eps * (8.5 + 2 * (g * 100)) * (0.044 / aaaYield); } // Quality score (0-100) function qualityScore(f: Fundamentals | null, opts: { growthCAGR: number; moat: number; mgmt: number; debtFCF: number | null; }): number { if (!f) return 0; const profitability = Math.min(100, (Number(f.roic)||0) * 2 + (Number(f.opMargin)||0)); const growth = Math.min(100, (opts.growthCAGR*100) * 3); const health = Math.min(100, opts.debtFCF==null?50: Math.max(0, 100 - (opts.debtFCF*10))); const moat = Math.min(100, opts.moat); const mgmt = Math.min(100, opts.mgmt); return Math.round(0.30*profitability + 0.25*growth + 0.20*health + 0.15*moat + 0.10*mgmt); } function verdictFromScore(score: number) { if (score >= 75) return { label: "BUY (nie odporúčanie)", color: "text-emerald-500" }; if (score >= 55) return { label: "HOLD", color: "text-amber-500" }; return { label: "SELL", color: "text-rose-500" }; } // ---------- Main Component ---------- export default function HeroHeroAnalyzer() { const [ticker, setTicker] = useState("AAPL"); const [useMock, setUseMock] = useState(true); const [loading, setLoading] = useState(false); const [data, setData] = useState(mockData); // Valuation inputs const [wacc, setWacc] = useState(0.09); const [g5y, setG5y] = useState(0.06); const [gTerm, setGTerm] = useState(0.025); const [peTarget, setPeTarget] = useState(22); const [evEbitdaTarget, setEvEbitdaTarget] = useState(14); const [epsFwd, setEpsFwd] = useState(7.2); const [ebitdaFwd, setEbitdaFwd] = useState(130_000_000_000); const [aaaYield, setAaaYield] = useState(0.045); const [growthCAGR, setGrowthCAGR] = useState(0.08); const [moat, setMoat] = useState(80); const [mgmt, setMgmt] = useState(75); // Load state from URL if present useEffect(() => { const params = new URLSearchParams(wnd.fe.Location.search); const t = params.get("t"); if (t) setTicker(t.toUpperCase()); }, []); const fetchData = async () => { setLoading(true); try { if (useMock) { // simulate latency await new Promise(r => setTimeout(r, 400)); setData({ ...mockData, ticker }); } else { const res = await fetch(`/api/analyze?ticker=${encodeURIComponent(ticker)}`); const json = await res.json(); setData(json as AnalyzeResponse); if (json?.fundamentals?.eps) setEpsFwd(json.fundamentals.eps * 1.05); } const url = new URL(wnd.fe.Location.href); url.searchParams.set("t", ticker.toUpperCase()); window.history.replaceState({}, "", url.toString()); } catch (e) { console.error(e); alert("Nepodarilo sa načítať dáta. Skúste znova."); } finally { setLoading(false); } }; const intrinsicDCF = useMemo(() => { const fcf0 = Number(data?.fundamentals?.fcf || 0); const shares = Number(data?.sharesOutstanding || 0); if (!fcf0 || !shares) return null; return dcfIntrinsic({ fcf0, g: g5y, wacc, gTerm, shares }); }, [data, g5y, wacc, gTerm]); const intrinsicPE = useMemo(() => { if (!epsFwd) return null; return epsFwd * peTarget; }, [epsFwd, peTarget]); const intrinsicEVEBITDA = useMemo(() => { if (!ebitdaFwd) return null; // Jednoduchý prepočet na cenu akcie je mimo rozsah EV -> Eq; pre MVP berieme "cieľovú cenu" ako pomer k aktuálnej // V produkčnej verzii použite: Price = ((EV/EBITDA * EBITDA) - NetDebt) / Shares const price = Number(data?.price || 0); if (!price) return null; const rel = evEbitdaTarget / 12; // pivot 12x = 1.0 return price * rel; }, [ebitdaFwd, evEbitdaTarget, data?.price]); const intrinsicGraham = useMemo(() => { if (!data?.fundamentals?.eps) return null; return grahamIntrinsic({ eps: data.fundamentals.eps, g: g5y, aaaYield }); }, [data?.fundamentals?.eps, g5y, aaaYield]); const blendedIntrinsic = useMemo(() => { const arr = [intrinsicDCF, intrinsicPE, intrinsicEVEBITDA, intrinsicGraham].filter(Boolean) as number[]; if (arr.length === 0) return null; // Dajte väčšiu váhu DCF a P/E const w = [0.45, 0.35, 0.10, 0.10]; let i = 0; return round(arr.reduce((acc, val) => acc + val * (w[i++] || 0), 0), 2); }, [intrinsicDCF, intrinsicPE, intrinsicEVEBITDA, intrinsicGraham]); const price = data?.price ?? null; const upside = price && blendedIntrinsic ? round((blendedIntrinsic - price) / price * 100, 1) : null; const debtFCF = useMemo(() => { const d = Number(data?.fundamentals?.debt || 0); const f = Number(data?.fundamentals?.fcf || 0); if (!d || !f) return null; return d / f; }, [data]); const qScore = useMemo(() => qualityScore(data?.fundamentals || null, { growthCAGR, moat, mgmt, debtFCF }), [data, growthCAGR, moat, mgmt, debtFCF]); const verdict = verdictFromScore(qScore); return (

Vypočítaj si vnútornú hodnotu

Slovenský Investor · MVP (demo)
setTicker(e.target.value.toUpperCase())} placeholder="AAPL" className="bg-neutral-900 border-neutral-700"/>
Tento nástroj je edukačný. Nie je to finančné odporúčanie.
{/* Overview */}
Prehľad
Google Finance
Spoločnosť
{data?.profile?.name || ticker}
{data?.profile?.sector || "–"} · {data?.profile?.exchange || "–"}
Cena
{price ? `$${round(price,2)}` : "–"}
Beta {round(data?.fundamentals?.beta,2) ?? "–"}
Upside vs intrinsic
=0?"text-emerald-400":"text-rose-400") : ""}`}>{upside!=null? `${upside}%` : "–"}
Cieľová (blend): {blendedIntrinsic? `$${blendedIntrinsic}` : "–"}
{/* Fundamentals */}
Fundamenty
{/* Valuation Tabs */}
Oceňovanie
Blended DCF P/E EV/EBITDA Graham
setWacc(v/100)} suffix="%"/> setG5y(v/100)} suffix="%"/> setGTerm(v/100)} suffix="%"/>
Výsledok: {intrinsicDCF? ${round(intrinsicDCF,2)} : "–"} na akciu
setEpsFwd(v)} /> setPeTarget(v)} />
Cieľová cena: {intrinsicPE? ${round(intrinsicPE,2)} : "–"}
setEbitdaFwd(v)} /> setEvEbitdaTarget(v)} />
Indikácia ceny (rel.): {intrinsicEVEBITDA? ${round(intrinsicEVEBITDA,2)} : "–"}
Pozn.: Pre presnú cenu je potrebné zohľadniť NetDebt a počet akcií.
{}} disabled/> setG5y(v/100)} suffix="%"/> setAaaYield(v/100)} suffix="%"/>
Graham hodnota: {intrinsicGraham? ${round(intrinsicGraham,2)} : "–"}
{/* Quality & Risks */}
Kvalita & Verdikt
setGrowthCAGR(v/100)} suffix="%"/>
Skóre kvality
{qScore}/100
{verdict.label}
Tržby & EPS
{/* Footer */}
© Slovenský Investor · Edukačný nástroj · Nie je to investičné odporúčanie.
); } // ---------- Small UI helpers ---------- function Item({ label, value }: { label: string; value: React.ReactNode }) { return (
{label}
{value ?? "–"}
); } function Stat({ label, value }: { label: string; value: React.ReactNode }) { return (
{label}
{value}
); } function Knob({ label, value, onChange, suffix }: { label: string; value: number; onChange: (v:number)=>void; suffix?: string }) { return (
{label}: {value}{suffix||""}
onChange(v[0])} max={suffix?100:100} step={1} className="w-full"/>
); } function InputLabeled({ label, value, onChange, disabled=false }: { label: string; value: number; onChange: (v:number)=>void; disabled?: boolean }) { return (
onChange(parseFloat(e.target.value))} className="bg-neutral-900 border-neutral-700"/>
); } function mergeSeries(revenue?: {date:string; value:number}[], eps?: {date:string; value:number}[]) { const map: Record = {}; (revenue||[]).forEach(d => { map[d.date] = { date: d.date, revenue: d.value }; }); (eps||[]).forEach(d => { map[d.date] = { ...(map[d.date]||{date:d.date}), eps: d.value }; }); return Object.values(map).sort((a:any,b:any)=> (a.date > b.date ? 1 : -1)); }