----------------------------------------------------------------코드소스----------------------------------------------------
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>한국투자증권 자동매매 프로그램</title>
<script src="
https://unpkg.com/@tailwindcss/browser@4"></script>
<link href="
https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
/* 추가 스타일 */
#realtime-stock-table {
border-collapse: collapse;
width: 100%;
margin-top: 20px;
}
#realtime-stock-table th, #realtime-stock-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
#realtime-stock-table th {
background-color: #f0f0f0;
}
.up {
color: red;
}
.down {
color: blue;
}
.even {
color: black;
}
</style>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
'inter': ['Inter', 'sans-serif'],
},
},
},
}
</script>
</head>
<body class="bg-gray-100 p-4">
<div class="container mx-auto p-6 bg-white rounded-lg shadow-md">
<h1 class="text-2xl font-semibold text-center text-gray-800 mb-6">한국투자증권 자동매매 프로그램</h1>
<div id="api-info" class="mb-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h2 class="text-lg font-semibold text-gray-700 mb-4">API 정보 입력</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="mb-2">
<label for="account-number" class="block text-gray-700 text-sm font-bold mb-2">계좌번호:</label>
<input type="text" id="account-number" placeholder="계좌번호를 입력하세요" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
<div class="mb-2">
<label for="access-token" class="block text-gray-700 text-sm font-bold mb-2">Access Token:</label>
<input type="text" id="access-token" placeholder="Access Token을 입력하세요" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
<div class="mb-2">
<label for="app-key" class="block text-gray-700 text-sm font-bold mb-2">App Key:</label>
<input type="text" id="app-key" placeholder="App Key를 입력하세요" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
<div class="mb-2">
<label for="app-secret" class="block text-gray-700 text-sm font-bold mb-2">App Secret:</label>
<input type="text" id="app-secret" placeholder="App Secret을 입력하세요" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
</div>
</div>
<div id="stock-info" class="mb-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h2 class="text-lg font-semibold text-gray-700 mb-4">종목 정보 입력</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="mb-2">
<label for="stock-code" class="block text-gray-700 text-sm font-bold mb-2">종목 코드:</label>
<input type="text" id="stock-code" placeholder="종목 코드를 입력하세요" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
<div class="mb-2">
<label for="quantity" class="block text-gray-700 text-sm font-bold mb-2">매수/매도 수량:</label>
<input type="number" id="quantity" value="1" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
<div class="mb-2">
<label for="price" class="block text-gray-700 text-sm font-bold mb-2">매수/매도 가격 (지정가):</label>
<input type="number" id="price" placeholder="매수/매도 가격을 입력하세요 (선택 사항)" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
</div>
</div>
<div id="transaction-buttons" class="flex justify-center space-x-4 mb-6">
<button id="buy-button" class="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">매수</button>
<button id="sell-button" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">매도</button>
<button id="cancel-button" class="bg-yellow-500 hover:bg-yellow-700 text-gray-800 font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">취소</button>
</div>
<div id="order-results" class="mb-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h2 class="text-lg font-semibold text-gray-700 mb-4">주문 결과</h2>
<div id="result-message" class="text-gray-700"></div>
</div>
<div id="account-info" class="p-4 bg-gray-50 rounded-lg border border-gray-200">
<h2 class="text-lg font-semibold text-gray-700 mb-4">계좌 정보</h2>
<div id="account-balance" class="text-gray-700 mb-2">잔고: </div>
<div id="stock-holdings" class="text-gray-700">보유 주식: </div>
</div>
<div id="new-listings" class="mb-6 p-4 bg-gray-50 rounded-lg border border-gray-200">
<h2 class="text-lg font-semibold text-gray-700 mb-4">신규 상장 기업</h2>
<ul id="new-listings-list" class="list-disc list-inside text-gray-700">
</ul>
</div>
<div id="realtime-stock-info" class="p-4 bg-gray-50 rounded-lg border border-gray-200">
<h2 class="text-lg font-semibold text-gray-700 mb-4">오늘의 주가 및 거래량</h2>
<table id="realtime-stock-table">
<thead>
<tr>
<th>종목명</th>
<th>현재가</th>
<th>변동률</th>
<th>거래량</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="tax-info" class="p-4 bg-gray-50 rounded-lg border border-gray-200">
<h2 class="text-lg font-semibold text-gray-700 mb-4">기업별 세무 신고액</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="mb-2">
<label for="company-code" class="block text-gray-700 text-sm font-bold mb-2">회사 코드:</label>
<input type="text" id="company-code" placeholder="회사 코드를 입력하세요" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
<div class="mb-2">
<label for="year" class="block text-gray-700 text-sm font-bold mb-2">년도:</label>
<input type="number" id="year" value="2023" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline">
</div>
</div>
<button id="show-tax-button" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">세무 신고액 조회</button>
<div id="tax-result" class="mt-4 text-gray-700"></div>
</div>
</div>
<script>
const accountNumberInput = document.getElementById('account-number');
const accessTokenInput = document.getElementById('access-token');
const appKeyInput = document.getElementById('app-key');
const appSecretInput = document.getElementById('app-secret');
const stockCodeInput = document.getElementById('stock-code');
const quantityInput = document.getElementById('quantity');
const priceInput = document.getElementById('price');
const buyButton = document.getElementById('buy-button');
const sellButton = document.getElementById('sell-button');
const cancelButton = document.getElementById('cancel-button');
const resultMessage = document.getElementById('result-message');
const accountBalanceDisplay = document.getElementById('account-balance');
const stockHoldingsDisplay = document.getElementById('stock-holdings');
const newListingsList = document.getElementById('new-listings-list');
const realtimeStockTable = document.getElementById('realtime-stock-table').getElementsByTagName('tbody')[0];
const companyCodeInput = document.getElementById('company-code');
const yearInput = document.getElementById('year');
const showTaxButton = document.getElementById('show-tax-button');
const taxResultDisplay = document.getElementById('tax-result');
// 실제 API 호출 시에는 HTTPS를 사용해야 합니다.
const API_BASE_URL = '
https://openapi.koreainvestment.com:9443'; // 실전 투자 URL
// const API_BASE_URL = '
https://openapivts.koreainvestment.com:29443'; // 모의 투자 URL
let accessToken = '';
// Access Token 발급 함수 (실제 사용 시에는 보안에 유의하여 서버에서 처리해야 합니다.)
async function getAccessToken() {
const appKey = appKeyInput.value;
const appSecret = appSecretInput.value;
const tokenUrl = `${API_BASE_URL}/oauth2/token`;
const requestBody = new URLSearchParams();
requestBody.append('grant_type', 'client_credentials');
requestBody.append('appkey', appKey);
requestBody.append('appsecret', appSecret);
try {
const response = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: requestBody,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
accessToken = data.access_token;
console.log('Access Token 발급 성공:', accessToken);
return accessToken;
} catch (error) {
console.error('Access Token 발급 실패:', error);
resultMessage.textContent = `Access Token 발급 실패: ${error.message}`;
return null;
}
}
// 주식 주문 함수
async function placeOrder(orderType) {
const accountNumber = accountNumberInput.value;
const stockCode = stockCodeInput.value;
const quantity = quantityInput.value;
const price = priceInput.value; // 지정가 주문 가격
const accessToken = await getAccessToken();
if (!accessToken) {
resultMessage.textContent = "Access Token이 없습니다. 발급 후 다시 시도해주세요.";
return;
}
const orderUrl = `${API_BASE_URL}/uapi/domestic-stock/v1/trading/order`;
const header = {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Bearer ${accessToken}`,
'appkey': appKeyInput.value,
'appsecret': appSecretInput.value,
'tr_id': orderType === 'buy' ? (price ? 'TTTC0801U' : 'TTTC0802U') : (price ? 'TTTC0803U' : 'TTTC0804U'), // 매수: TTTC0801U(지정가), TTTC0802U(시장가), 매도: TTTC0803U(지정가), TTTC0804U(시장가)
'custtype': 'P',
};
const body = {
'CANO': accountNumber.substring(0, 8), // 계좌번호 앞 8자리
'ACNT_PRDT_CD': accountNumber.substring(8), // 계좌번호 뒤 2자리
'PDNO': stockCode,
'ORD_QTY': quantity,
'ORD_TYPE': price ? '01' : '00', // 00: 시장가, 01: 지정가
'ORD_PRICE': price || '', // 지정가인 경우에만 가격 설정
};
try {
const response = await fetch(orderUrl, {
method: 'POST',
headers: header,
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.json(); // 에러 응답을 JSON 형태로 파싱
let errorMessage = `주문 실패: ${response.status}`;
if (errorData && errorData.rt_msg) { // 에러 메시지가 있다면 추가
errorMessage += ` - ${errorData.rt_msg}`;
}
throw new Error(errorMessage);
}
const data = await response.json();
console.log('주문 성공:', data);
resultMessage.textContent = `주문 성공: ${data.msg1 || '성공적으로 처리되었습니다.'}`;
} catch (error) {
console.error('주문 실패:', error);
resultMessage.textContent = `주문 실패: ${error.message}`;
}
}
// 주문 취소 함수
async function cancelOrder() {
const accountNumber = accountNumberInput.value;
const stockCode = stockCodeInput.value;
const accessToken = await getAccessToken();
if (!accessToken) {
resultMessage.textContent = "Access Token이 없습니다. 발급 후 다시 시도해주세요.";
return;
}
const cancelUrl = `${API_BASE_URL}/uapi/domestic-stock/v1/trading/ordercancel`;
const header = {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Bearer ${accessToken}`,
'appkey': appKeyInput.value,
'appsecret': appSecretInput.value,
'tr_id': 'TTTC0805U', // 주문 취소 TR_ID
'custtype': 'P',
};
// 주문 취소 시 필요한 정보. 원 주문 번호(org_no)를 알아야 함.
// 여기서는 예시로 임의의 주문 번호를 사용. 실제로는 주문 체결 내역 조회 API 등을 통해 얻어야 함.
const body = {
'CANO': accountNumber.substring(0, 8),
'ACNT_PRDT_CD': accountNumber.substring(8),
'KRX_FWDG_ORD_NO': '1234567890', // !!! 실제 주문 번호로 변경 필요 !!!
'PDNO': stockCode,
'ORD_QTY': quantityInput.value,
};
try {
const response = await fetch(cancelUrl, {
method: 'POST',
headers: header,
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.json();
let errorMessage = `주문 취소 실패: ${response.status}`;
if (errorData && errorData.rt_msg) {
errorMessage += ` - ${errorData.rt_msg}`;
}
throw new Error(errorMessage);
}
const data = await response.json();
console.log('주문 취소 성공:', data);
resultMessage.textContent = `주문 취소 성공: ${data.msg1 || '성공적으로 처리되었습니다.'}`;
} catch (error) {
console.error('주문 취소 실패:', error);
resultMessage.textContent = `주문 취소 실패: ${error.message}`;
}
}
// 계좌 잔고 조회 함수
async function getAccountBalance() {
const accountNumber = accountNumberInput.value;
const accessToken = await getAccessToken();
if (!accessToken) {
resultMessage.textContent = "Access Token이 없습니다. 발급 후 다시 시도해주세요.";
return;
}
const balanceUrl = `${API_BASE_URL}/uapi/domestic-stock/v1/account/balance`;
const header = {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Bearer ${accessToken}`,
'appkey': appKeyInput.value,
'appsecret': appSecretInput.value,
'tr_id': 'TTTC8434R', // 잔고 조회 TR_ID
'custtype': 'P',
};
const body = {
'CANO': accountNumber.substring(0, 8),
'ACNT_PRDT_CD': accountNumber.substring(8),
'AFHR_FLUI_ID': '0', // 0: 출금가능금액, 1: 총평가금액
'OFL_YN': 'N',
};
try {
const response = await fetch(balanceUrl, {
method: 'GET',
headers: header,
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.json();
let errorMessage = `잔고 조회 실패: ${response.status}`;
if (errorData && errorData.rt_msg) {
errorMessage += ` - ${errorData.rt_msg}`;
}
throw new Error(errorMessage);
}
const data = await response.json();
console.log('계좌 잔고 조회 성공:', data);
// 잔고 정보 표시
if (data.output && data.output.length > 0) {
accountBalanceDisplay.textContent = `잔고: ${parseInt(data.output[0].hldg_sbl_amt).toLocaleString()} 원`;
} else {
accountBalanceDisplay.textContent = '잔고 정보 없음';
}
} catch (error) {
console.error('잔고 조회 실패:', error);
resultMessage.textContent = `잔고 조회 실패: ${error.message}`;
}
}
// 계좌 보유 주식 조회 함수
async function getStockHoldings() {
const accountNumber = accountNumberInput.value;
const accessToken = await getAccessToken();
if (!accessToken) {
resultMessage.textContent = "Access Token이 없습니다. 발급 후 다시 시도해주세요.";
return;
}
const holdingsUrl = `${API_BASE_URL}/uapi/domestic-stock/v1/account/stock`;
const header = {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Bearer ${accessToken}`,
'appkey': appKeyInput.value,
'appsecret': appSecretInput.value,
'tr_id': 'TTTC8436R', // 보유 주식 조회 TR_ID
'custtype': 'P',
};
const body = {
'CANO': accountNumber.substring(0, 8),
'ACNT_PRDT_CD': accountNumber.substring(8),
'AFHR_FLUI_ID': '0',
'OFL_YN': 'N',
'PRCS_DVSN': '01', // 01: 주식 잔고, 02: 펀드 잔고
};
try {
const response = await fetch(holdingsUrl, {
method: 'GET',
headers: header,
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.json();
let errorMessage = `보유 주식 조회 실패: ${response.status}`;
if (errorData && errorData.rt_msg) {
errorMessage += ` - ${errorData.rt_msg}`;
}
throw new Error(errorMessage);
}
const data = await response.json();
console.log('계좌 보유 주식 조회 성공:', data);
// 보유 주식 정보 표시
if (data.output && data.output.length > 0) {
let holdingsText = '보유 주식: ';
data.output.forEach(stock => {
holdingsText += `${stock.pdnm}(${stock.hldg_qty}주), `;
});
holdingsText = holdingsText.slice(0, -2); // 마지막 ', ' 제거
stockHoldingsDisplay.textContent = holdingsText;
} else {
stockHoldingsDisplay.textContent = '보유 주식 정보 없음';
}
} catch (error) {
console.error('보유 주식 조회 실패:', error);
resultMessage.textContent = `보유 주식 조회 실패: ${error.message}`;
}
}
// 신규 상장 기업 조회 함수 (실제 API 연동 필요)
async function getNewListings() {
// 실제로는 한국투자증권 API 또는 다른 데이터 소스를 통해 신규 상장 기업 정보를 가져와야 합니다.
// 여기서는 임시로 더미 데이터를 사용합니다.
const newListings = [
{ name: 'A기업', date: '2024-07-24' },
{ name: 'B기업', date: '2024-07-25' },
{ name: 'C기업', date: '2024-07-26' },
];
// Get new listings from OpenDart
try {
const response = await fetch('
https://opendart.fss.or.kr/api/existed.json?crp_cd=00126380&bsns_year=2023&api_key=YOUR_API_KEY'); if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log("OpenDart API 호출 결과", data);
//신규상장기업 데이터 처리 로직
if (data && Array.isArray(data.list)) {
newListingsList.innerHTML = '';
data.forEach(item => {
const li = document.createElement('li');
li.textContent = `${item.crp_nm} (${item.est_dt})`;
newListingsList.appendChild(li);
});
}
} catch (error) {
console.error("신규상장기업 조회 실패", error);
resultMessage.textContent = `신규 상장 기업 조회 실패: ${error.message}`;
}
// 신규 상장 기업 목록 표시
newListings.forEach(item => {
const li = document.createElement('li');
li.textContent = `${item.name} (${item.date})`;
newListingsList.appendChild(li);
});
}
// 실시간 주가 조회 함수 (Web Socket API필요)
async function getRealtimeStockPrice() {
// 실제로는 한국투자증권 Web Socket API를 사용하여 실시간 주가를 받아와야 합니다.
// 여기서는 임시로 더미 데이터를 생성하여 표시합니다.
const dummyStocks = [
{ name: '삼성전자', currentPrice: 70000, changeRate: 0.5, volume: 1000000 },
{ name: 'LG전자', currentPrice: 150000, changeRate: -1.2, volume: 500000 },
{ name: 'SK하이닉스', currentPrice: 120000, changeRate: 0, volume: 800000 },
];
// Clear the table
realtimeStockTable.innerHTML = '';
dummyStocks.forEach(stock => {
const row = realtimeStockTable.insertRow();
const nameCell = row.insertCell();
const priceCell = row.insertCell();
const changeRateCell = row.insertCell();
const volumeCell = row.insertCell();
nameCell.textContent = stock.name;
priceCell.textContent = stock.currentPrice.toLocaleString();
changeRateCell.textContent = `${stock.changeRate.toFixed(2)}%`;
changeRateCell.className = stock.changeRate > 0 ? 'up' : stock.changeRate < 0 ? 'down' : 'even';
volumeCell.textContent = stock.volume.toLocaleString();
});
}
// 기업별 세무 신고액 조회 함수 (Open Dart API 활용)
async function getCompanyTaxReport() {
const companyCode = companyCodeInput.value;
const year = yearInput.value;
const apiKey = 'YOUR_API_KEY'; // 여기에 발급받은 API 키를 입력하세요.
if (!companyCode) {
taxResultDisplay.textContent = "회사 코드를 입력해주세요.";
return;
}
const apiUrl = `
https://opendart.fss.or.kr/api/taxReport.json?crp_cd=${companyCode}&bsns_year=${year}&api_key=${apiKey}`;
try {
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('세무 신고액 조회 결과:', data);
// 결과 표시
if (data && data.list && data.list.length > 0) {
let resultText = `
<p>회사 코드: ${data.list[0].crp_cd}</p>
<p>회사 이름: ${data.list[0].crp_nm}</p>
<p>사업 연도: ${data.list[0].bsns_year}</p>
<p>법인세액 (천원): ${data.list[0].corp_tax_amt}</p>
<p>총 납부액 (천원): ${data.list[0].ttl_pay_amt}</p>
`;
taxResultDisplay.innerHTML = resultText;
} else {
taxResultDisplay.textContent = "해당하는 세무 신고 내역이 없습니다.";
}
} catch (error) {
console.error('세무 신고액 조회 실패:', error);
taxResultDisplay.textContent = `세무 신고액 조회 실패: ${error.message}`;
}
}
// 이벤트 리스너 등록
buyButton.addEventListener('click', () => placeOrder('buy'));
sellButton.addEventListener('click', () => placeOrder('sell'));
cancelButton.addEventListener('click', cancelOrder);
showTaxButton.addEventListener('click', getCompanyTaxReport);
// 페이지 로드 시 계좌 정보, 잔고, 신규 상장 기업 정보 및 실시간 주가 조회
window.onload = async () => {
if (accountNumberInput.value) {
await getAccountBalance();
await getStockHoldings();
}
await getNewListings();
getRealtimeStockPrice(); // 최초 1회 호출
// 5초마다 실시간 주가 갱신 (실제로는 Web Socket을 사용해야 함)
setInterval(getRealtimeStockPrice, 5000);
};
</script>
</body>
</html>