상세 컨텐츠

본문 제목

제목: 키움 OpenAPI 실시간 데이터 처리 — OnReceiveRealData, FID, DisconnectRealData C# 예제

주식 자동매매 시스템/Kiwoom OpenAPI

by 별을 보는 사람 2026. 6. 9. 07:00

본문

반응형

안녕하세요! 조회 요청과 데이터 수신까지 다뤘으니, 이제 마지막 퍼즐인 실시간 시세 데이터를 정리할 차례입니다. 조회는 내가 요청해야 응답이 오지만, 실시간은 한번 등록하면 시세가 변할 때마다 자동으로 데이터가 흘러들어오는 구조예요. 체감이 완전히 다릅니다!

OnReceiveRealData 이벤트 — 실시간의 입구

실시간 시세가 수신될 때마다 종목 단위로 이 이벤트가 발생합니다. 조회(CommRqData)로 종목을 요청하면 자동으로 실시간 등록이 되고, 이후 해당 종목의 체결, 호가 등의 변동이 생길 때마다 이벤트가 호출돼요.

파라미터
sCode → 종목코드 (조회 시 사용한 코드 그대로, _NX/_AL 포함)
sRealType → 실시간 타입 ("주식체결", "주식호가잔량" 등)
sRealData → 실시간 데이터 전문 (직접 사용 불가)
// 의사 코드
void OnReceiveRealData(string sCode, string sRealType, string sRealData)
{
    if (sRealType == "주식체결")
        // → GetCommRealData()로 현재가, 거래량 등 추출

    if (sRealType == "주식호가잔량")
        // → GetCommRealData()로 호가/잔량 데이터 추출

    // sRealData는 직접 파싱하지 말고, 반드시 GetCommRealData() 사용!
}
주의! 세 번째 파라미터 sRealData는 원시 전문 데이터로, 직접 사용할 수 없습니다. 반드시 GetCommRealData() 함수로 데이터를 꺼내세요.

GetCommRealData() — FID로 실시간 값 꺼내기

실시간 데이터에서 원하는 값을 추출하는 함수예요. 조회의 GetCommData()가 항목 이름(문자열)을 쓰는 것과 달리, 실시간은 FID(Field ID, 숫자)를 사용합니다.

파라미터
strCode → 종목코드
nFid → 실시간 타입에 포함된 FID 번호
주식체결 주요 FID
10 → 현재가
11 → 전일대비
12 → 등락율
13 → 누적거래량
15 → 거래량
20 → 체결시간
228 → 체결강도

※ 전체 FID 목록은 KOA Studio "실시간목록" 탭에서 확인할 수 있습니다.
// 의사 코드 — 주식체결 실시간 데이터

if (sRealType == "주식체결")
{
    현재가     = GetCommRealData(sCode, 10);
    전일대비   = GetCommRealData(sCode, 11);
    등락율     = GetCommRealData(sCode, 12);
    누적거래량 = GetCommRealData(sCode, 13);
    체결시간   = GetCommRealData(sCode, 20);
    체결강도   = GetCommRealData(sCode, 228);
}

여기서 한 가지 팁! 조회의 GetCommData()는 "현재가" 같은 문자열을 쓰지만, 실시간의 GetCommRealData()는 10 같은 숫자(FID)를 씁니다. 헷갈리기 쉬우니 꼭 구분해두세요.

DisconnectRealData() — 실시간 등록 해제

실시간 수신을 더 이상 원하지 않을 때, 화면번호 단위로 실시간 등록을 해제하는 함수입니다. 해당 화면번호에 등록된 모든 종목의 실시간 수신이 중단돼요.

파라미터
sScnNo → 실시간 해제할 화면번호 (조회 시 사용했던 4자리)
// 의사 코드

// 화면번호 "1001"로 조회한 종목들의 실시간 해제
DisconnectRealData("1001");

// 주의: 같은 종목이 다른 화면번호로도 등록되어 있으면
// 그쪽 실시간은 계속 수신됩니다.
// 예) "1001"로 005930 조회, "1002"로도 005930 조회한 경우
//     DisconnectRealData("1001") 해도 "1002" 쪽은 유지
실시간 등록은 화면번호 단위입니다. 같은 종목이 여러 화면번호로 등록된 경우, 해당 화면번호의 등록만 해제되고 다른 화면번호의 실시간은 계속 수신돼요.

