
사는 것보다 파는 것이 더 중요하다
안녕하세요. 감정을 배제하고 완벽한 로직으로 계좌를 재설계하는 [계좌 리팩토링] 시리즈 7편입니다. 지난 6편에서 우리는 봇에게 주식을 매수하는 '손'을 달아주었습니다. 주문이 체결되는 것을 보며 짜릿함을 느끼셨을 텐데요.
하지만 주식 시장에서 오랜 격언이 있죠. "매수는 기술이고, 매도는 예술이다." 아무리 좋은 타점에 주식을 샀더라도, 적절한 시점에 팔지 못하면 그 수익은 결코 내 계좌의 것이 되지 않습니다. 오히려 '조금만 더 오르겠지' 하는 탐욕이나 '다시 오를 거야'라는 미련 때문에 익절과 손절 타이밍을 놓치고 계좌가 망가지는 경우가 허다합니다.
이번 7편에서는 봇이 냉철하게 수익률을 계산하고, 목표한 조건(익절/손절 라인)에 도달했을 때 미련 없이 주식을 던지는 '매도 주문(Sell Order)' 로직을 구현해 보겠습니다.
매도 API의 핵심: 매수와 데칼코마니
다행히도 한국투자증권 API에서 매도 주문을 구현하는 것은 매수 주문과 99% 동일합니다. 통신 방식(POST)과 파라미터 구조가 같으며, 딱 한 가지 TR 코드(tr_id)만 변경해 주면 됩니다.
- 매수 TR 코드 (모의투자):
VTTC0802U - 매도 TR 코드 (모의투자):
VTTC0801U
(참고: 실전 투자의 매도 TR 코드는 TTTC0801U입니다. 항상 모의투자로 먼저 검증해야 함을 잊지 마세요!)
언제 팔 것인가? 수익률 계산 로직
무작정 파는 것이 아니라, 우리가 정한 '조건'에 맞을 때 팔아야 합니다. 이를 위해 4편에서 구현했던 잔고 조회(VTTC8434R) 데이터를 활용하여 현재 보유 중인 종목의 평균 매입가와 현재가를 비교해야 합니다.
수익률 계산 공식은 간단합니다: ((현재가 - 매입단가) / 매입단가) * 100
하지만 KIS 잔고 조회 API는 이미 계산된 수익률(evlu_pfls_rt)을 제공하므로, 파싱 과정에서 이 값을 그대로 가져와 조건문(if)에 활용하는 것이 훨씬 효율적입니다.
Node.js 매도 주문 및 익절/손절 코드 구현하기
새로운 파일 sell_order.js를 만들고 아래 코드를 작성해 봅시다. (매수 코드와 매우 유사합니다.)
// (프로젝트 루트의 sell_order.js)
require('dotenv').config();
const axios = require('axios');
const { getAccessToken } = require('./get_token'); // 토큰 발급 함수
const BASE_URL = process.env.KIS_API_URL || 'https://openapivts.koreainvestment.com:29443';
/**
* 주식 매도 주문 함수
* @param {string} stockCode - 종목코드 (예: '005930')
* @param {number} orderQty - 매도 수량
* @param {number} orderPrice - 주문 단가 (시장가일 경우 무시됨)
* @param {string} orderType - 주문 구분 (00: 지정가, 01: 시장가)
*/
const sellStock = async (stockCode, orderQty, orderPrice, orderType = '00') => {
try {
const token = await getAccessToken();
const headers = {
'Content-Type': 'application/json',
'authorization': `Bearer ${token}`,
'appkey': process.env.KIS_APP_KEY,
'appsecret': process.env.KIS_APP_SECRET,
'tr_id': 'VTTC0801U', // [모의투자] 주식 현금 매도 TR 코드
'custtype': 'P',
};
const data = {
CANO: process.env.KIS_ACCOUNT_NUMBER,
ACNT_PRDT_CD: '01',
PDNO: stockCode,
ORD_DVSN: orderType,
ORD_QTY: orderQty.toString(),
ORD_UNPR: orderType === '01' ? '0' : orderPrice.toString(),
};
console.log(`\n[매도 전송] ${stockCode} ${orderQty}주 매도 시도 중... (타입: ${orderType})`);
const response = await axios.post(`${BASE_URL}/uapi/domestic-stock/v1/trading/order-cash`, data, { headers });
const result = response.data;
if (result.rt_cd === '0') {
console.log(`✅ [매도 성공] 처리 메시지: ${result.msg1}`);
console.log(`✅ [주문 번호]: ${result.output.ODNO}`);
} else {
console.error(`❌ [매도 실패] 에러 코드: ${result.rt_cd}`);
console.error(`❌ [실패 사유]: ${result.msg1}`);
}
} catch (error) {
console.error('❌ [서버 통신 에러 발생]');
console.error(error.response ? error.response.data : error.message);
}
};
/**
* 가상의 트레이딩 봇 로직 예시 (수익률 감지 및 매도 실행)
* @param {number} currentProfitRate - 현재 수익률 (잔고 조회 API에서 파싱한 값)
*/
const checkAndSell = (currentProfitRate) => {
const TAKE_PROFIT_RATE = 3.0; // 익절 라인 (+3%)
const STOP_LOSS_RATE = -2.0; // 손절 라인 (-2%)
console.log(`[모니터링] 현재 수익률: ${currentProfitRate}%`);
if (currentProfitRate >= TAKE_PROFIT_RATE) {
console.log(`🎯 [익절 조건 도달] 수익률 ${currentProfitRate}%. 시장가 매도를 실행합니다.`);
sellStock('005930', 1, 0, '01'); // 삼성전자 1주 시장가 익절
} else if (currentProfitRate <= STOP_LOSS_RATE) {
console.log(`⚠️ [손절 조건 도달] 수익률 ${currentProfitRate}%. 시장가 매도를 실행합니다.`);
sellStock('005930', 1, 0, '01'); // 삼성전자 1주 시장가 손절
} else {
console.log(`⏳ [홀딩] 아직 매도 조건에 도달하지 않았습니다.`);
}
};
// 실습 실행: 가상의 수익률을 넣어 봇 로직 테스트
checkAndSell(3.5); // 익절 조건 테스트
// checkAndSell(-2.5); // 손절 조건 테스트
// checkAndSell(1.0); // 홀딩 테스트
코드 리팩토링 포인트: 감정의 완전한 배제
위의 checkAndSell 함수가 바로 계좌 리팩토링의 정수입니다. 인간이라면 +3% 수익 구간에서 "조금만 더 오르면 5% 채우고 팔아야지" 하다가 미끄러져 손실을 보거나, -2% 손실 구간에서 "반등할 거야"라며 버티다 계좌가 녹아내립니다.
하지만 우리의 봇 코드는 TAKE_PROFIT_RATE와 STOP_LOSS_RATE 상수에 도달하는 순간, 일말의 자비도 없이 시장에 주식을 던져버립니다. 이것이 우리가 봇을 만드는 진짜 이유입니다.
마무리 및 다음 편 예고
축하합니다! 이제 여러분은 주식을 자동으로 사고파는 전체 사이클(매수 -> 상태 모니터링 -> 수익률 계산 -> 매도)을 모두 완성했습니다.
하지만 아직 봇은 우리가 직접 스크립트를 실행해 주어야만 움직입니다. 진정한 자동매매 봇이라면 내가 자고 있을 때나 직장에서 일하고 있을 때도 24시간 알아서 돌아가야겠죠?
다음 8편에서는 우리의 봇 코드를 스케줄러(Cron) 및 서버 배포 도구(PM2)를 활용하여 365일 무중단으로 실행되도록 세팅하는 '자동화의 완성' 단계에 대해 알아보겠습니다.
⚠️ 주의사항 (면책 조항)
자동매매 주문 로직 테스트는 반드시 증권사의 '모의투자' 환경에서 소액으로 수차례 검증을 거쳐야 합니다. 코드의 오타나 논리적 오류로 인한 폭포수 주문(무한루프) 등 금전적 손실이 발생할 수 있으며, 이에 대한 모든 책임은 개발자 본인에게 있습니다.
'🛠️ Dev & Tools (투자 도구 개발)' 카테고리의 다른 글
| [계좌 리팩토링] 9편(외전). 봇의 보고서를 내 폰으로! 텔레그램(Telegram) 실시간 알림 연동 (0) | 2026.05.23 |
|---|---|
| [계좌 리팩토링] 8편. 24시간 잠들지 않는 봇! PM2와 node-cron으로 무중단 자동화 완성하기 (0) | 2026.05.22 |
| [계좌 리팩토링] 6편. 드디어 실전! Node.js로 주식 매수 주문 자동화하기 (0) | 2026.05.19 |
| [계좌 리팩토링] 5편. 뚝딱뚝딱 호가창 연동! Node.js 실시간 시세 조회 (WebSocket) (0) | 2026.05.18 |
| [계좌 리팩토링] 4편. 내 계좌 정보, 실시간으로 완벽하게 파싱하기 (REST API) (0) | 2026.05.15 |