// portfolioData.jsx — 포트폴리오 관리 데이터 + 리스크 모델. Exports to window. // 리스크 = CVaR 99% / 1일 (억원). 한국 관례: + 빨강 / − 파랑. // ---- 종목 유니버스 ---- // vol = 개별 연변동성(%), ret = 연수익률(%), dur = 듀레이션(채권), country = 국가 const UNIV = [ // 국내 주식 { id:'005930', name:'삼성전자', type:'주식', sector:'반도체', country:'한국', ret:12.4, vol:22, beta:1.15 }, { id:'000660', name:'SK하이닉스', type:'주식', sector:'반도체', country:'한국', ret:18.6, vol:31, beta:1.35 }, { id:'373220', name:'LG에너지솔루션', type:'주식', sector:'2차전지', country:'한국', ret:-8.3, vol:38, beta:1.30 }, { id:'005380', name:'현대차', type:'주식', sector:'자동차', country:'한국', ret:9.8, vol:24, beta:1.05 }, { id:'105560', name:'KB금융', type:'주식', sector:'금융', country:'한국', ret:18.6, vol:19, beta:0.92 }, { id:'035420', name:'NAVER', type:'주식', sector:'인터넷', country:'한국', ret:-4.5, vol:32, beta:1.18 }, { id:'207940', name:'삼성바이오로직스', type:'주식', sector:'바이오', country:'한국', ret:6.2, vol:28, beta:0.88 }, { id:'005490', name:'POSCO홀딩스', type:'주식', sector:'소재', country:'한국', ret:-2.1, vol:33, beta:1.22 }, { id:'010120', name:'LS ELECTRIC', type:'주식', sector:'전력기기', country:'한국', ret:21.4, vol:29, beta:1.10 }, { id:'068270', name:'셀트리온', type:'주식', sector:'바이오', country:'한국', ret:1.8, vol:30, beta:0.95 }, // 해외 주식 (fx = 환노출) { id:'AAPL', name:'애플', type:'주식', sector:'IT하드웨어', country:'미국', fx:true, ret:15.2, vol:26, beta:1.10 }, { id:'NVDA', name:'엔비디아', type:'주식', sector:'반도체', country:'미국', fx:true, ret:38.4, vol:44, beta:1.65 }, { id:'MSFT', name:'마이크로소프트', type:'주식', sector:'소프트웨어', country:'미국', fx:true, ret:17.8, vol:24, beta:1.02 }, { id:'TSM', name:'TSMC', type:'주식', sector:'반도체', country:'대만', fx:true, ret:22.1, vol:33, beta:1.28 }, { id:'7203', name:'도요타', type:'주식', sector:'자동차', country:'일본', fx:true, ret:8.4, vol:21, beta:0.85 }, // 채권 — rating, conv(컨벡시티), call(콜옵션 내재, 베가 노출) { id:'KR103502', name:'국고03375-3206', type:'채권', sector:'국채', country:'한국', rating:'국가', ret:3.4, vol:5.0, dur:7.5, credit:0, conv:0.66 }, { id:'KR4900', name:'통안02000-2706', type:'채권', sector:'국채', country:'한국', rating:'국가', ret:3.1, vol:2.6, dur:1.9, credit:0, conv:0.05 }, { id:'KR350706', name:'도로공사752', type:'채권', sector:'공사·공단채', country:'한국', rating:'AAA', ret:3.6, vol:4.1, dur:3.2, credit:1, conv:0.13 }, { id:'KR3546', name:'한국전력채712', type:'채권', sector:'공사·공단채', country:'한국', rating:'AAA', ret:3.8, vol:4.6, dur:4.1, credit:1, conv:0.20 }, { id:'KR8085', name:'신한은행4-12', type:'채권', sector:'금융채', country:'한국', rating:'AA+', ret:3.9, vol:3.4, dur:2.6, credit:1, conv:0.09, call:true, vega:0.8 }, { id:'KR2253', name:'회사채AA- 5년', type:'채권', sector:'회사채', country:'한국', rating:'AA-', ret:4.2, vol:6.2, dur:3.5, credit:2, conv:0.15 }, { id:'KR6000', name:'IBK캐피탈27-3', type:'채권', sector:'금융채', country:'한국', rating:'AA-', ret:4.4, vol:4.8, dur:2.2, credit:2, conv:0.07, call:true, vega:0.5 }, { id:'US91282', name:'미국채 10년', type:'채권', sector:'해외채권', country:'미국', rating:'국가', ret:4.3, vol:8.0, dur:8.5, credit:0, conv:0.84, fx:true }, { id:'US037833', name:'AAPL 3.85 43', type:'채권', sector:'해외채권', country:'미국', rating:'AA+', ret:4.9, vol:10.5, dur:11.2, credit:1, conv:1.42, fx:true }, { id:'FR0013', name:'프랑스국채 OAT31', type:'채권', sector:'해외채권', country:'프랑스', rating:'AA', ret:3.2, vol:6.5, dur:5.8, credit:0, conv:0.41, fx:true }, { id:'XS2364', name:'QNBK 1.71 26', type:'채권', sector:'해외채권', country:'카타르', rating:'A+', ret:4.6, vol:9.2, dur:4.0, credit:2, conv:0.19, fx:true }, ]; const UMAP = {}; UNIV.forEach(u=>UMAP[u.id]=u); const SECTOR_HUE = { '반도체':'#0A4FA3','2차전지':'#6D5BD0','자동차':'#1F8A8A','금융':'#0E7C5A','인터넷':'#00A0E0', '바이오':'#C76A1F','소재':'#8A6D3B','전력기기':'#3E63B8','IT하드웨어':'#4C7BD9','소프트웨어':'#B0476B', '국채':'#0A4FA3','공사·공단채':'#00A0E0','금융채':'#0E7C5A','해외채권':'#E0922A','회사채':'#6D5BD0','특수채':'#1F8A8A' }; const COUNTRY_HUE = { '한국':'#0A4FA3','미국':'#00A0E0','대만':'#6D5BD0','일본':'#0E7C5A','프랑스':'#C76A1F','카타르':'#E0922A' }; // ---- 리스크 모델 ---- const Z = 2.33; // 99% z-score const DAILY = Z/Math.sqrt(252); // 일간 CVaR 환산 ≈ 0.1468 // 상관: 동일자산군 0.45, 주식-채권 0.15, 자기 1 function corr(a,b){ if(a.id===b.id) return 1; if(a.type===b.type) return a.type==='주식'?0.50:0.42; return 0.15; } // 비중(%) 배열 -> 연환산 포트 변동성(%) function portVol(holds, volOverride){ let s=0; for(const h of holds){ for(const g of holds){ const a=UMAP[h.id], b=UMAP[g.id]; const va=(volOverride&&volOverride[h.id])||a.vol, vb=(volOverride&&volOverride[g.id])||b.vol; s += (h.w/100)*(g.w/100)*corr(a,b)*va*vb; }} return Math.sqrt(Math.max(0,s)); } function portRet(holds){ return holds.reduce((s,h)=>s+(h.w/100)*UMAP[h.id].ret,0); } function riskAmt(value, volPct){ return value*(volPct/100)*DAILY; } // 억 // 정규화: 비중 합 100 function normalize(holds){ const t=holds.reduce((s,h)=>s+h.w,0)||1; return holds.map(h=>({...h, w:+(h.w/100*100*(100/t)).toFixed(2)})); } // 한 포트폴리오의 종합 지표 계산 function summarize(p){ const holds = p.holds; const vol = portVol(holds); const risk = riskAmt(p.value, vol); const ret = portRet(holds); const remain = p.limit - risk; return { vol, risk, ret, remain, util:risk/p.limit*100, value:p.value, limit:p.limit }; } // ---- 위기 시나리오 ---- // eq: 주식 충격(%), rateBp: 금리변동(bp,+상승), creditBp: 신용스프레드(bp), fxPct: 원화환산(+원약세), volX: 변동성 배수 const SCENARIOS = [ { id:'covid', name:'COVID-19 팬데믹', date:'2020.03', icon:'alert', desc:'급격한 위험회피·유동성 경색', eq:-34, rateBp:-110, creditBp:260, fxPct:8, volX:2.6 }, { id:'ratehike', name:'美 금리 급등', date:'2022.09', icon:'trendUp', desc:'연준 공격적 긴축·실질금리 상승', eq:-18, rateBp:215, creditBp:120, fxPct:7, volX:1.8 }, { id:'gfc', name:'글로벌 금융위기', date:'2008.10', icon:'alert', desc:'신용경색·자산 동반 급락', eq:-45, rateBp:-90, creditBp:420, fxPct:14, volX:3.0 }, { id:'infl', name:'인플레이션 쇼크', date:'2021.11', icon:'percent', desc:'물가 급등·금리 상방 압력', eq:-12, rateBp:150, creditBp:70, fxPct:4, volX:1.5 }, { id:'fx', name:'원화 급락 (환위기)', date:'1997.12', icon:'won', desc:'원화 급락·해외자산 환산 확대', eq:-15, rateBp:120, creditBp:140, fxPct:22, volX:1.9 }, { id:'geo', name:'지정학 리스크 격화', date:'2022.02', icon:'flag', desc:'에너지·공급망 충격', eq:-14, rateBp:40, creditBp:80, fxPct:6, volX:1.6 }, ]; // 시나리오 적용 -> 종목별 손익% + 포트 결과 function simulate(p, sc){ const holds = p.holds; const volOv = {}; const perHold = holds.map(h=>{ const u = UMAP[h.id]; let pnl; if(u.type==='주식'){ pnl = sc.eq * (u.beta||1); if(u.fx) pnl += sc.fxPct; } else { const spread = (u.credit||0)*0.5*sc.creditBp; // 신용등급 민감도 pnl = -(u.dur||3) * (sc.rateBp + spread)/100; // 가격% = -듀레이션 × Δ수익률(%) if(u.fx) pnl += sc.fxPct; // 환산 효과 } volOv[h.id] = u.vol * sc.volX; return { id:h.id, name:u.name, type:u.type, sector:u.sector, w:h.w, pnl:+pnl.toFixed(1) }; }); const portPnl = perHold.reduce((s,x)=>s+(x.w/100)*x.pnl,0); const newValue = p.value*(1+portPnl/100); const newVol = portVol(holds, volOv); const newRisk = riskAmt(newValue, newVol); return { perHold, portPnl:+portPnl.toFixed(2), lossAmt:Math.round(p.value*portPnl/100), newValue:Math.round(newValue), newVol, newRisk:Math.round(newRisk), util:newRisk/p.limit*100 }; } // ---- AI 최적화 (리스크 관점 비중 추천) ---- // 위험조정 점수 ∝ (기대수익+α)/분산 으로 틸트 후 현재 비중과 블렌드 → 리스크 절감 function optimize(p){ const holds = p.holds; const score = holds.map(h=>{ const u=UMAP[h.id]; return Math.max(0.05,(u.ret+4)/u.vol); }); const ss = score.reduce((a,b)=>a+b,0); const target = holds.map((h,i)=> score[i]/ss*100); // 50% 최적 + 50% 현재 (현실적 리밸런싱) let blended = holds.map((h,i)=> 0.5*target[i] + 0.5*h.w); const t = blended.reduce((a,b)=>a+b,0); blended = blended.map(w=>w/t*100); const newHolds = holds.map((h,i)=>({ id:h.id, w:+blended[i].toFixed(2) })); const before = summarize(p); const after = summarize({ ...p, holds:newHolds }); return { newHolds, before, after, deltas: holds.map((h,i)=>({ id:h.id, name:UMAP[h.id].name, type:UMAP[h.id].type, from:h.w, to:+newHolds[i].w.toFixed(1), diff:+(newHolds[i].w-h.w).toFixed(1) })) }; } // ---- 조직 / 담당자 / 포트폴리오 ---- // 트레이딩 데스크는 주식/채권 담당이 분리되어 있다. asset = 담당 자산군. // holds 비중은 자동 정규화. limit/value 단위 억. function P(holds, value, limit){ return { holds: normalize(holds.map(([id,w])=>({id,w}))), value, limit }; } const PEOPLE = [ // ── 주식 데스크 ── { emp:'20180417', name:'김진호', asset:'주식', dept:'자금운용부', team:'트레이딩데스크 1팀', title:'팀장', pf:P([['005930',22],['000660',16],['105560',12],['005380',12],['NVDA',14],['AAPL',12],['TSM',12]], 3240, 140) }, { emp:'20191103', name:'박서연', asset:'주식', dept:'자금운용부', team:'트레이딩데스크 1팀', title:'책임', pf:P([['005930',18],['373220',12],['035420',12],['207940',12],['068270',10],['MSFT',18],['7203',18]], 2620, 115) }, { emp:'20200622', name:'이도현', asset:'주식', dept:'자금운용부', team:'트레이딩데스크 2팀', title:'책임', pf:P([['000660',18],['010120',14],['005490',12],['005380',12],['NVDA',16],['TSM',16],['MSFT',12]], 2980, 125) }, { emp:'20190718', name:'오세훈', asset:'주식', dept:'투자금융부', team:'주식운용팀', title:'책임', pf:P([['005930',16],['000660',12],['005380',12],['105560',12],['010120',10],['207940',10],['035420',10],['AAPL',18]], 3270, 130) }, // ── 채권 데스크 ── { emp:'20170815', name:'최민준', asset:'채권', dept:'자금운용부', team:'트레이딩데스크 2팀', title:'팀장', pf:P([['KR103502',20],['KR350706',14],['US91282',18],['KR8085',14],['FR0013',12],['KR2253',12],['KR4900',10]], 6130, 110) }, { emp:'20210309', name:'정유진', asset:'채권', dept:'투자금융부', team:'채권운용팀', title:'책임', pf:P([['KR103502',22],['KR4900',16],['KR350706',14],['KR3546',13],['US91282',15],['XS2364',12],['KR2253',8]], 7480, 120) }, { emp:'20160527', name:'한지우', asset:'채권', dept:'투자금융부', team:'채권운용팀', title:'수석', pf:P([['KR103502',20],['KR350706',16],['KR3546',14],['KR8085',12],['US91282',16],['XS2364',12],['KR4900',10]], 8920, 150) }, { emp:'20151001', name:'강하늘', asset:'채권', dept:'프로젝트금융부', team:'대체투자팀', title:'팀장', pf:P([['US91282',22],['XS2364',18],['KR2253',16],['KR3546',12],['US037833',16],['FR0013',16]], 5560, 102) }, { emp:'20220214', name:'윤채원', asset:'채권', dept:'프로젝트금융부', team:'대체투자팀', title:'주임', pf:P([['US91282',24],['XS2364',16],['KR2253',14],['KR350706',12],['US037833',12],['KR6000',12],['KR4900',10]], 2980, 58) }, ]; const PMAP = {}; PEOPLE.forEach(p=>PMAP[p.emp]=p); const peopleOf = (asset)=> asset ? PEOPLE.filter(p=>p.asset===asset) : PEOPLE; const deptsOf = (asset)=> [...new Set(peopleOf(asset).map(p=>p.dept))]; // 조직별 집계 포트폴리오 (부서 단위 합산, asset 필터 가능) function deptPortfolio(deptName, asset){ const members = PEOPLE.filter(p=>p.dept===deptName && (!asset || p.asset===asset)); const value = members.reduce((s,m)=>s+m.pf.value,0); const limit = members.reduce((s,m)=>s+m.pf.limit,0); const agg = {}; members.forEach(m=>m.pf.holds.forEach(h=>{ agg[h.id]=(agg[h.id]||0)+h.w*m.pf.value; })); const tot = Object.values(agg).reduce((a,b)=>a+b,0)||1; const holds = Object.entries(agg).map(([id,v])=>({id, w:+(v/tot*100).toFixed(2)})).sort((a,b)=>b.w-a.w); return { holds, value, limit }; } const DEPT_LIST = ['자금운용부','투자금융부','프로젝트금융부']; Object.assign(window, { UNIV, UMAP, SECTOR_HUE, COUNTRY_HUE, SCENARIOS, PEOPLE, PMAP, DEPT_LIST, corr, DAILY, peopleOf, deptsOf, portVol, portRet, riskAmt, normalize, summarize, simulate, optimize, deptPortfolio });