조회 vs 실시간, 뭐가 다른 거야?

지금까지 다룬 조회와 실시간의 차이를 한눈에 정리하면 이렇습니다.

조회 데이터
요청 함수: CommRqData()
수신 이벤트: OnReceiveTrData
데이터 추출: GetCommData("항목이름")
특징: 내가 요청 → 1회 응답

실시간 데이터
등록: 조회 시 자동 등록
수신 이벤트: OnReceiveRealData
데이터 추출: GetCommRealData(FID숫자)
해제: DisconnectRealData(화면번호)
특징: 한번 등록 → 시세 변동마다 자동 수신

전체 WinForms 예제 — 실시간 체결 수신 + 해제

종목을 조회하면 자동으로 실시간 등록이 되고, 체결이 발생할 때마다 로그에 찍히는 예제입니다. 실시간 해제 버튼도 넣었어요. 폼에는 axKHOpenAPI1 컨트롤만 올려둔 상태입니다.

using AxKHOpenAPILib;
using System;
using System.Drawing;
using System.Windows.Forms;

namespace KiwoomRealTimeDemo
{
    public partial class MainForm : Form
    {
        private Button btnLogin, btnRequest, btnDisconnect;
        private TextBox txtCode, txtLog;
        private Label lblStatus, lblRealCount;
        private string currentScrNo = "1001";
        private int realTickCount = 0;

        public MainForm()
        {
            InitializeComponent();
            CreateControls();

            axKHOpenAPI1.OnEventConnect += OnEventConnect;
            axKHOpenAPI1.OnReceiveTrData += OnReceiveTrData;
            axKHOpenAPI1.OnReceiveRealData += OnReceiveRealData;
        }

        private void CreateControls()
        {
            btnLogin = new Button { Text = "로그인", Location = new Point(12, 12), Size = new Size(80, 30) };
            btnLogin.Click += (s, e) => axKHOpenAPI1.CommConnect();

            lblStatus = new Label { Text = "대기중...", Location = new Point(100, 18), AutoSize = true };

            txtCode = new TextBox { Text = "005930", Location = new Point(12, 55), Size = new Size(100, 22) };

            btnRequest = new Button { Text = "조회+실시간", Location = new Point(120, 53), Size = new Size(100, 26) };
            btnRequest.Click += (s, e) => RequestWithRealTime();

            btnDisconnect = new Button { Text = "실시간 해제", Location = new Point(230, 53), Size = new Size(100, 26) };
            btnDisconnect.Click += (s, e) =>
            {
                axKHOpenAPI1.DisconnectRealData(currentScrNo);
                AppendLog($"[해제] 화면번호 {currentScrNo} 실시간 등록 해제");
            };

            lblRealCount = new Label
            {
                Text = "실시간 수신: 0건",
                Location = new Point(340, 57), AutoSize = true,
                ForeColor = Color.Gray
            };

            txtLog = new TextBox
            {
                Multiline = true, ReadOnly = true,
                Location = new Point(12, 90), Size = new Size(460, 260),
                ScrollBars = ScrollBars.Vertical
            };

            Controls.AddRange(new Control[] {
                btnLogin, lblStatus, txtCode, btnRequest,
                btnDisconnect, lblRealCount, txtLog
            });
        }

        // ── 조회 요청 (실시간 자동 등록) ──
        private void RequestWithRealTime()
        {
            realTickCount = 0;
            axKHOpenAPI1.SetInputValue("종목코드", txtCode.Text.Trim());
            axKHOpenAPI1.CommRqData("주식기본정보", "opt10001", 0, currentScrNo);
            AppendLog($"[조회] {txtCode.Text.Trim()} 요청 (화면번호:{currentScrNo})");
        }

        // ── 로그인 ──
        private void OnEventConnect(object sender, _DKHOpenAPIEvents_OnEventConnectEvent e)
        {
            lblStatus.Text = e.nErrCode == 0 ? "로그인 성공!" : $"실패({e.nErrCode})";
        }

        // ── 조회 데이터 수신 (1회) ──
        private void OnReceiveTrData(object sender, _DKHOpenAPIEvents_OnReceiveTrDataEvent e)
        {
            if (e.sRQName == "주식기본정보")
            {
                string name  = axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, 0, "종목명").Trim();
                string price = axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, 0, "현재가").Trim();
                AppendLog($"[조회결과] {name} | 현재가:{price}");
                AppendLog("[실시간] 등록 완료! 체결 데이터를 수신합니다...");
            }
        }

        // ── 실시간 데이터 수신 (시세 변동마다 반복) ──
        private void OnReceiveRealData(object sender, _DKHOpenAPIEvents_OnReceiveRealDataEvent e)
        {
            if (e.sRealType == "주식체결")
            {
                string time     = axKHOpenAPI1.GetCommRealData(e.sRealKey, 20);   // 체결시간
                string price    = axKHOpenAPI1.GetCommRealData(e.sRealKey, 10);   // 현재가
                string diff     = axKHOpenAPI1.GetCommRealData(e.sRealKey, 11);   // 전일대비
                string rate     = axKHOpenAPI1.GetCommRealData(e.sRealKey, 12);   // 등락율
                string volume   = axKHOpenAPI1.GetCommRealData(e.sRealKey, 15);   // 거래량
                string strength = axKHOpenAPI1.GetCommRealData(e.sRealKey, 228);  // 체결강도

                realTickCount++;
                lblRealCount.Text = $"실시간 수신: {realTickCount}건";

                AppendLog($"[체결] {e.sRealKey} | {time} | {price}({diff}, {rate}%) | 거래량:{volume} | 강도:{strength}");
            }
        }

        private void AppendLog(string msg)
        {
            txtLog.AppendText(msg + "\r\n");
        }
    }
}

핵심 로직 뜯어보기

1. 실시간 체결 데이터 추출 — sRealType 분기 + FID

if (e.sRealType == "주식체결")
{
    string price    = axKHOpenAPI1.GetCommRealData(e.sRealKey, 10);   // 현재가
    string volume   = axKHOpenAPI1.GetCommRealData(e.sRealKey, 15);   // 거래량
    string strength = axKHOpenAPI1.GetCommRealData(e.sRealKey, 228);  // 체결강도
}

OnReceiveRealData에서는 먼저 sRealType으로 어떤 종류의 데이터인지 구분합니다. "주식체결"이면 현재가, 거래량 등을 꺼내고, "주식호가잔량"이면 매수/매도 호가를 꺼내는 식이죠. 그리고 GetCommRealData()의 두 번째 인자에 FID 번호를 넣어 원하는 값을 가져옵니다. 각 실시간 타입별 FID 목록은 KOA Studio의 "실시간목록" 탭에서 확인할 수 있어요.

2. 실시간 해제 — DisconnectRealData

axKHOpenAPI1.DisconnectRealData(currentScrNo);
// currentScrNo = "1001" → 이 화면번호로 등록된 실시간 전부 해제

실시간 수신이 더 이상 필요 없을 때 호출합니다. 포인트는 화면번호 단위라는 점이에요. 조회할 때 사용한 화면번호를 그대로 넘기면 됩니다. 만약 같은 종목을 다른 화면번호로도 등록해뒀다면 그쪽 실시간은 영향 없이 계속 수신돼요. 그래서 종목별로 화면번호를 체계적으로 관리하는 게 중요합니다.

마무리하며

실시간 데이터 처리를 정리하면 이렇습니다. 조회하면 자동으로 실시간 등록이 되고, OnReceiveRealData에서 sRealType으로 데이터 종류를 구분한 뒤, GetCommRealData(FID)로 값을 꺼내고, 필요 없으면 DisconnectRealData(화면번호)로 해제하면 돼요. 이걸로 조회 3부작이 끝났습니다! 조회 요청 → 데이터 수신 → 실시간 수신, 이 세 가지 흐름을 손에 익히면 키움 OpenAPI의 시세 파트는 완전 정복입니다.
반응형

관련글 더보기

댓글 영역