Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="ko">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>매매일지 분석기</title>
- <style>
- /*
- * 모든 CSS 스타일을 '#trading-analyzer-root' ID 내부로 범위화하여
- * 다른 웹사이트의 전역 CSS와 충돌하지 않도록 합니다.
- */
- #trading-analyzer-root {
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- max-width: 1400px; /* 컨테이너의 최대 너비 */
- margin: 20px auto; /* 중앙 정렬 및 상하 여백 추가 */
- padding: 0; /* 루트 컨테이너 자체의 패딩은 제거 */
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); /* 배경 그라디언트 */
- min-height: 100vh; /* 최소 높이 설정 */
- border-radius: 15px; /* 컨테이너 자체에도 둥근 모서리 적용 */
- box-shadow: 0 20px 40px rgba(0,0,0,0.1); /* 그림자 효과 */
- overflow: hidden; /* 자식 요소가 넘치지 않도록 */
- display: flex; /* 내부 콘텐츠를 중앙에 정렬하기 위해 flexbox 사용 */
- flex-direction: column; /* 세로 방향으로 정렬 */
- justify-content: center; /* 수직 중앙 정렬 */
- align-items: center; /* 수평 중앙 정렬 */
- }
- /* 기존 body 스타일 중 분석기에 필요한 부분만 #trading-analyzer-root 내부로 이동 */
- #trading-analyzer-root .container {
- width: 100%; /* 부모 너비에 맞춤 */
- background: white;
- border-radius: 15px;
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
- overflow: hidden;
- }
- #trading-analyzer-root .header {
- background: linear-gradient(45deg, #2c3e50, #34495e);
- color: white;
- padding: 30px;
- text-align: center;
- }
- #trading-analyzer-root .header h1 {
- margin: 0;
- font-size: 2.5em;
- font-weight: 300;
- }
- #trading-analyzer-root .content {
- padding: 30px;
- }
- #trading-analyzer-root .input-section {
- margin-bottom: 30px;
- }
- #trading-analyzer-root .input-section label {
- display: block;
- margin-bottom: 10px;
- font-weight: bold;
- color: #2c3e50;
- }
- #trading-analyzer-root textarea {
- width: 100%;
- height: 200px;
- padding: 15px;
- border: 2px solid #e0e0e0;
- border-radius: 8px;
- font-family: monospace;
- font-size: 12px;
- resize: vertical;
- transition: border-color 0.3s ease;
- box-sizing: border-box; /* 패딩이 너비에 포함되도록 */
- }
- #trading-analyzer-root textarea:focus {
- outline: none;
- border-color: #667eea;
- }
- #trading-analyzer-root .btn {
- background: linear-gradient(45deg, #667eea, #764ba2);
- color: white;
- border: none;
- padding: 15px 30px;
- border-radius: 8px;
- cursor: pointer;
- font-size: 16px;
- font-weight: bold;
- transition: transform 0.2s ease;
- width: 100%; /* 버튼 너비 100%로 설정 */
- margin-top: 15px; /* textarea 아래 여백 추가 */
- }
- #trading-analyzer-root .btn:hover {
- transform: translateY(-2px);
- }
- #trading-analyzer-root .results {
- margin-top: 30px;
- }
- #trading-analyzer-root .stock-card {
- background: #f8f9fa;
- border-radius: 12px;
- padding: 20px;
- margin-bottom: 20px;
- border-left: 5px solid #667eea;
- transition: transform 0.2s ease;
- }
- #trading-analyzer-root .stock-card:hover {
- transform: translateX(5px);
- }
- #trading-analyzer-root .stock-name {
- font-size: 1.5em;
- font-weight: bold;
- color: #2c3e50;
- margin-bottom: 15px;
- }
- #trading-analyzer-root .trade-table {
- width: 100%;
- border-collapse: collapse;
- margin-top: 10px;
- }
- #trading-analyzer-root .trade-table th,
- #trading-analyzer-root .trade-table td {
- padding: 10px;
- text-align: left;
- border-bottom: 1px solid #e0e0e0;
- }
- #trading-analyzer-root .trade-table th {
- background: #667eea;
- color: white;
- font-weight: bold;
- }
- #trading-analyzer-root .profit {
- color: #e74c3c;
- font-weight: bold;
- }
- #trading-analyzer-root .loss {
- color: #3498db;
- font-weight: bold;
- }
- #trading-analyzer-root .summary-table {
- width: 100%;
- border-collapse: collapse;
- margin-bottom: 30px;
- background: white;
- border-radius: 10px;
- overflow: hidden;
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
- }
- #trading-analyzer-root .summary-table th,
- #trading-analyzer-root .summary-table td {
- padding: 12px 15px;
- text-align: center;
- border-bottom: 1px solid #e0e0e0;
- }
- #trading-analyzer-root .summary-table th {
- background: linear-gradient(45deg, #667eea, #764ba2);
- color: white;
- font-weight: bold;
- }
- #trading-analyzer-root .summary-table tr:hover {
- background-color: #f8f9fa;
- }
- #trading-analyzer-root .no-results {
- text-align: center;
- color: #7f8c8d;
- font-style: italic;
- padding: 40px;
- }
- #trading-analyzer-root .sample-btn {
- background: #95a5a6;
- color: white;
- border: none;
- padding: 10px 20px;
- border-radius: 5px;
- cursor: pointer;
- margin-left: 10px;
- margin-bottom: 10px; /* 버튼 아래 여백 추가 */
- }
- #trading-analyzer-root .sample-btn:hover {
- background: #7f8c8d;
- }
- /* 반응형 디자인 */
- @media (max-width: 768px) {
- #trading-analyzer-root .content {
- padding: 15px;
- }
- #trading-analyzer-root .header h1 {
- font-size: 2em;
- }
- #trading-analyzer-root .trade-table th,
- #trading-analyzer-root .trade-table td,
- #trading-analyzer-root .summary-table th,
- #trading-analyzer-root .summary-table td {
- font-size: 12px;
- padding: 8px;
- }
- #trading-analyzer-root .btn,
- #trading-analyzer-root .sample-btn {
- padding: 10px 15px;
- font-size: 14px;
- }
- #trading-analyzer-root .stock-name {
- font-size: 1.2em;
- }
- }
- </style>
- </head>
- <body>
- <!-- 모든 분석기 콘텐츠를 고유한 ID를 가진 div로 감쌉니다. -->
- <div id="trading-analyzer-root">
- <div class="container">
- <div class="header">
- <h1>📈 매매일지 분석기</h1>
- <p>거래 데이터를 붙여넣으면 종목별 매수/매도 현황과 수익률을 자동 계산합니다</p>
- </div>
- <div class="content">
- <div id="results" class="results"></div>
- <div class="input-section">
- <label for="dataInput">거래 데이터 입력:</label>
- <button class="sample-btn" onclick="loadSampleData()">샘플 데이터 로드</button>
- <textarea id="dataInput" placeholder="거래일자 거래종류 종목명 수량 가격 거래금액 수수료 세금 잔고수량 잔고금액
- 형태의 탭으로 구분된 데이터를 붙여넣으세요..."></textarea>
- <button class="btn" onclick="analyzeData()">📊 분석하기</button>
- </div>
- </div>
- </div>
- </div>
- <script>
- /**
- * 문자열에서 쉼표를 제거하고 정수로 파싱합니다.
- * 유효하지 않은 입력은 0을 반환합니다.
- * @param {string} str - 파싱할 문자열.
- * @returns {number} 파싱된 숫자 또는 0.
- */
- function parseNumber(str) {
- if (!str || str === '') return 0;
- return parseInt(str.replace(/,/g, ''));
- }
- /**
- * 숫자를 한국 로케일 형식으로 포맷합니다 (쉼표 추가).
- * @param {number} num - 포맷할 숫자.
- * @returns {string} 포맷된 숫자 문자열.
- */
- function formatNumber(num) {
- return num.toLocaleString('ko-KR');
- }
- /**
- * 날짜 문자열의 슬래시를 하이픈으로 바꿉니다.
- * @param {string} dateStr - 날짜 문자열 (예: "2017/10/23").
- * @returns {string} 포맷된 날짜 문자열 (예: "2017-10-23").
- */
- function formatDate(dateStr) {
- return dateStr.replace(/\//g, '-');
- }
- /**
- * 입력된 거래 데이터를 분석하여 종목별 매매 현황과 수익률을 계산합니다.
- */
- function analyzeData() {
- const input = document.getElementById('dataInput').value.trim();
- if (!input) {
- alert('데이터를 입력해주세요.'); // 사용자 정의 알림으로 변경 가능
- return;
- }
- const lines = input.split('\n');
- // 첫 번째 줄은 헤더이므로 건너뜁니다.
- // const header = lines[0].split('\t');
- const trades = [];
- // 분석에서 제외할 종목명 또는 키워드 목록
- const excludeStocks = ['QV CMA형', 'CMA', '입금', '출금', '이체'];
- // 헤더를 제외하고 데이터 파싱
- for (let i = 1; i < lines.length; i++) {
- const row = lines[i].split('\t');
- // 행의 길이가 충분하고 종목명이 존재하며 제외 목록에 없는 경우에만 처리
- if (row.length >= 10 && row[2] && !excludeStocks.some(exclude => row[2].includes(exclude))) {
- const trade = {
- date: row[0],
- type: row[1],
- stock: row[2],
- quantity: parseNumber(row[3]),
- price: parseNumber(row[4]),
- amount: parseNumber(row[5]),
- fee: parseNumber(row[6]),
- tax: parseNumber(row[7])
- // 잔고수량, 잔고금액은 현재 분석에 사용하지 않으므로 포함하지 않음
- };
- trades.push(trade);
- }
- }
- // 종목별로 거래를 그룹화
- const stockGroups = {};
- trades.forEach(trade => {
- if (!stockGroups[trade.stock]) {
- stockGroups[trade.stock] = [];
- }
- stockGroups[trade.stock].push(trade);
- });
- // 각 종목에 대한 분석 결과 생성
- const results = [];
- Object.keys(stockGroups).forEach(stockName => {
- const stockTrades = stockGroups[stockName];
- // 매수/매도 거래를 구분하고 날짜순으로 정렬
- const buyTrades = stockTrades.filter(t => t.type.includes('매수')).sort((a, b) => new Date(a.date) - new Date(b.date));
- const sellTrades = stockTrades.filter(t => t.type.includes('매도')).sort((a, b) => new Date(a.date) - new Date(b.date));
- // 매수 정보 집계
- const totalBuyQuantity = buyTrades.reduce((sum, t) => sum + t.quantity, 0);
- const totalBuyAmount = buyTrades.reduce((sum, t) => sum + t.amount, 0);
- const totalBuyFee = buyTrades.reduce((sum, t) => sum + t.fee, 0);
- // 매수 평균단가 (매수금액 + 매수수수료를 총 매수 수량으로 나눔)
- const avgBuyPrice = totalBuyQuantity > 0 ? Math.round((totalBuyAmount + totalBuyFee) / totalBuyQuantity) : 0;
- // 매도 정보 집계
- const totalSellQuantity = sellTrades.reduce((sum, t) => sum + t.quantity, 0);
- const totalSellAmount = sellTrades.reduce((sum, t) => sum + t.amount, 0);
- const totalSellFee = sellTrades.reduce((sum, t) => sum + t.fee, 0);
- const totalSellTax = sellTrades.reduce((sum, t) => sum + t.tax, 0);
- // 매도 평균단가 (매도금액 - 매도수수료 - 매도세금을 총 매도 수량으로 나눔)
- const avgSellPrice = totalSellQuantity > 0 ? Math.round((totalSellAmount - totalSellFee - totalSellTax) / totalSellQuantity) : 0;
- // 매매기간 계산 (모든 거래 중 가장 이른 날짜와 가장 늦은 날짜)
- const allTrades = [...buyTrades, ...sellTrades].sort((a, b) => new Date(a.date) - new Date(b.date));
- const firstTradeDate = allTrades.length > 0 ? formatDate(allTrades[0].date) : '';
- const lastTradeDate = allTrades.length > 0 ? formatDate(allTrades[allTrades.length - 1].date) : '';
- const tradingPeriod = firstTradeDate === lastTradeDate ? firstTradeDate : `${firstTradeDate} ~ ${lastTradeDate}`;
- // 실현손익 계산: (총 매도 금액 - 총 매도 수수료 - 총 매도 세금) - (매도된 수량에 대한 매수 원가)
- // 매수 원가는 평균 매수단가 * 총 매도 수량으로 계산
- const realizedProfit = (totalSellAmount - totalSellFee - totalSellTax) - (avgBuyPrice * totalSellQuantity);
- // 실현 수익률 계산: (평균 매도단가 - 평균 매수단가) / 평균 매수단가 * 100
- const realizedProfitRate = totalSellQuantity > 0 && avgBuyPrice > 0 ? ((avgSellPrice - avgBuyPrice) / avgBuyPrice * 100) : 0;
- // 현재 보유 수량
- const remainingQuantity = totalBuyQuantity - totalSellQuantity;
- results.push({
- stockName,
- buyTrades,
- sellTrades,
- allTrades: allTrades, // 상세 내역 표시를 위해 모든 거래 포함
- totalBuyQuantity,
- totalSellQuantity,
- remainingQuantity,
- avgBuyPrice,
- avgSellPrice,
- totalBuyAmount: totalBuyAmount + totalBuyFee, // 실제 지불한 총 매수 금액
- totalSellAmount: totalSellAmount - totalSellFee - totalSellTax, // 실제 수령한 총 매도 금액
- realizedProfit,
- realizedProfitRate,
- tradingPeriod
- });
- });
- displayResults(results);
- }
- /**
- * 분석 결과를 HTML로 생성하여 화면에 표시합니다.
- * @param {Array<Object>} results - 분석된 종목별 데이터 배열.
- */
- function displayResults(results) {
- const resultsDiv = document.getElementById('results');
- if (results.length === 0) {
- resultsDiv.innerHTML = '<div class="no-results">분석할 데이터가 없습니다.</div>';
- return;
- }
- // 전체 투자 현황 계산
- const totalRealizedProfit = results.reduce((sum, result) => sum + result.realizedProfit, 0);
- // 총 매수 금액과 총 매도 금액은 각 종목의 순수 매수/매도 금액 합계
- const totalBuyAmountOverall = results.reduce((sum, result) => sum + result.totalBuyAmount, 0);
- const totalSellAmountOverall = results.reduce((sum, result) => sum + result.totalSellAmount, 0);
- // 전체 수익률 계산: (총 실현 매도 금액 - 총 실현 매수 금액) / 총 실현 매수 금액 * 100
- // 여기서 '총 실현 매수 금액'은 매도된 주식에 대한 원가만을 의미해야 함.
- // 단순하게 총 매도 금액 / 총 매수 금액으로 계산하면 보유 주식이 있는 경우 왜곡될 수 있음.
- // 정확한 전체 수익률 계산을 위해, 각 종목의 실현손익을 합산하고,
- // 매도된 부분에 대한 총 매수 원가를 계산해야 하지만,
- // 현재 데이터 구조로는 복잡하므로, 단순화된 총 실현손익과 총 매도금액을 기준으로 표시.
- // 보다 정확한 전체 수익률은 모든 매수/매도 거래를 통합하여 계산해야 함.
- const overallProfitRate = totalBuyAmountOverall > 0 ? ((totalSellAmountOverall - totalBuyAmountOverall) / totalBuyAmountOverall * 100) : 0;
- // 전체 매매 기간 계산
- const allDates = [];
- results.forEach(result => {
- result.allTrades.forEach(trade => {
- allDates.push(new Date(trade.date));
- });
- });
- const overallStartDate = allDates.length > 0 ? formatDate(new Date(Math.min(...allDates)).toISOString().split('T')[0]) : '';
- const overallEndDate = allDates.length > 0 ? formatDate(new Date(Math.max(...allDates)).toISOString().split('T')[0]) : '';
- const overallPeriod = overallStartDate === overallEndDate ? overallStartDate : `${overallStartDate} ~ ${overallEndDate}`;
- let html = `
- <div style="background: white; color: #333; padding: 20px; border-radius: 10px; margin-bottom: 30px; text-align: center;">
- <h2 style="margin: 0 0 15px 0; color: #2c3e50;">📊 전체 투자 현황</h2>
- <div style="display: flex; justify-content: space-around; flex-wrap: wrap; gap: 20px;">
- <div>
- <div style="font-size: 0.9em; opacity: 1;">총 기간</div>
- <div style="font-size: 1.2em; font-weight: bold;">${overallPeriod}</div>
- </div>
- <div>
- <div style="font-size: 0.9em; opacity: 1;">총 실현손익 (단위:원)</div>
- <div style="font-size: 1.2em; font-weight: bold; color: ${totalRealizedProfit >= 0 ? '#e74c3c' : '#3498db'};">${totalRealizedProfit >= 0 ? '+' : ''}${formatNumber(totalRealizedProfit)}</div>
- </div>
- </div>
- </div>
- <h2>📊 종목별 매매 요약</h2>
- `;
- // 상단 요약 테이블
- html += `
- <table class="summary-table">
- <thead>
- <tr>
- <th>종목명</th>
- <th>매매기간</th>
- <th>실현수익률</th>
- <th>실현손익 (단위:원)</th>
- <th>보유수량</th>
- </tr>
- </thead>
- <tbody>
- `;
- results.forEach(result => {
- const profitClass = result.realizedProfit >= 0 ? 'profit' : 'loss';
- const profitSign = result.realizedProfit >= 0 ? '+' : '';
- html += `
- <tr>
- <td><strong>${result.stockName}</strong></td>
- <td>${result.tradingPeriod}</td>
- <td class="${profitClass}">${result.realizedProfitRate.toFixed(2)}%</td>
- <td class="${profitClass}">${profitSign}${formatNumber(result.realizedProfit)}</td>
- <td>${formatNumber(result.remainingQuantity)}</td>
- </tr>
- `;
- });
- html += `
- </tbody>
- </table>
- <h2>📈 종목별 상세 거래 내역</h2>
- `;
- // 종목별 상세 내역
- results.forEach(result => {
- const profitClass = result.realizedProfit >= 0 ? 'profit' : 'loss';
- const profitSign = result.realizedProfit >= 0 ? '+' : '';
- html += `
- <div class="stock-card">
- <div class="stock-name">${result.stockName}</div>
- <div style="background: linear-gradient(45deg, #f39c12, #f1c40f); color: white; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
- <strong>요약:</strong>
- 매수 ${formatNumber(result.totalBuyQuantity)}주 (평균 ${formatNumber(result.avgBuyPrice)}원) →
- 매도 ${formatNumber(result.totalSellQuantity)}주 (평균 ${formatNumber(result.avgSellPrice)}원) →
- 보유 ${formatNumber(result.remainingQuantity)}주
- ${result.totalSellQuantity > 0 ? `| 실현손익: <span class="${profitClass}">${profitSign}${formatNumber(result.realizedProfit)}원 (${result.realizedProfitRate.toFixed(2)}%)</span>` : ''}
- </div>
- <table class="trade-table">
- <thead>
- <tr>
- <th>날짜</th>
- <th>구분</th>
- <th>수량</th>
- <th>가격 (원)</th>
- <th>거래금액 (원)</th>
- <th>수수료+세금 (원)</th>
- </tr>
- </thead>
- <tbody>
- `;
- // 모든 거래를 날짜순으로 정렬해서 표시
- result.allTrades.forEach(trade => {
- const isBuy = trade.type.includes('매수');
- const bgColor = isBuy ? '#ffe6e6' : '#e6f3ff'; // 매수/매도에 따라 배경색 변경
- html += `
- <tr style="background-color: ${bgColor};">
- <td>${formatDate(trade.date)}</td>
- <td>${trade.type}</td>
- <td>${formatNumber(trade.quantity)}</td>
- <td>${formatNumber(trade.price)}</td>
- <td>${formatNumber(trade.amount)}</td>
- <td>${formatNumber(trade.fee + trade.tax)}</td>
- </tr>
- `;
- });
- html += `
- </tbody>
- </table>
- </div>
- `;
- });
- resultsDiv.innerHTML = html;
- }
- /**
- * 샘플 거래 데이터를 텍스트 영역에 로드합니다.
- */
- function loadSampleData() {
- const sampleData = `거래일자 거래종류 종목명 수량 가격 거래금액 수수료 세금 잔고수량 잔고금액
- 2017/10/23 이체입금 5,000,000 5,000,000
- 2017/10/23 조건부매수 QV CMA형 1,034,574 1 1,034,574 1,034,574 3,965,426
- 2017/10/24 이체입금 5,011,240 8,976,666
- 2017/10/24 조건부매수 QV CMA형 456,821 1 456,821 456,821 8,519,845
- 2017/10/25 코스피매도 한세예스24홀딩스 14 9,260 129,640 -14 8,649,485
- 2017/10/25 코스피매도 한세예스24홀딩스 93 9,250 860,250 50 2,966 -107 9,506,719
- 2017/10/25 코스피매수 한국화장품제조 29 33,700 977,300 50 29 8,529,369
- 2017/10/25 코스피매수 카프로 117 8,490 993,330 50 117 7,535,989
- 2017/10/25 코스피매수 한세예스24홀딩스 107 9,300 995,100 50 6,540,839
- 2017/10/25 코스피매수 제이준코스메틱 144 6,930 997,920 50 144 5,542,869
- 2017/10/25 코스피매수 조선선재 14 70,600 988,400 50 14 4,554,419
- 2017/10/25 환매도 QV CMA형 1,034,574 1 1,034,626 5,589,045
- 2017/10/25 환매도 QV CMA형 456,252 1 456,264 569 6,045,309
- 2017/10/26 코스피매도 카프로 117 8,320 973,440 50 2,920 7,015,779
- 2017/10/26 KOSDAQ매도 포스코DX 557 7,020 3,910,140 200 11,726 -557 10,913,993
- 2017/10/26 코스피매수 SK하이닉스 18 82,000 1,476,000 70 18 9,437,923
- 2017/10/26 코스피매수 한국화장품제조 10 31,850 318,500 39 9,119,423
- 2017/10/26 코스피매수 한국화장품제조 10 32,200 322,000 49 8,797,423
- 2017/10/26 코스피매수 한국화장품제조 10 32,400 324,000 59 8,473,423
- 2017/10/26 코스피매수 한국화장품제조 20 32,150 643,000 80 79 7,830,343
- 2017/10/26 KOSDAQ매수 포스코DX 70 7,100 497,000 -487 7,333,343
- 2017/10/26 KOSDAQ매수 포스코DX 100 7,040 704,000 -387 6,629,343
- 2017/10/26 KOSDAQ매수 포스코DX 100 7,090 709,000 -287 5,920,343
- 2017/10/26 KOSDAQ매수 포스코DX 145 6,850 993,250 -142 4,927,093
- 2017/10/26 KOSDAQ매수 포스코DX 142 7,080 1,005,360 200 3,921,533
- 2017/10/26 코스피매수 카카오 1 151,000 151,000 1 3,770,533
- 2017/10/26 코스피매수 카카오 1 151,500 151,500 2 3,619,033
- 2017/10/26 코스피매수 카카오 14 152,000 2,128,000 120 16 1,490,913
- 2017/10/26 이체입금 7,501,360 8,992,273`;
- document.getElementById('dataInput').value = sampleData;
- analyzeData(); // 샘플 데이터 로드 후 바로 분석 실행
- }
- // 페이지 로드시 샘플 데이터 자동 로드 (사용자가 필요시 버튼 클릭하도록 변경)
- document.addEventListener('DOMContentLoaded', function() {
- // 초기 로드 시 샘플 데이터를 자동으로 로드하고 분석합니다.
- loadSampleData();
- });
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment