iamtalkers

나무증권 종합거래내역 분석기

Aug 6th, 2025
345
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 31.44 KB | None | 0 0
  1. <!DOCTYPE html>
  2.  
  3. <html lang="ko">
  4.  
  5. <head>
  6.  
  7.     <meta charset="UTF-8">
  8.  
  9.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  10.  
  11.     <title>매매일지 분석기</title>
  12.  
  13.     <style>
  14.  
  15.         /*
  16.  
  17.          * 모든 CSS 스타일을 '#trading-analyzer-root' ID 내부로 범위화하여
  18.  
  19.          * 다른 웹사이트의 전역 CSS와 충돌하지 않도록 합니다.
  20.  
  21.          */
  22.  
  23.         #trading-analyzer-root {
  24.  
  25.             font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  26.  
  27.             max-width: 1400px; /* 컨테이너의 최대 너비 */
  28.  
  29.             margin: 20px auto; /* 중앙 정렬 및 상하 여백 추가 */
  30.  
  31.             padding: 0; /* 루트 컨테이너 자체의 패딩은 제거 */
  32.  
  33.             background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); /* 배경 그라디언트 */
  34.  
  35.             min-height: 100vh; /* 최소 높이 설정 */
  36.  
  37.             border-radius: 15px; /* 컨테이너 자체에도 둥근 모서리 적용 */
  38.  
  39.             box-shadow: 0 20px 40px rgba(0,0,0,0.1); /* 그림자 효과 */
  40.  
  41.             overflow: hidden; /* 자식 요소가 넘치지 않도록 */
  42.  
  43.             display: flex; /* 내부 콘텐츠를 중앙에 정렬하기 위해 flexbox 사용 */
  44.  
  45.             flex-direction: column; /* 세로 방향으로 정렬 */
  46.  
  47.             justify-content: center; /* 수직 중앙 정렬 */
  48.  
  49.             align-items: center; /* 수평 중앙 정렬 */
  50.  
  51.         }
  52.  
  53.         /* 기존 body 스타일 중 분석기에 필요한 부분만 #trading-analyzer-root 내부로 이동 */
  54.  
  55.         #trading-analyzer-root .container {
  56.  
  57.             width: 100%; /* 부모 너비에 맞춤 */
  58.  
  59.             background: white;
  60.  
  61.             border-radius: 15px;
  62.  
  63.             box-shadow: 0 20px 40px rgba(0,0,0,0.1);
  64.  
  65.             overflow: hidden;
  66.  
  67.         }
  68.  
  69.         #trading-analyzer-root .header {
  70.  
  71.             background: linear-gradient(45deg, #2c3e50, #34495e);
  72.  
  73.             color: white;
  74.  
  75.             padding: 30px;
  76.  
  77.             text-align: center;
  78.  
  79.         }
  80.  
  81.         #trading-analyzer-root .header h1 {
  82.  
  83.             margin: 0;
  84.  
  85.             font-size: 2.5em;
  86.  
  87.             font-weight: 300;
  88.  
  89.         }
  90.  
  91.         #trading-analyzer-root .content {
  92.  
  93.             padding: 30px;
  94.  
  95.         }
  96.  
  97.         #trading-analyzer-root .input-section {
  98.  
  99.             margin-bottom: 30px;
  100.  
  101.         }
  102.  
  103.         #trading-analyzer-root .input-section label {
  104.  
  105.             display: block;
  106.  
  107.             margin-bottom: 10px;
  108.  
  109.             font-weight: bold;
  110.  
  111.             color: #2c3e50;
  112.  
  113.         }
  114.  
  115.         #trading-analyzer-root textarea {
  116.  
  117.             width: 100%;
  118.  
  119.             height: 200px;
  120.  
  121.             padding: 15px;
  122.  
  123.             border: 2px solid #e0e0e0;
  124.  
  125.             border-radius: 8px;
  126.  
  127.             font-family: monospace;
  128.  
  129.             font-size: 12px;
  130.  
  131.             resize: vertical;
  132.  
  133.             transition: border-color 0.3s ease;
  134.  
  135.             box-sizing: border-box; /* 패딩이 너비에 포함되도록 */
  136.  
  137.         }
  138.  
  139.         #trading-analyzer-root textarea:focus {
  140.  
  141.             outline: none;
  142.  
  143.             border-color: #667eea;
  144.  
  145.         }
  146.  
  147.         #trading-analyzer-root .btn {
  148.  
  149.             background: linear-gradient(45deg, #667eea, #764ba2);
  150.  
  151.             color: white;
  152.  
  153.             border: none;
  154.  
  155.             padding: 15px 30px;
  156.  
  157.             border-radius: 8px;
  158.  
  159.             cursor: pointer;
  160.  
  161.             font-size: 16px;
  162.  
  163.             font-weight: bold;
  164.  
  165.             transition: transform 0.2s ease;
  166.  
  167.             width: 100%; /* 버튼 너비 100%로 설정 */
  168.  
  169.             margin-top: 15px; /* textarea 아래 여백 추가 */
  170.  
  171.         }
  172.  
  173.         #trading-analyzer-root .btn:hover {
  174.  
  175.             transform: translateY(-2px);
  176.  
  177.         }
  178.  
  179.         #trading-analyzer-root .results {
  180.  
  181.             margin-top: 30px;
  182.  
  183.         }
  184.  
  185.         #trading-analyzer-root .stock-card {
  186.  
  187.             background: #f8f9fa;
  188.  
  189.             border-radius: 12px;
  190.  
  191.             padding: 20px;
  192.  
  193.             margin-bottom: 20px;
  194.  
  195.             border-left: 5px solid #667eea;
  196.  
  197.             transition: transform 0.2s ease;
  198.  
  199.         }
  200.  
  201.         #trading-analyzer-root .stock-card:hover {
  202.  
  203.             transform: translateX(5px);
  204.  
  205.         }
  206.  
  207.         #trading-analyzer-root .stock-name {
  208.  
  209.             font-size: 1.5em;
  210.  
  211.             font-weight: bold;
  212.  
  213.             color: #2c3e50;
  214.  
  215.             margin-bottom: 15px;
  216.  
  217.         }
  218.  
  219.         #trading-analyzer-root .trade-table {
  220.  
  221.             width: 100%;
  222.  
  223.             border-collapse: collapse;
  224.  
  225.             margin-top: 10px;
  226.  
  227.         }
  228.  
  229.         #trading-analyzer-root .trade-table th,
  230.  
  231.         #trading-analyzer-root .trade-table td {
  232.  
  233.             padding: 10px;
  234.  
  235.             text-align: left;
  236.  
  237.             border-bottom: 1px solid #e0e0e0;
  238.  
  239.         }
  240.  
  241.         #trading-analyzer-root .trade-table th {
  242.  
  243.             background: #667eea;
  244.  
  245.             color: white;
  246.  
  247.             font-weight: bold;
  248.  
  249.         }
  250.  
  251.         #trading-analyzer-root .profit {
  252.  
  253.             color: #e74c3c;
  254.  
  255.             font-weight: bold;
  256.  
  257.         }
  258.  
  259.         #trading-analyzer-root .loss {
  260.  
  261.             color: #3498db;
  262.  
  263.             font-weight: bold;
  264.  
  265.         }
  266.  
  267.         #trading-analyzer-root .summary-table {
  268.  
  269.             width: 100%;
  270.  
  271.             border-collapse: collapse;
  272.  
  273.             margin-bottom: 30px;
  274.  
  275.             background: white;
  276.  
  277.             border-radius: 10px;
  278.  
  279.             overflow: hidden;
  280.  
  281.             box-shadow: 0 5px 15px rgba(0,0,0,0.1);
  282.  
  283.         }
  284.  
  285.  
  286.  
  287.         #trading-analyzer-root .summary-table th,
  288.  
  289.         #trading-analyzer-root .summary-table td {
  290.  
  291.             padding: 12px 15px;
  292.  
  293.             text-align: center;
  294.  
  295.             border-bottom: 1px solid #e0e0e0;
  296.  
  297.         }
  298.  
  299.  
  300.  
  301.         #trading-analyzer-root .summary-table th {
  302.  
  303.             background: linear-gradient(45deg, #667eea, #764ba2);
  304.  
  305.             color: white;
  306.  
  307.             font-weight: bold;
  308.  
  309.         }
  310.  
  311.  
  312.  
  313.         #trading-analyzer-root .summary-table tr:hover {
  314.  
  315.             background-color: #f8f9fa;
  316.  
  317.         }
  318.  
  319.         #trading-analyzer-root .no-results {
  320.  
  321.             text-align: center;
  322.  
  323.             color: #7f8c8d;
  324.  
  325.             font-style: italic;
  326.  
  327.             padding: 40px;
  328.  
  329.         }
  330.  
  331.         #trading-analyzer-root .sample-btn {
  332.  
  333.             background: #95a5a6;
  334.  
  335.             color: white;
  336.  
  337.             border: none;
  338.  
  339.             padding: 10px 20px;
  340.  
  341.             border-radius: 5px;
  342.  
  343.             cursor: pointer;
  344.  
  345.             margin-left: 10px;
  346.  
  347.             margin-bottom: 10px; /* 버튼 아래 여백 추가 */
  348.  
  349.         }
  350.  
  351.         #trading-analyzer-root .sample-btn:hover {
  352.  
  353.             background: #7f8c8d;
  354.  
  355.         }
  356.  
  357.  
  358.  
  359.         /* 반응형 디자인 */
  360.  
  361.         @media (max-width: 768px) {
  362.  
  363.             #trading-analyzer-root .content {
  364.  
  365.                 padding: 15px;
  366.  
  367.             }
  368.  
  369.             #trading-analyzer-root .header h1 {
  370.  
  371.                 font-size: 2em;
  372.  
  373.             }
  374.  
  375.             #trading-analyzer-root .trade-table th,
  376.  
  377.             #trading-analyzer-root .trade-table td,
  378.  
  379.             #trading-analyzer-root .summary-table th,
  380.  
  381.             #trading-analyzer-root .summary-table td {
  382.  
  383.                 font-size: 12px;
  384.  
  385.                 padding: 8px;
  386.  
  387.             }
  388.  
  389.             #trading-analyzer-root .btn,
  390.  
  391.             #trading-analyzer-root .sample-btn {
  392.  
  393.                 padding: 10px 15px;
  394.  
  395.                 font-size: 14px;
  396.  
  397.             }
  398.  
  399.             #trading-analyzer-root .stock-name {
  400.  
  401.                 font-size: 1.2em;
  402.  
  403.             }
  404.  
  405.         }
  406.  
  407.     </style>
  408.  
  409. </head>
  410.  
  411. <body>
  412.  
  413.     <!-- 모든 분석기 콘텐츠를 고유한 ID를 가진 div로 감쌉니다. -->
  414.  
  415.     <div id="trading-analyzer-root">
  416.  
  417.         <div class="container">
  418.  
  419.             <div class="header">
  420.  
  421.                 <h1>📈 매매일지 분석기</h1>
  422.  
  423.                 <p>거래 데이터를 붙여넣으면 종목별 매수/매도 현황과 수익률을 자동 계산합니다</p>
  424.  
  425.             </div>
  426.  
  427.             <div class="content">
  428.  
  429.                 <div id="results" class="results"></div>
  430.  
  431.                 <div class="input-section">
  432.  
  433.                     <label for="dataInput">거래 데이터 입력:</label>
  434.  
  435.                     <button class="sample-btn" onclick="loadSampleData()">샘플 데이터 로드</button>
  436.  
  437.                     <textarea id="dataInput" placeholder="거래일자  거래종류    종목명 수량  가격  거래금액    수수료 세금  잔고수량    잔고금액
  438.  
  439. 형태의 탭으로 구분된 데이터를 붙여넣으세요..."></textarea>
  440.  
  441.                     <button class="btn" onclick="analyzeData()">📊 분석하기</button>
  442.  
  443.                 </div>
  444.  
  445.             </div>
  446.  
  447.         </div>
  448.  
  449.     </div>
  450.  
  451.  
  452.  
  453.     <script>
  454.  
  455.         /**
  456.  
  457.          * 문자열에서 쉼표를 제거하고 정수로 파싱합니다.
  458.  
  459.          * 유효하지 않은 입력은 0을 반환합니다.
  460.  
  461.          * @param {string} str - 파싱할 문자열.
  462.  
  463.          * @returns {number} 파싱된 숫자 또는 0.
  464.  
  465.          */
  466.  
  467.         function parseNumber(str) {
  468.  
  469.             if (!str || str === '') return 0;
  470.  
  471.             return parseInt(str.replace(/,/g, ''));
  472.  
  473.         }
  474.  
  475.  
  476.  
  477.         /**
  478.  
  479.          * 숫자를 한국 로케일 형식으로 포맷합니다 (쉼표 추가).
  480.  
  481.          * @param {number} num - 포맷할 숫자.
  482.  
  483.          * @returns {string} 포맷된 숫자 문자열.
  484.  
  485.          */
  486.  
  487.         function formatNumber(num) {
  488.  
  489.             return num.toLocaleString('ko-KR');
  490.  
  491.         }
  492.  
  493.  
  494.  
  495.         /**
  496.  
  497.          * 날짜 문자열의 슬래시를 하이픈으로 바꿉니다.
  498.  
  499.          * @param {string} dateStr - 날짜 문자열 (예: "2017/10/23").
  500.  
  501.          * @returns {string} 포맷된 날짜 문자열 (예: "2017-10-23").
  502.  
  503.          */
  504.  
  505.         function formatDate(dateStr) {
  506.  
  507.             return dateStr.replace(/\//g, '-');
  508.  
  509.         }
  510.  
  511.  
  512.  
  513.         /**
  514.  
  515.          * 입력된 거래 데이터를 분석하여 종목별 매매 현황과 수익률을 계산합니다.
  516.  
  517.          */
  518.  
  519.         function analyzeData() {
  520.  
  521.             const input = document.getElementById('dataInput').value.trim();
  522.  
  523.             if (!input) {
  524.  
  525.                 alert('데이터를 입력해주세요.'); // 사용자 정의 알림으로 변경 가능
  526.  
  527.                 return;
  528.  
  529.             }
  530.  
  531.  
  532.  
  533.             const lines = input.split('\n');
  534.  
  535.             // 첫 번째 줄은 헤더이므로 건너뜁니다.
  536.  
  537.             // const header = lines[0].split('\t');
  538.  
  539.             const trades = [];
  540.  
  541.  
  542.  
  543.             // 분석에서 제외할 종목명 또는 키워드 목록
  544.  
  545.             const excludeStocks = ['QV CMA형', 'CMA', '입금', '출금', '이체'];
  546.  
  547.  
  548.  
  549.             // 헤더를 제외하고 데이터 파싱
  550.  
  551.             for (let i = 1; i < lines.length; i++) {
  552.  
  553.                 const row = lines[i].split('\t');
  554.  
  555.                 // 행의 길이가 충분하고 종목명이 존재하며 제외 목록에 없는 경우에만 처리
  556.  
  557.                 if (row.length >= 10 && row[2] && !excludeStocks.some(exclude => row[2].includes(exclude))) {
  558.  
  559.                     const trade = {
  560.  
  561.                         date: row[0],
  562.  
  563.                         type: row[1],
  564.  
  565.                         stock: row[2],
  566.  
  567.                         quantity: parseNumber(row[3]),
  568.  
  569.                         price: parseNumber(row[4]),
  570.  
  571.                         amount: parseNumber(row[5]),
  572.  
  573.                         fee: parseNumber(row[6]),
  574.  
  575.                         tax: parseNumber(row[7])
  576.  
  577.                         // 잔고수량, 잔고금액은 현재 분석에 사용하지 않으므로 포함하지 않음
  578.  
  579.                     };
  580.  
  581.                     trades.push(trade);
  582.  
  583.                 }
  584.  
  585.             }
  586.  
  587.  
  588.  
  589.             // 종목별로 거래를 그룹화
  590.  
  591.             const stockGroups = {};
  592.  
  593.             trades.forEach(trade => {
  594.  
  595.                 if (!stockGroups[trade.stock]) {
  596.  
  597.                     stockGroups[trade.stock] = [];
  598.  
  599.                 }
  600.  
  601.                 stockGroups[trade.stock].push(trade);
  602.  
  603.             });
  604.  
  605.  
  606.  
  607.             // 각 종목에 대한 분석 결과 생성
  608.  
  609.             const results = [];
  610.  
  611.             Object.keys(stockGroups).forEach(stockName => {
  612.  
  613.                 const stockTrades = stockGroups[stockName];
  614.  
  615.                 // 매수/매도 거래를 구분하고 날짜순으로 정렬
  616.  
  617.                 const buyTrades = stockTrades.filter(t => t.type.includes('매수')).sort((a, b) => new Date(a.date) - new Date(b.date));
  618.  
  619.                 const sellTrades = stockTrades.filter(t => t.type.includes('매도')).sort((a, b) => new Date(a.date) - new Date(b.date));
  620.  
  621.  
  622.  
  623.                 // 매수 정보 집계
  624.  
  625.                 const totalBuyQuantity = buyTrades.reduce((sum, t) => sum + t.quantity, 0);
  626.  
  627.                 const totalBuyAmount = buyTrades.reduce((sum, t) => sum + t.amount, 0);
  628.  
  629.                 const totalBuyFee = buyTrades.reduce((sum, t) => sum + t.fee, 0);
  630.  
  631.                 // 매수 평균단가 (매수금액 + 매수수수료를 총 매수 수량으로 나눔)
  632.  
  633.                 const avgBuyPrice = totalBuyQuantity > 0 ? Math.round((totalBuyAmount + totalBuyFee) / totalBuyQuantity) : 0;
  634.  
  635.  
  636.  
  637.                 // 매도 정보 집계
  638.  
  639.                 const totalSellQuantity = sellTrades.reduce((sum, t) => sum + t.quantity, 0);
  640.  
  641.                 const totalSellAmount = sellTrades.reduce((sum, t) => sum + t.amount, 0);
  642.  
  643.                 const totalSellFee = sellTrades.reduce((sum, t) => sum + t.fee, 0);
  644.  
  645.                 const totalSellTax = sellTrades.reduce((sum, t) => sum + t.tax, 0);
  646.  
  647.                 // 매도 평균단가 (매도금액 - 매도수수료 - 매도세금을 총 매도 수량으로 나눔)
  648.  
  649.                 const avgSellPrice = totalSellQuantity > 0 ? Math.round((totalSellAmount - totalSellFee - totalSellTax) / totalSellQuantity) : 0;
  650.  
  651.  
  652.  
  653.                 // 매매기간 계산 (모든 거래 중 가장 이른 날짜와 가장 늦은 날짜)
  654.  
  655.                 const allTrades = [...buyTrades, ...sellTrades].sort((a, b) => new Date(a.date) - new Date(b.date));
  656.  
  657.                 const firstTradeDate = allTrades.length > 0 ? formatDate(allTrades[0].date) : '';
  658.  
  659.                 const lastTradeDate = allTrades.length > 0 ? formatDate(allTrades[allTrades.length - 1].date) : '';
  660.  
  661.                 const tradingPeriod = firstTradeDate === lastTradeDate ? firstTradeDate : `${firstTradeDate} ~ ${lastTradeDate}`;
  662.  
  663.  
  664.  
  665.                 // 실현손익 계산: (총 매도 금액 - 총 매도 수수료 - 총 매도 세금) - (매도된 수량에 대한 매수 원가)
  666.  
  667.                 // 매수 원가는 평균 매수단가 * 총 매도 수량으로 계산
  668.  
  669.                 const realizedProfit = (totalSellAmount - totalSellFee - totalSellTax) - (avgBuyPrice * totalSellQuantity);
  670.  
  671.                 // 실현 수익률 계산: (평균 매도단가 - 평균 매수단가) / 평균 매수단가 * 100
  672.  
  673.                 const realizedProfitRate = totalSellQuantity > 0 && avgBuyPrice > 0 ? ((avgSellPrice - avgBuyPrice) / avgBuyPrice * 100) : 0;
  674.  
  675.  
  676.  
  677.                 // 현재 보유 수량
  678.  
  679.                 const remainingQuantity = totalBuyQuantity - totalSellQuantity;
  680.  
  681.  
  682.  
  683.                 results.push({
  684.  
  685.                     stockName,
  686.  
  687.                     buyTrades,
  688.  
  689.                     sellTrades,
  690.  
  691.                     allTrades: allTrades, // 상세 내역 표시를 위해 모든 거래 포함
  692.  
  693.                     totalBuyQuantity,
  694.  
  695.                     totalSellQuantity,
  696.  
  697.                     remainingQuantity,
  698.  
  699.                     avgBuyPrice,
  700.  
  701.                     avgSellPrice,
  702.  
  703.                     totalBuyAmount: totalBuyAmount + totalBuyFee, // 실제 지불한 총 매수 금액
  704.  
  705.                     totalSellAmount: totalSellAmount - totalSellFee - totalSellTax, // 실제 수령한 총 매도 금액
  706.  
  707.                     realizedProfit,
  708.  
  709.                     realizedProfitRate,
  710.  
  711.                     tradingPeriod
  712.  
  713.                 });
  714.  
  715.             });
  716.  
  717.  
  718.  
  719.             displayResults(results);
  720.  
  721.         }
  722.  
  723.  
  724.  
  725.         /**
  726.  
  727.          * 분석 결과를 HTML로 생성하여 화면에 표시합니다.
  728.  
  729.          * @param {Array<Object>} results - 분석된 종목별 데이터 배열.
  730.  
  731.          */
  732.  
  733.         function displayResults(results) {
  734.  
  735.             const resultsDiv = document.getElementById('results');
  736.  
  737.             if (results.length === 0) {
  738.  
  739.                 resultsDiv.innerHTML = '<div class="no-results">분석할 데이터가 없습니다.</div>';
  740.  
  741.                 return;
  742.  
  743.             }
  744.  
  745.  
  746.  
  747.             // 전체 투자 현황 계산
  748.  
  749.             const totalRealizedProfit = results.reduce((sum, result) => sum + result.realizedProfit, 0);
  750.  
  751.             // 총 매수 금액과 총 매도 금액은 각 종목의 순수 매수/매도 금액 합계
  752.  
  753.             const totalBuyAmountOverall = results.reduce((sum, result) => sum + result.totalBuyAmount, 0);
  754.  
  755.             const totalSellAmountOverall = results.reduce((sum, result) => sum + result.totalSellAmount, 0);
  756.  
  757.             // 전체 수익률 계산: (총 실현 매도 금액 - 총 실현 매수 금액) / 총 실현 매수 금액 * 100
  758.  
  759.             // 여기서 '총 실현 매수 금액'은 매도된 주식에 대한 원가만을 의미해야 함.
  760.  
  761.             // 단순하게 총 매도 금액 / 총 매수 금액으로 계산하면 보유 주식이 있는 경우 왜곡될 수 있음.
  762.  
  763.             // 정확한 전체 수익률 계산을 위해, 각 종목의 실현손익을 합산하고,
  764.  
  765.             // 매도된 부분에 대한 총 매수 원가를 계산해야 하지만,
  766.  
  767.             // 현재 데이터 구조로는 복잡하므로, 단순화된 총 실현손익과 총 매도금액을 기준으로 표시.
  768.  
  769.             // 보다 정확한 전체 수익률은 모든 매수/매도 거래를 통합하여 계산해야 함.
  770.  
  771.             const overallProfitRate = totalBuyAmountOverall > 0 ? ((totalSellAmountOverall - totalBuyAmountOverall) / totalBuyAmountOverall * 100) : 0;
  772.  
  773.  
  774.  
  775.             // 전체 매매 기간 계산
  776.  
  777.             const allDates = [];
  778.  
  779.             results.forEach(result => {
  780.  
  781.                 result.allTrades.forEach(trade => {
  782.  
  783.                     allDates.push(new Date(trade.date));
  784.  
  785.                 });
  786.  
  787.             });
  788.  
  789.             const overallStartDate = allDates.length > 0 ? formatDate(new Date(Math.min(...allDates)).toISOString().split('T')[0]) : '';
  790.  
  791.             const overallEndDate = allDates.length > 0 ? formatDate(new Date(Math.max(...allDates)).toISOString().split('T')[0]) : '';
  792.  
  793.             const overallPeriod = overallStartDate === overallEndDate ? overallStartDate : `${overallStartDate} ~ ${overallEndDate}`;
  794.  
  795.  
  796.  
  797.             let html = `
  798.  
  799.                 <div style="background: white; color: #333; padding: 20px; border-radius: 10px; margin-bottom: 30px; text-align: center;">
  800.  
  801.                     <h2 style="margin: 0 0 15px 0; color: #2c3e50;">📊 전체 투자 현황</h2>
  802.  
  803.                     <div style="display: flex; justify-content: space-around; flex-wrap: wrap; gap: 20px;">
  804.  
  805.                         <div>
  806.  
  807.                             <div style="font-size: 0.9em; opacity: 1;">총 기간</div>
  808.  
  809.                             <div style="font-size: 1.2em; font-weight: bold;">${overallPeriod}</div>
  810.  
  811.                         </div>
  812.  
  813.                         <div>
  814.  
  815.                             <div style="font-size: 0.9em; opacity: 1;">총 실현손익 (단위:원)</div>
  816.  
  817.                             <div style="font-size: 1.2em; font-weight: bold; color: ${totalRealizedProfit >= 0 ? '#e74c3c' : '#3498db'};">${totalRealizedProfit >= 0 ? '+' : ''}${formatNumber(totalRealizedProfit)}</div>
  818.  
  819.                         </div>
  820.  
  821.                     </div>
  822.  
  823.                 </div>
  824.  
  825.                 <h2>📊 종목별 매매 요약</h2>
  826.  
  827.             `;
  828.  
  829.             // 상단 요약 테이블
  830.  
  831.             html += `
  832.  
  833.                 <table class="summary-table">
  834.  
  835.                     <thead>
  836.  
  837.                         <tr>
  838.  
  839.                             <th>종목명</th>
  840.  
  841.                             <th>매매기간</th>
  842.  
  843.                             <th>실현수익률</th>
  844.  
  845.                             <th>실현손익 (단위:원)</th>
  846.  
  847.                             <th>보유수량</th>
  848.  
  849.                         </tr>
  850.  
  851.                     </thead>
  852.  
  853.                     <tbody>
  854.  
  855.             `;
  856.  
  857.  
  858.  
  859.             results.forEach(result => {
  860.  
  861.                 const profitClass = result.realizedProfit >= 0 ? 'profit' : 'loss';
  862.  
  863.                 const profitSign = result.realizedProfit >= 0 ? '+' : '';
  864.  
  865.                 html += `
  866.  
  867.                     <tr>
  868.  
  869.                         <td><strong>${result.stockName}</strong></td>
  870.  
  871.                         <td>${result.tradingPeriod}</td>
  872.  
  873.                         <td class="${profitClass}">${result.realizedProfitRate.toFixed(2)}%</td>
  874.  
  875.                         <td class="${profitClass}">${profitSign}${formatNumber(result.realizedProfit)}</td>
  876.  
  877.                         <td>${formatNumber(result.remainingQuantity)}</td>
  878.  
  879.                     </tr>
  880.  
  881.                 `;
  882.  
  883.             });
  884.  
  885.  
  886.  
  887.             html += `
  888.  
  889.                     </tbody>
  890.  
  891.                 </table>
  892.  
  893.                 <h2>📈 종목별 상세 거래 내역</h2>
  894.  
  895.             `;
  896.  
  897.  
  898.  
  899.             // 종목별 상세 내역
  900.  
  901.             results.forEach(result => {
  902.  
  903.                 const profitClass = result.realizedProfit >= 0 ? 'profit' : 'loss';
  904.  
  905.                 const profitSign = result.realizedProfit >= 0 ? '+' : '';
  906.  
  907.                 html += `
  908.  
  909.                     <div class="stock-card">
  910.  
  911.                         <div class="stock-name">${result.stockName}</div>
  912.  
  913.                         <div style="background: linear-gradient(45deg, #f39c12, #f1c40f); color: white; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
  914.  
  915.                             <strong>요약:</strong>
  916.  
  917.                             매수 ${formatNumber(result.totalBuyQuantity)}주 (평균 ${formatNumber(result.avgBuyPrice)}원) →
  918.  
  919.                             매도 ${formatNumber(result.totalSellQuantity)}주 (평균 ${formatNumber(result.avgSellPrice)}원) →
  920.  
  921.                             보유 ${formatNumber(result.remainingQuantity)}주
  922.  
  923.                             ${result.totalSellQuantity > 0 ? `| 실현손익: <span class="${profitClass}">${profitSign}${formatNumber(result.realizedProfit)}원 (${result.realizedProfitRate.toFixed(2)}%)</span>` : ''}
  924.  
  925.                         </div>
  926.  
  927.  
  928.  
  929.                         <table class="trade-table">
  930.  
  931.                             <thead>
  932.  
  933.                                 <tr>
  934.  
  935.                                     <th>날짜</th>
  936.  
  937.                                     <th>구분</th>
  938.  
  939.                                     <th>수량</th>
  940.  
  941.                                     <th>가격 (원)</th>
  942.  
  943.                                     <th>거래금액 (원)</th>
  944.  
  945.                                     <th>수수료+세금 (원)</th>
  946.  
  947.                                 </tr>
  948.  
  949.                             </thead>
  950.  
  951.                             <tbody>
  952.  
  953.                 `;
  954.  
  955.  
  956.  
  957.                 // 모든 거래를 날짜순으로 정렬해서 표시
  958.  
  959.                 result.allTrades.forEach(trade => {
  960.  
  961.                     const isBuy = trade.type.includes('매수');
  962.  
  963.                     const bgColor = isBuy ? '#ffe6e6' : '#e6f3ff'; // 매수/매도에 따라 배경색 변경
  964.  
  965.                     html += `
  966.  
  967.                         <tr style="background-color: ${bgColor};">
  968.  
  969.                             <td>${formatDate(trade.date)}</td>
  970.  
  971.                             <td>${trade.type}</td>
  972.  
  973.                             <td>${formatNumber(trade.quantity)}</td>
  974.  
  975.                             <td>${formatNumber(trade.price)}</td>
  976.  
  977.                             <td>${formatNumber(trade.amount)}</td>
  978.  
  979.                             <td>${formatNumber(trade.fee + trade.tax)}</td>
  980.  
  981.                         </tr>
  982.  
  983.                     `;
  984.  
  985.                 });
  986.  
  987.  
  988.  
  989.                 html += `
  990.  
  991.                             </tbody>
  992.  
  993.                         </table>
  994.  
  995.                     </div>
  996.  
  997.                 `;
  998.  
  999.             });
  1000.  
  1001.  
  1002.  
  1003.             resultsDiv.innerHTML = html;
  1004.  
  1005.         }
  1006.  
  1007.  
  1008.  
  1009.         /**
  1010.  
  1011.          * 샘플 거래 데이터를 텍스트 영역에 로드합니다.
  1012.  
  1013.          */
  1014.  
  1015.         function loadSampleData() {
  1016.  
  1017.             const sampleData = `거래일자    거래종류    종목명 수량  가격  거래금액    수수료 세금  잔고수량    잔고금액
  1018.  
  1019. 2017/10/23  이체입금                5,000,000               5,000,000
  1020.  
  1021. 2017/10/23  조건부매수   QV CMA형 1,034,574   1   1,034,574           1,034,574   3,965,426
  1022.  
  1023. 2017/10/24  이체입금                5,011,240               8,976,666
  1024.  
  1025. 2017/10/24  조건부매수   QV CMA형 456,821 1   456,821         456,821 8,519,845
  1026.  
  1027. 2017/10/25  코스피매도   한세예스24홀딩스   14  9,260   129,640         -14 8,649,485
  1028.  
  1029. 2017/10/25  코스피매도   한세예스24홀딩스   93  9,250   860,250 50  2,966   -107    9,506,719
  1030.  
  1031. 2017/10/25  코스피매수   한국화장품제조 29  33,700  977,300 50      29  8,529,369
  1032.  
  1033. 2017/10/25  코스피매수   카프로 117 8,490   993,330 50      117 7,535,989
  1034.  
  1035. 2017/10/25  코스피매수   한세예스24홀딩스   107 9,300   995,100 50          6,540,839
  1036.  
  1037. 2017/10/25  코스피매수   제이준코스메틱 144 6,930   997,920 50      144 5,542,869
  1038.  
  1039. 2017/10/25  코스피매수   조선선재    14  70,600  988,400 50      14  4,554,419
  1040.  
  1041. 2017/10/25  환매도 QV CMA형 1,034,574   1   1,034,626               5,589,045
  1042.  
  1043. 2017/10/25  환매도 QV CMA형 456,252 1   456,264         569 6,045,309
  1044.  
  1045. 2017/10/26  코스피매도   카프로 117 8,320   973,440 50  2,920       7,015,779
  1046.  
  1047. 2017/10/26  KOSDAQ매도    포스코DX   557 7,020   3,910,140   200 11,726  -557    10,913,993
  1048.  
  1049. 2017/10/26  코스피매수   SK하이닉스  18  82,000  1,476,000   70      18  9,437,923
  1050.  
  1051. 2017/10/26  코스피매수   한국화장품제조 10  31,850  318,500         39  9,119,423
  1052.  
  1053. 2017/10/26  코스피매수   한국화장품제조 10  32,200  322,000         49  8,797,423
  1054.  
  1055. 2017/10/26  코스피매수   한국화장품제조 10  32,400  324,000         59  8,473,423
  1056.  
  1057. 2017/10/26  코스피매수   한국화장품제조 20  32,150  643,000 80      79  7,830,343
  1058.  
  1059. 2017/10/26  KOSDAQ매수    포스코DX   70  7,100   497,000         -487    7,333,343
  1060.  
  1061. 2017/10/26  KOSDAQ매수    포스코DX   100 7,040   704,000         -387    6,629,343
  1062.  
  1063. 2017/10/26  KOSDAQ매수    포스코DX   100 7,090   709,000         -287    5,920,343
  1064.  
  1065. 2017/10/26  KOSDAQ매수    포스코DX   145 6,850   993,250         -142    4,927,093
  1066.  
  1067. 2017/10/26  KOSDAQ매수    포스코DX   142 7,080   1,005,360   200         3,921,533
  1068.  
  1069. 2017/10/26  코스피매수   카카오 1   151,000 151,000         1   3,770,533
  1070.  
  1071. 2017/10/26  코스피매수   카카오 1   151,500 151,500         2   3,619,033
  1072.  
  1073. 2017/10/26  코스피매수   카카오 14  152,000 2,128,000   120     16  1,490,913
  1074.  
  1075. 2017/10/26  이체입금                7,501,360               8,992,273`;
  1076.  
  1077.             document.getElementById('dataInput').value = sampleData;
  1078.  
  1079.             analyzeData(); // 샘플 데이터 로드 후 바로 분석 실행
  1080.  
  1081.         }
  1082.  
  1083.  
  1084.  
  1085.         // 페이지 로드시 샘플 데이터 자동 로드 (사용자가 필요시 버튼 클릭하도록 변경)
  1086.  
  1087.         document.addEventListener('DOMContentLoaded', function() {
  1088.  
  1089.             // 초기 로드 시 샘플 데이터를 자동으로 로드하고 분석합니다.
  1090.  
  1091.             loadSampleData();
  1092.  
  1093.         });
  1094.  
  1095.     </script>
  1096.  
  1097. </body>
  1098.  
  1099. </html>
Advertisement
Add Comment
Please, Sign In to add comment