IT개발

키움증권 Open API를 활용하여 갭 매매 전략 구현- C#

HyochulLab 2025. 3. 10. 17:18

 

1. 갭 매매 전략이란?

갭 매매란 주가가 전일 종가 대비 큰 폭으로 상승(갭업)하거나 하락(갭다운)하여 개장할 때, 발생한 갭의 움직임을 활용하여 수익을 내는 전략입니다.

이번에 소개할 전략은 다음 조건에 해당하는 종목을 대상으로 합니다.

  • 조건검색: 전일 대비 오늘 시가가 5% 이상 갭업으로 시작한 종목
  • 전략 실행: 개장 후 30분 이내에 주가가 전일 종가 수준까지 회귀하면 매수 진입 후, 갭이 완전히 메워질 때 매도합니다.
  • 손절매: 진입 가격에서 갭 상승분의 50% 하락 시 손절
  • 익절매: 갭 상승분의 50% 이상 추가 상승 시 익절합니다.

2. 구현 환경 및 준비사항

  • 키움증권 API+ 설치 (영웅문 API)
  • Visual Studio Community Edition (C#)
  • 키움증권 계좌 (모의투자 계좌 추천)

3. C# 코드 전체 구현 (주석 포함)

다음 코드에는 갭 매매 전략, 주문 실행, 체결 확인 및 정정 주문까지 포함되어 있습니다.

using System;
using System.Collections.Generic;
using AxKHOpenAPILib;

public class GapTrading
{
    private AxKHOpenAPI axKHOpenAPI;
    private string accountNumber;

    // 매수 후 체결된 정보를 저장할 Dictionary (종목코드, (체결가격, 체결수량))
    private Dictionary<string, (int price, int qty)> positions = new Dictionary<string, (int, int)>();

    public GapTradingStrategy(AxKHOpenAPI api, string accountNo)
    {
        axKHOpenAPI = api;
        accountNumber = accountNo;
        
        axKHOpenAPI.OnReceiveChejanData += OnReceiveChejanData;
        axKHOpenAPI.OnReceiveRealData += axKHOpenAPI_OnReceiveRealData;
    }

    // 1. 갭 상승 확인 후 매매 실행
    public void ExecuteGapTrade(string stockCode)
    {
        int yesterdayClose = int.Parse(axKHOpenAPI.GetMasterLastPrice(stockCode));
        int todayOpen = int.Parse(axKHOpenAPI.GetCommRealData(stockCode, 16)); // 시가 실시간 코드: 16

        double gapPercent = (todayOpen - yesterdayClose) / (double)yesterdayClose * 100.0;

        // 5% 이상 갭업 체크
        if (gapPercent >= 5)
        {
            Console.WriteLine($"[갭업] {stockCode}: {gapPercent}% 상승");
            
            // 개장 30분 내에 전일 종가로 회귀하는 조건 체크
            // (이 부분은 실시간 데이터 이벤트를 통해 지속적으로 확인 필요)
        }
    }

    // 실시간 주가 이벤트 핸들러
    private void axKHOpenAPI_OnReceiveRealData(object sender, _DKHOpenAPIEvents_OnReceiveRealDataEvent e)
    {
        string stockCode = e.sRealKey;
        int currentPrice = Math.Abs(int.Parse(axKHOpenAPI.GetCommRealData(stockCode, 10))); // 현재가 코드: 10

        if (!positions.ContainsKey(stockCode))
        {
            // 매수 조건 확인 (전일 종가 이하로 회귀)
            int yesterdayClose = int.Parse(axKHOpenAPI.GetMasterLastPrice(stockCode));
            if (currentPrice <= yesterdayClose)
            {
                int qty = 10; // 매수 수량
                SendOrder(stockCode, 1, qty, currentPrice, "03"); // 시장가 매수
            }
        }
        else
        {
            // 이미 보유 중이면 손절/익절 체크
            ManagePosition(stockCode, currentPrice);
        }
    }

    // 체결 데이터 이벤트 처리
    private void axKHOpenAPI_OnReceiveChejanData(object sender, AxKHOpenAPILib._DKHOpenAPIEvents_OnReceiveChejanDataEvent e)
    {
        if (e.sGubun == "0") // 체결
        {
            string stockCode = axKHOpenAPI.GetChejanData(9001).Trim();
            int executedPrice = int.Parse(axKHOpenAPI.GetChejanData(910));
            int executedQty = int.Parse(axKHOpenAPI.GetChejanData(911));

            // 매수 체결인 경우, 포지션 기록
            if (!positions.ContainsKey(stockCode))
            {
                positions.Add(stockCode, (executedPrice, executedQty));
                Console.WriteLine($"매수 체결 완료: {stockCode}, 가격: {executedPrice}, 수량: {executedQty}");
            }
        }
    }

    // 포지션 관리 (익절/손절 처리)
    private void ManagePosition(string stockCode, int currentPrice)
    {
        if (positions.TryGetValue(stockCode, out (int entryPrice, int qty) position))
        {
            int yesterdayClose = int.Parse(axKHOpenAPI.GetMasterLastPrice(stockCode));
            int gapAmount = position.price - yesterdayClose;

            int stopLossPrice = position.price - (gapAmount / 2);  // 손절: 갭의 50% 하락 시
            int takeProfitPrice = position.price + (gapAmount / 2); // 익절: 갭의 50% 추가 상승 시

            if (currentPrice <= stopLossPrice)
            {
                // 손절매 실행
                SendOrder(stockCode, 2, position.qty, currentPrice, "03"); // 시장가 매도
                positions.Remove(stockCode);
                Console.WriteLine($"손절매 실행: {stockCode}, 가격: {currentPrice}");
            }
            else if (currentPrice >= takeProfitPrice)
            {
                // 익절매 실행
                SendOrder(stockCode, 2, position.qty, currentPrice, "03", "");
                Console.WriteLine($"익절매 실행: {stockCode}, 가격: {currentPrice}");
                positions.Remove(stockCode);
            }
        }
    }

    // 주문 전송 메서드
    private void SendOrder(string stockCode, int orderType, int qty, int price, string hogaGb, string orderNo = "")
    {
        int result = axKHOpenAPI.SendOrder("AutoTrade", "0101", accountNumber, orderType, stockCode, qty, price, hogaGb, orderNo);
        if (result == 0)
        {
            Console.WriteLine("주문 전송 성공");
        }
        else
        {
            Console.WriteLine("주문 전송 실패");
        }
    }

    // 주문 정정 로직 추가
    public void CorrectOrder(string stockCode, int newQty, int newPrice)
    {
        string orderNumber = axKHOpenAPI.GetChejanData(9203); // 기존 주문번호
        int cancelResult = axKHOpenAPI.SendOrder("OrderCancel", "0101", accountNumber, 3, stockCode, 0, 0, "00", orderNumber);
        if (cancelResult == 0)
        {
            Console.WriteLine("주문 취소 성공, 새로운 주문 재전송");
            SendOrder(stockCode, 2, newQty, newPrice, "00", "");
        }
    }

    private void SendOrder(string stockCode, int orderType, int qty, int price, string hogaGb, string orderNo)
    {
        axKHOpenAPI.SendOrder("AutoTrade", "0101", accountNumber, orderType, stockCode, qty, price, hogaGb, orderNo);
    }
}

4. 주의사항 및 팁

  • 실제 거래 전에 반드시 키움증권의 모의투자 계좌를 활용해 충분한 테스트를 진행하세요.
  • API 호출 제한, 거래시간 제한 등을 고려하여 구현해야 합니다.
  • 손절매와 익절매는 자동매매의 핵심으로, 로직을 철저히 검증하고 실행하세요.

이 글을 참고하여 초단타 매매 전략을 실전에 성공적으로 적용하시길 바랍니다!