// FactorViews.jsx — 요인 분석 공용 컴포넌트: 요인 시황 연결 · 단일 요인 스트레스. // deterministic spark series (LCG) function fvSpark(seed, end, vol) { let r = seed >>> 0; const rnd = () => { r = Math.imul(r, 1103515245) + 12345 >>> 0; return r / 4294967296; }; const n = 22, arr = new Array(n); let v = end; for (let i = n - 1; i >= 0; i--) { arr[i] = v; v = v * (1 + (rnd() - 0.5) * vol); } return arr; } const fvEok = (v) => (v > 0 ? '+' : v < 0 ? '−' : '') + Math.abs(Math.round(v)).toLocaleString() + '억'; // ── 요인 시황 연결 (지표 + 내 포지션 민감도) ── function FactorMarketLink({ pf, fids }) { const s = factorSens(pf); const items = fids ? FACTOR_MARKET.filter((m) => fids.includes(m.fid)) : FACTOR_MARKET; return (
{items.map((m) => { const f = FMAP[m.fid]; const c = dirColor(m.delta); const impact = m.shock(s, pf.value); return (
{m.label} {m.sub}
{m.value} {m.unit}
{m.shockText} → {fvEok(impact)}
); })}
); } // ── 단일 요인 스트레스 ── keys: 표시할 요인 슬라이더 목록 (자산군별로 다름) function FactorStress({ pf, keys }) { const [v, setV] = React.useState({ rate: 0, credit: 0, eq: 0, fx: 0 }); const set = (k) => (e) => setV((o) => ({ ...o, [k]: +e.target.value })); const base = summarize(pf); const sim = simulate(pf, { eq: v.eq, rateBp: v.rate, creditBp: v.credit, fxPct: v.fx, volX: 1 }); const active = Object.values(v).some((x) => x !== 0); const ALL_SLIDERS = [ { k: 'rate', name: '금리', min: -200, max: 200, step: 5, unit: 'bp', fmt: (x) => (x > 0 ? '+' : '') + x + 'bp' }, { k: 'credit', name: '신용스프레드', min: -100, max: 300, step: 5, unit: 'bp', fmt: (x) => (x > 0 ? '+' : '') + x + 'bp' }, { k: 'eq', name: '주식', min: -30, max: 15, step: 1, unit: '%', fmt: (x) => (x > 0 ? '+' : '') + x + '%' }, { k: 'fx', name: '원/달러', min: -10, max: 20, step: 0.5, unit: '%', fmt: (x) => (x > 0 ? '+' : '') + x + '% (원' + (x >= 0 ? '약세' : '강세') + ')' }, ]; const sliders = keys ? ALL_SLIDERS.filter((s) => keys.includes(s.k)) : ALL_SLIDERS; return (
{sliders.map((sl) => { const f = FMAP[sl.k]; const scol = f.color; const pct = (v[sl.k] - sl.min) / (sl.max - sl.min) * 100; // 단일 요인 손익 (해당 요인만 적용) const solo = simulate(pf, { eq: sl.k === 'eq' ? v.eq : 0, rateBp: sl.k === 'rate' ? v.rate : 0, creditBp: sl.k === 'credit' ? v.credit : 0, fxPct: sl.k === 'fx' ? v.fx : 0, volX: 1 }); return (
{sl.name} {sl.fmt(v[sl.k])} {v[sl.k] === 0 ? '—' : fvEok(solo.lossAmt)}
{sl.fmt(sl.min)} {sl.fmt(sl.max)}
); })}
스트레스 결과
{sim.util >= 100 &&
이 충격에서 리스크 한도를 초과해요.
}
{!active &&
슬라이더를 움직여 단일 요인 충격의 영향을 확인하세요.
}
); } Object.assign(window, { FactorMarketLink, FactorStress, fvEok, fvSpark });