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" />
- <title>모듈형 매매법 분할매수 계산기</title>
- <style>
- /*
- * 모든 CSS 스타일을 '#calculator-root' ID 내부로 범위화하여
- * 다른 웹사이트의 전역 CSS와 충돌하지 않도록 합니다.
- */
- #calculator-root {
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- max-width: 1000px;
- margin: 20px auto; /* 중앙 정렬 및 상하 여백 추가 */
- padding: 0; /* 루트 컨테이너 자체의 패딩은 제거 */
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- border-radius: 20px; /* 컨테이너 자체에도 둥근 모서리 적용 */
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
- position: relative; /* 커스텀 알림을 위한 기준점 */
- overflow: hidden; /* 자식 요소가 넘치지 않도록 */
- }
- /* 기존 body 스타일 중 계산기에 필요한 부분만 #calculator-root 내부로 이동 */
- #calculator-root .container {
- background: rgba(255, 255, 255, 0.95);
- border-radius: 20px;
- padding: 30px;
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
- backdrop-filter: blur(10px); /* Frosted glass effect */
- width: 100%; /* Ensure container takes full width of max-width */
- box-sizing: border-box; /* Include padding in width calculation */
- }
- #calculator-root h1 {
- text-align: center;
- color: #333;
- margin-bottom: 30px;
- font-size: 2.2em;
- background: linear-gradient(135deg, #667eea, #764ba2);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- }
- /* Styling for the input section */
- #calculator-root .input-section {
- background: #f8f9fa;
- padding: 25px;
- border-radius: 15px;
- margin-bottom: 30px;
- border: 2px solid #e9ecef;
- }
- /* Grid layout for input groups for responsiveness */
- #calculator-root .input-group {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 20px;
- margin-bottom: 20px;
- }
- #calculator-root .input-item {
- display: flex;
- flex-direction: column;
- }
- #calculator-root label {
- font-weight: bold;
- color: #495057;
- margin-bottom: 8px;
- font-size: 14px;
- }
- #calculator-root input, #calculator-root select {
- padding: 12px 15px;
- border: 2px solid #dee2e6;
- border-radius: 10px;
- font-size: 16px;
- transition: all 0.3s ease;
- background: white;
- box-sizing: border-box; /* Include padding in width calculation */
- }
- #calculator-root input:focus, #calculator-root select:focus {
- outline: none;
- border-color: #667eea;
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
- }
- /* Button styling */
- #calculator-root button {
- background: linear-gradient(135deg, #667eea, #764ba2);
- color: white;
- border: none;
- padding: 12px 30px;
- border-radius: 10px;
- font-size: 16px;
- font-weight: bold;
- cursor: pointer;
- transition: all 0.3s ease;
- margin-top: 10px;
- width: 100%; /* Make button full width on smaller screens */
- }
- #calculator-root button:hover {
- transform: translateY(-2px);
- box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
- }
- /* Result table styling */
- #calculator-root .result-table {
- background: white;
- border-radius: 15px;
- overflow: hidden; /* Ensures rounded corners for table */
- box-shadow: 0 10px 30px rgba(0,0,0,0.1);
- margin-top: 20px; /* Add some space above the table */
- }
- #calculator-root table {
- width: 100%;
- border-collapse: collapse;
- }
- #calculator-root th {
- background: linear-gradient(135deg, #667eea, #764ba2);
- color: white;
- padding: 15px;
- text-align: center;
- font-weight: bold;
- font-size: 14px;
- }
- #calculator-root td {
- padding: 12px 15px;
- text-align: center;
- border-bottom: 1px solid #f1f3f4;
- font-size: 14px;
- }
- #calculator-root tr:nth-child(even) {
- background-color: #f8f9fa;
- }
- #calculator-root tr:hover {
- background-color: #e3f2fd;
- transition: all 0.3s ease;
- }
- /* Summary section styling */
- #calculator-root .summary {
- background: linear-gradient(135deg, #667eea, #764ba2);
- color: white;
- padding: 20px;
- border-radius: 15px;
- margin-top: 20px;
- text-align: center;
- }
- #calculator-root .summary h3 {
- margin: 0 0 10px 0;
- font-size: 18px;
- }
- #calculator-root .summary p {
- margin: 5px 0;
- font-size: 14px;
- opacity: 0.9;
- }
- /* Custom Alert Styles - 이제 #calculator-root 내부에서 절대 위치를 사용합니다. */
- #calculator-root .custom-alert {
- position: absolute; /* 부모 컨테이너에 상대적으로 위치 */
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1000;
- }
- #calculator-root .custom-alert-content {
- background: white;
- padding: 30px;
- border-radius: 15px;
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
- text-align: center;
- max-width: 400px;
- width: 90%;
- }
- #calculator-root .custom-alert-content p {
- margin-bottom: 20px;
- font-size: 18px;
- color: #333;
- }
- #calculator-root .custom-alert-content button {
- background: linear-gradient(135deg, #667eea, #764ba2);
- color: white;
- border: none;
- padding: 10px 25px;
- border-radius: 8px;
- font-size: 16px;
- cursor: pointer;
- transition: all 0.3s ease;
- }
- #calculator-root .custom-alert-content button:hover {
- transform: translateY(-1px);
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
- }
- /* Responsive adjustments for smaller screens */
- @media (max-width: 768px) {
- #calculator-root .input-group {
- grid-template-columns: 1fr; /* Stack inputs vertically */
- }
- #calculator-root table {
- font-size: 12px;
- }
- #calculator-root th, #calculator-root td {
- padding: 8px;
- }
- #calculator-root .container {
- padding: 15px; /* Reduce padding on smaller screens */
- }
- #calculator-root h1 {
- font-size: 1.8em; /* Adjust heading size */
- }
- }
- /* Further adjustments for very small screens if needed */
- @media (max-width: 480px) {
- #calculator-root th, #calculator-root td {
- padding: 6px; /* Further reduce padding for very small screens */
- }
- }
- </style>
- </head>
- <body>
- <!-- 모든 계산기 콘텐츠를 고유한 ID를 가진 div로 감쌉니다. -->
- <div id="calculator-root">
- <div class="container">
- <h1>모듈형 매매법 분할매수 계산기</h1>
- <div class="input-section">
- <div class="input-group">
- <div class="input-item">
- <label for="tradeType">매매 방향</label>
- <select id="tradeType" onchange="toggleInputFields()">
- <option value="buy">매수</option>
- <option value="sell">매도</option>
- </select>
- </div>
- <!-- This single input item will dynamically change its label and content -->
- <div class="input-item">
- <label id="dynamicInputLabel" for="dynamicInputValue"></label>
- <input type="number" id="dynamicInputValue" value="" placeholder="">
- </div>
- <div class="input-item">
- <label for="currentPrice">현재 주가 (원)</label>
- <input type="number" id="currentPrice" value="100" placeholder="예: 100">
- </div>
- <div class="input-item">
- <label for="divisions">분할 횟수</label>
- <input type="number" id="divisions" value="10" min="2" max="20" placeholder="예: 10">
- </div>
- <div class="input-item">
- <label for="priceInterval">가격 변동 간격 (%)</label>
- <select id="priceInterval">
- <option value="1">1%</option>
- <option value="2">2%</option>
- <option value="3">3%</option>
- <option value="5">5%</option>
- </select>
- </div>
- </div>
- <button onclick="calculateSplit()">계산하기</button>
- </div>
- <div class="result-table" id="resultSection" style="display: none;">
- <table>
- <thead>
- <tr>
- <th>차수</th>
- <th id="priceHeader">매수 가격 (원)</th>
- <th id="changeHeader">하락률 (%)</th>
- <th id="sharesHeader">매수 주수 (주)</th>
- <th id="amountHeader">매수 금액 (원)</th>
- <th id="cumulativeSharesHeader">누적 매수 주수 (주)</th>
- <th id="cumulativeAmountHeader">누적 매수 금액 (원)</th>
- <th id="avgPriceHeader">평균 매수단가 (원)</th>
- </tr>
- </thead>
- <tbody id="resultBody">
- </tbody>
- </table>
- <div class="summary" id="summary">
- </div>
- </div>
- </div>
- <!-- Custom Alert Modal Structure -->
- <div id="customAlert" class="custom-alert" style="display: none;">
- <div class="custom-alert-content">
- <p id="customAlertMessage"></p>
- <button onclick="hideCustomAlert()">확인</button>
- </div>
- </div>
- </div>
- <script>
- /**
- * Displays a custom alert modal with a given message.
- * @param {string} message - The message to display in the alert.
- */
- function showCustomAlert(message) {
- document.getElementById('customAlertMessage').textContent = message;
- document.getElementById('customAlert').style.display = 'flex';
- }
- /**
- * Hides the custom alert modal.
- */
- function hideCustomAlert() {
- document.getElementById('customAlert').style.display = 'none';
- }
- /**
- * Toggles the label text, value, and placeholder of the dynamic input field
- * based on the selected trade type (buy/sell).
- * Updates the table headers accordingly.
- */
- function toggleInputFields() {
- const tradeType = document.getElementById('tradeType').value;
- const dynamicInputLabel = document.getElementById('dynamicInputLabel');
- const dynamicInputValue = document.getElementById('dynamicInputValue');
- if (tradeType === 'buy') {
- dynamicInputLabel.textContent = '총 투자금액 (원)';
- dynamicInputLabel.setAttribute('for', 'dynamicInputValue'); // Label's 'for' attribute
- dynamicInputValue.value = '1000000'; // Default value for buy
- dynamicInputValue.placeholder = '예: 1,000,000';
- } else {
- dynamicInputLabel.textContent = '보유 주수 (주)';
- dynamicInputLabel.setAttribute('for', 'dynamicInputValue'); // Label's 'for' attribute
- dynamicInputValue.value = '10000'; // Default value for sell
- dynamicInputValue.placeholder = '예: 10,000';
- }
- updateHeaders();
- }
- /**
- * Updates the text content of the table headers based on the current trade type (buy/sell).
- */
- function updateHeaders() {
- const tradeType = document.getElementById('tradeType').value;
- const isBuy = tradeType === 'buy';
- document.getElementById('priceHeader').textContent = isBuy ? '매수 가격 (원)' : '매도 가격 (원)';
- document.getElementById('changeHeader').textContent = isBuy ? '하락률 (%)' : '상승률 (%)';
- document.getElementById('sharesHeader').textContent = isBuy ? '매수 주수 (주)' : '매도 주수 (주)';
- document.getElementById('amountHeader').textContent = isBuy ? '매수 금액 (원)' : '매도 금액 (원)';
- document.getElementById('cumulativeSharesHeader').textContent = isBuy ? '누적 매수 주수 (주)' : '잔여 보유 주수 (주)';
- document.getElementById('cumulativeAmountHeader').textContent = isBuy ? '누적 매수 금액 (원)' : '누적 매도 금액 (원)';
- document.getElementById('avgPriceHeader').textContent = isBuy ? '평균 매수단가 (원)' : '평균 매도단가 (원)';
- }
- /**
- * Calculates the split purchase/sale plan based on user inputs.
- * Generates and displays the results in a table and a summary section.
- */
- function calculateSplit() {
- // Get input values
- const tradeType = document.getElementById('tradeType').value;
- const currentPrice = parseInt(document.getElementById('currentPrice').value);
- const divisions = parseInt(document.getElementById('divisions').value);
- const priceInterval = parseFloat(document.getElementById('priceInterval').value);
- // Input validation for common fields
- if (isNaN(currentPrice) || currentPrice <= 0) {
- showCustomAlert('현재 주가는 0보다 큰 숫자를 입력해주세요.');
- return;
- }
- if (isNaN(divisions) || divisions < 2 || divisions > 20) {
- showCustomAlert('분할 횟수는 2에서 20 사이의 숫자를 입력해주세요.');
- return;
- }
- if (isNaN(priceInterval) || priceInterval <= 0) {
- showCustomAlert('가격 변동 간격은 0보다 큰 숫자를 선택해주세요.');
- return;
- }
- const resultBody = document.getElementById('resultBody');
- resultBody.innerHTML = ''; // Clear previous results
- let cumulativeShares = 0;
- let cumulativeAmount = 0;
- let initialTotalAmountOrShares = 0; // Stores the initial total for summary purposes
- // Get the value from the dynamic input field
- const dynamicInputValue = parseInt(document.getElementById('dynamicInputValue').value);
- if (tradeType === 'buy') {
- const totalAmount = dynamicInputValue;
- // Input validation for buy-specific field
- if (isNaN(totalAmount) || totalAmount <= 0) {
- showCustomAlert('총 투자금액은 0보다 큰 숫자를 입력해주세요.');
- return;
- }
- initialTotalAmountOrShares = totalAmount; // Store initial total amount
- // Calculate the base amount to be allocated per division
- const amountPerDivisionBase = totalAmount / divisions;
- for (let i = 1; i <= divisions; i++) {
- const changeRate = (i - 1) * priceInterval; // Calculate percentage drop
- // Calculate trade price based on current price and drop percentage
- const tradePrice = Math.round(currentPrice * (100 - changeRate) / 100);
- // Ensure trade price does not go to zero or negative
- if (tradePrice <= 0) {
- showCustomAlert(`매수 가격이 ${tradePrice.toLocaleString()}원으로 0 이하가 됩니다. 현재 주가 또는 분할 간격을 조정해주세요.`);
- return;
- }
- // Calculate shares for the current division based on the allocated amount and trade price
- const currentShares = Math.floor(amountPerDivisionBase / tradePrice);
- const tradeAmount = currentShares * tradePrice;
- cumulativeShares += currentShares;
- cumulativeAmount += tradeAmount;
- // Calculate average price, handling division by zero if no shares are bought yet
- const avgPrice = cumulativeShares > 0 ? Math.round(cumulativeAmount / cumulativeShares) : 0;
- // Create and append table row
- const row = document.createElement('tr');
- row.innerHTML = `
- <td>${i}</td>
- <td>${tradePrice.toLocaleString()}</td>
- <td>${changeRate.toFixed(1)}</td>
- <td>${currentShares.toLocaleString()}</td>
- <td>${tradeAmount.toLocaleString()}</td>
- <td>${cumulativeShares.toLocaleString()}</td>
- <td>${cumulativeAmount.toLocaleString()}</td>
- <td>${avgPrice.toLocaleString()}</td>
- `;
- resultBody.appendChild(row);
- }
- // Generate summary for buy mode
- const maxChange = (divisions - 1) * priceInterval;
- const minPrice = Math.round(currentPrice * (100 - maxChange) / 100);
- const finalAvgPrice = cumulativeShares > 0 ? Math.round(cumulativeAmount / cumulativeShares) : 0;
- const remainingFunds = initialTotalAmountOrShares - cumulativeAmount; // Calculate unspent funds
- document.getElementById('summary').innerHTML = `
- <h3>📈 분할매수 요약</h3>
- <p><strong>총 매수 주수:</strong> ${cumulativeShares.toLocaleString()}주</p>
- <p><strong>총 투자 금액:</b> ${cumulativeAmount.toLocaleString()}원</p>
- <p><strong>평균 매수단가:</strong> ${finalAvgPrice.toLocaleString()}원</p>
- <p><strong>최대 하락 허용:</strong> ${maxChange}% (${minPrice.toLocaleString()}원까지)</p>
- <p><strong>잔여 자금:</strong> ${remainingFunds.toLocaleString()}원</p>
- `;
- } else { // Trade type is 'sell'
- const holdingShares = dynamicInputValue;
- // Input validation for sell-specific field
- if (isNaN(holdingShares) || holdingShares <= 0) {
- showCustomAlert('보유 주수는 0보다 큰 숫자를 입력해주세요.');
- return;
- }
- initialTotalAmountOrShares = holdingShares; // Store initial holding shares
- // Calculate base shares per trade and any remaining shares to distribute
- const sharesPerTradeBase = Math.floor(holdingShares / divisions);
- let remainingSharesToDistribute = holdingShares % divisions;
- for (let i = 1; i <= divisions; i++) {
- const changeRate = (i - 1) * priceInterval; // Calculate percentage increase
- // Calculate trade price based on current price and increase percentage
- const tradePrice = Math.round(currentPrice * (100 + changeRate) / 100);
- let currentShares = sharesPerTradeBase;
- // Distribute remaining shares one by one to the first few divisions
- if (remainingSharesToDistribute > 0) {
- currentShares++;
- remainingSharesToDistribute--;
- }
- const tradeAmount = currentShares * tradePrice;
- cumulativeShares += currentShares; // cumulativeShares now represents total shares sold
- cumulativeAmount += tradeAmount;
- // Calculate remaining shares in hand after this trade
- const remainingHoldingShares = initialTotalAmountOrShares - cumulativeShares;
- // Calculate average price, handling division by zero if no shares are sold yet
- const avgPrice = cumulativeShares > 0 ? Math.round(cumulativeAmount / cumulativeShares) : 0;
- // Create and append table row
- const row = document.createElement('tr');
- row.innerHTML = `
- <td>${i}</td>
- <td>${tradePrice.toLocaleString()}</td>
- <td>+${changeRate.toFixed(1)}</td>
- <td>${currentShares.toLocaleString()}</td>
- <td>${tradeAmount.toLocaleString()}</td>
- <td>${remainingHoldingShares.toLocaleString()}</td>
- <td>${cumulativeAmount.toLocaleString()}</td>
- <td>${avgPrice.toLocaleString()}</td>
- `;
- resultBody.appendChild(row);
- }
- // Generate summary for sell mode
- const maxChange = (divisions - 1) * priceInterval;
- const maxPrice = Math.round(currentPrice * (100 + maxChange) / 100);
- const finalAvgPrice = cumulativeShares > 0 ? Math.round(cumulativeAmount / cumulativeShares) : 0;
- const remainingHoldingSharesTotal = initialTotalAmountOrShares - cumulativeShares;
- document.getElementById('summary').innerHTML = `
- <h3>📉 분할매도 요약</h3>
- <p><strong>총 매도 주수:</strong> ${cumulativeShares.toLocaleString()}주</p>
- <p><strong>총 매도 금액:</strong> ${cumulativeAmount.toLocaleString()}원</p>
- <p><strong>평균 매도단가:</strong> ${finalAvgPrice.toLocaleString()}원</p>
- <p><strong>최대 상승 기대:</strong> ${maxChange}% (${maxPrice.toLocaleString()}원까지)</p>
- <p><strong>잔여 보유 주수:</strong> ${remainingHoldingSharesTotal.toLocaleString()}주</p>
- `;
- }
- // Display the result section
- document.getElementById('resultSection').style.display = 'block';
- }
- // Initial setup and calculation when the page loads
- window.onload = function() {
- toggleInputFields(); // Set initial input field visibility and headers
- calculateSplit(); // Perform initial calculation with default values
- };
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment