2026-09-15 16:46:17 +07:00
import { createResource , For , Show } from "solid-js" ;
2026-09-15 12:36:48 +07:00
import { api } from "../lib/api" ;
2026-09-15 20:23:40 +07:00
import { Term , IstilahStrip } from "../components/Awam" ;
2026-09-15 12:36:48 +07:00
import { Bar } from "solid-chartjs" ;
import { Chart , registerables } from "chart.js" ;
2026-09-15 17:40:39 +07:00
import { PageHead } from "../components/ui" ;
2026-09-15 20:23:40 +07:00
import { Badge } from "../components/ui/badge" ;
import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "../components/ui/card" ;
2026-09-15 17:40:39 +07:00
import { Skeleton } from "../components/ui/skeleton" ;
2026-09-15 12:36:48 +07:00
Chart . register (... registerables );
2026-09-15 22:12:09 +07:00
function PortfolioInner() {
2026-09-15 12:36:48 +07:00
const [ risk ] = createResource (() => api . risk ());
2026-09-15 20:23:40 +07:00
const conc = () => risk () ? . concentration || [];
const betaN = () => Number ( risk () ? . beta ?? 1 );
const betaArti = () => {
const b = betaN ();
if ( b > 1.2 ) return "bergerak lebih liar dari pasar — naik lebih kencang, turun lebih dalam." ;
if ( b < 0.8 ) return "lebih kalem dari pasar — cocok buat yang tidak suka deg-degan." ;
return "geraknya seirama pasar — tidak terlalu liar, tidak terlalu kalem." ;
};
const saran = () => {
const c = conc ();
if ( ! c . length ) return "" ;
2026-09-15 20:31:14 +07:00
// Sektor dulu (risiko sistemik) baru bobot single-ticker.
const perSek : Record < string , number > = {};
for ( const x of c ) perSek [ x . sector ] = ( perSek [ x . sector ] || 0 ) + x . weight ;
const [ sekTop , wSek ] = Object . entries ( perSek ). sort (( a , b ) => b [ 1 ] - a [ 1 ])[ 0 ];
if ( wSek > 0.4 ) return `Sektor ${ sekTop } porsinya ${ ( wSek * 100 ). toFixed ( 0 ) } % — kalau sektor itu jatuh, semua ikut. Coba lirik sektor lain.` ;
2026-09-15 20:23:40 +07:00
const top = c [ 0 ];
if ( top . weight > 0.4 ) return ` ${ top . ticker } porsinya ${ ( top . weight * 100 ). toFixed ( 0 ) } % — kebanyakan telur di satu keranjang. Pertimbangkan tambah saham beda sektor.` ;
return "Sebarannya sudah lumayan — tidak numpuk di satu tempat. Pertahankan. 👍" ;
};
2026-09-15 12:36:48 +07:00
const chartData = () => ({
2026-09-15 20:23:40 +07:00
labels : conc (). map (( c ) => c . ticker ),
datasets : [{ label : "Porsi %" , data : conc (). map (( c ) => + ( c . weight * 100 ). toFixed ( 1 )) }],
2026-09-15 12:36:48 +07:00
});
return (
2026-09-15 17:40:39 +07:00
< div class = "space-y-4" >
2026-09-15 20:23:40 +07:00
< PageHead title = "Risiko portofolio kamu 💼" sub = "Dihitung dari watchlist-mu. Bahasa manusia, bukan laporan bank." />
< Show when = { ! risk . loading } fallback = {< div class = "space-y-2" >< Skeleton class = "h-24 w-full" />< Skeleton class = "h-24 w-full" /></ div >}>
< Card class = "border-primary/30 bg-gradient-to-br from-primary/10 to-transparent" >
< CardHeader >< CardTitle class = "text-xl" > Saran 1 kalimat 💡 </ CardTitle ></ CardHeader >
< CardContent >< p class = "text-base leading-relaxed" >{ saran () || "Tambah saham ke watchlist dulu biar bisa dinilai." }</ p ></ CardContent >
</ Card >
2026-09-15 17:40:39 +07:00
< div class = "grid gap-4 md:grid-cols-2" >
< Card >
2026-09-15 20:23:40 +07:00
< CardHeader >< CardTitle > Porsi tiap saham </ CardTitle >< CardDescription > Siapa yang paling dominan di < Term kata = "portofolio" /> - mu .</ CardDescription ></ CardHeader >
2026-09-15 17:40:39 +07:00
< CardContent >
2026-09-15 20:23:40 +07:00
< Show when = { conc (). length } fallback = {< p class = "text-sm text-muted-foreground" > Tambah saham ke watchlist dulu .</ p >}>
2026-09-15 17:40:39 +07:00
< Bar data = { chartData ()} options = {{ responsive : true }} />
</ Show >
2026-09-15 20:23:40 +07:00
< For each = { risk () ? . warnings || []}>{( w ) => < p class = "mt-1 text-sm" > ⚠️ { w }</ p >}</ For >
2026-09-15 17:40:39 +07:00
</ CardContent >
</ Card >
< Card >
2026-09-15 20:23:40 +07:00
< CardHeader >< CardTitle > Kelakuan vs pasar </ CardTitle >< CardDescription > Skor beta = { betaN (). toFixed ( 2 )}</ CardDescription ></ CardHeader >
2026-09-15 17:40:39 +07:00
< CardContent class = "space-y-3" >
2026-09-15 20:23:40 +07:00
< p class = "text-base" > Portofolio kamu < strong >{ betaArti ()}</ strong ></ p >
< div >< Badge variant = "outline" >< Term kata = "diversifikasi" /> : {[... new Set ( conc (). map (( x ) => x . sector ))]. length } sektor </ Badge ></ div >
2026-09-15 17:40:39 +07:00
</ CardContent >
</ Card >
2026-09-15 12:36:48 +07:00
</ div >
2026-09-15 16:46:17 +07:00
</ Show >
2026-09-15 20:23:40 +07:00
< div >< IstilahStrip /></ div >
2026-09-15 12:36:48 +07:00
</ div >
);
}
2026-09-15 22:12:09 +07:00
import { Gate } from "../index" ;
export default function Portfolio() {
return < Gate fitur = "Portofolio" >{ PortfolioInner ()}</ Gate >;
}