티스토리 뷰

서론


C#을 통해 키움증권 Open API 활용한 프로그램 구현하기 포스팅 4탄입니다. 이번 포스팅에서는 키움증권 계정에 설정된 조건식을 읽어와서, 조건식 실시간 검색을 구현하려고 합니다. 조건식은 Open API를 통해 설정할 수 없고 키움증권 HTS (영웅문4)를 통해 설정해야 합니다.

C# 키움증권 Open API (3) - 예수금 및 종목 정보 조회 링크

C# 키움증권 Open API (3) - 예수금 및 종목 정보 조회
서론 C# 으로 키움증권 Open API 활용하여 프로그램을 구현하는 방법에 대한 포스팅 3탄입니다. 이번엔 좀 더 응용된 기능을 구현해보려고 합니다. 우선 선택한 계좌의 예수금 조회 를 추가하려고 합니다. 이후 주..
https://joel-helloworld.tistory.com/70

1. 조건식 가져오기


💡
조건식은 우선 키움증권 HTS(영웅문4)에서 설정할 수 있습니다. 화면 번호 “0150”에서 조건식을 본인이 원하는 조건에 맞게 설정하면 해당 조건에 맞는 종목들을 검색할 수 있습니다.
💡
저는 일단 인터넷에 찾은 시가갭 종목을 가져오는 조건식을 설정했습니다.

💡
이제 프로그램에서 키움증권 계정에 설정한 조건식을 읽어올 차례입니다. 조건식을 읽어올 ComboBox, Button을 추가합니다.

// 조건식 조회
btnGetCondition.Click += (s, e) =>
{
    if (axKHOpenAPI1.GetConnectState() == 0)
    {
        ShowMessageBox("Open API 연결되어 있지 않습니다.", MessageBoxIcon.Information);
        return;
    }

    WriteLog("[조건식 조회]");                
    if (axKHOpenAPI1.GetConditionLoad() == 1)
        WriteLog("   조건식 파일 저장 - 성공");
    else
        WriteLog("   조건식 파일 저장 - 실패");
};
💡
조건식을 읽어오기 위해서 ButtonClick 이벤트를 추가합니다.
💡
조건식을 가져오기 위해서 GetConditionLoad() 을 통해 조건식을 파일로 저장합니다. 해당 파일은 Open API 연결이 해지되는 시점에 자동 삭제됩니다.

class ConditionInfo
{
    public int Index { get; set; }
    public string Name { get; set; }
    public DateTime? LastRequestTime { get; set; }
}

private List<ConditionInfo> conditionInfo = new List<ConditionInfo>();

// KHOpenAPI Control Events
axKHOpenAPI1.OnReceiveConditionVer += (s, e) =>
{
    if (e.lRet != 1) return;

    cboCondition.Items.Clear();
    conditionInfo.Clear();

    string[] arrCondition = axKHOpenAPI1.GetConditionNameList().Trim().Split(';'); // ';' 구분

    foreach (var cond in arrCondition)
    {
        if (string.IsNullOrEmpty(cond)) continue;
        var item = cond.Split('^'); // '^' 구분 ex) 001^조건식1
        conditionInfo.Add(new ConditionInfo
        {
            Index = item[0].ToInt(),
            Name = item[1]
        });                    
    }
    cboCondition.Items.AddRange(arrCondition);
    if (cboCondition.Items.Count > 0) cboCondition.SelectedIndex = 0;
    WriteLog("   조건식 조회 - 성공");
};
💡
조건식을 파일로 저장하게되면 OnReceiveConditionVer 이벤트가 호출됩니다.
💡
추후 조건식을 활용한 일반 검색, 실시간 검색을 위해 Class ConditionInfo를 추가합니다.
💡
파일에 저장된 조건식을 읽어오는 동작은 GetConditionNameList()을 통해 구현할 수 있습니다. 결과값은 서로 다른 조건식은 ‘;’로 구분되고, 조건식 번호와 조건식 이름은 ‘^’으로 구분되어 가져옵니다. ex) 001^조건식1;002^조건식2;003^조건식3;

💡
정상적으로 조건식을 읽어온 결과입니다.

2. 조건식 일반 검색


💡
이제 읽어온 조건식에 맞는 종목을 일반 검색할 차례입니다. 일반 검색을 위해 Button을 추가합니다.

// 일반 검색
btnCondSearch.Click += (s, e) =>
{
    if (string.IsNullOrEmpty(cboCondition.SelectedItem.ToString())) return;
    string[] condition = cboCondition.SelectedItem.ToString().Split('^');
    
    var condInfo = conditionInfo.Find(f => f.Index == condition[0].ToInt());
    if (condInfo == null) return;

    if (condInfo.LastRequestTime != null && condInfo.LastRequestTime >= DateTime.Now.AddSeconds(-60))
    {
        int second = 60 - (DateTime.Now - condInfo.LastRequestTime.Value).Seconds;
        WriteLog($"{second}초 후에 조회 가능합니다.");
        return;
    }    

    WriteLog("[일반 검색]");

    condInfo.LastRequestTime = DateTime.Now;
    int result = axKHOpenAPI1.SendCondition(GetScreenNo(), condition[1], condition[0].ToInt(), 0);

    if (result == 1)
        WriteLog("   조건식 일반 검색 - 성공");
    else
        WriteLog("   조건식 일반 검색 - 실패");
};
💡
하나의 조건식에 대한 검색은 60초 마다 가능합니다. 즉 조건식1에 대한 검색을 한번 수행하면 60초 동안 재검색을 불가능합니다. 이를 위해 LastRequestTime을 기억하고 있다가 비교하여 Log를 남기도록 구현합니다.
💡
조건식에 맞는 종목 검색은 SendCondition를 통해 구현합니다. 파라미터는 화면 번호, 조건식 이름, 조건식 번호, 조회 구분 입니다. 조회 구분의 경우 0은 일반 검색, 1은 실시간 검색입니다.

// 조건식 일반 검색
axKHOpenAPI1.OnReceiveTrCondition += (s, e) =>
{
    string code = e.strCodeList.Trim();
    if (string.IsNullOrEmpty(code)) return;

    if (code.Length > 0) code = code.Remove(code.Length - 1);
    int codeCount = code.Split(';').Length;
    axKHOpenAPI1.CommKwRqData(code, 0, codeCount, 0, "조건일반검색", GetScreenNo());
};
💡
SendCondition을 통해 조건식을 보내면 OnReceiveTrCondition 이벤트가 호출됩니다.
💡
종목은 많은 수의 종목을 읽어올 예정이기 때문에 CommKwRqData을 통해 읽어옵니다. 첫번째 파라미터는 종목 코드(’;’로 구분한 string), 종목 코드 개수, Request 이름, 화면 번호가 필요합니다.

private DataTable dtCondStock = new DataTable();

// Receive Data
axKHOpenAPI1.OnReceiveTrData += (s, e) =>
{
    switch (e.sRQName)
    {
        case "조건일반검색":
            DataTable dataTable = new DataTable();
            dataTable.Columns.Add("종목코드", typeof(string));
            dataTable.Columns.Add("종목명", typeof(string));
            dataTable.Columns.Add("현재가", typeof(string));
            dataTable.Columns.Add("전일대비", typeof(string));
            dataTable.Columns.Add("등락율", typeof(string));
            dataTable.Columns.Add("거래량", typeof(string));
            int count = axKHOpenAPI1.GetRepeatCnt(e.sTrCode, e.sRQName);
            for (int i = 0; i < count; i++)
            {                            
                dataTable.Rows.Add(
                    axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, i, "종목코드").Trim(),
                    axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, i, "종목명").Trim(),
                    string.Format("{0:#,##0}", axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, i, "현재가").Trim().ToInt()),
                    string.Format("{0:#,##0}", axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, i, "전일대비").Trim().ToInt()),
                    string.Format("{0:#,##0.00}%", axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, i, "등락율").Trim().ToDecimal()),
                    string.Format("{0:#,##0}", axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, i, "거래량").Trim().ToDecimal())
                );
            }
            dtCondStock = dataTable;
            dataGridView1.DataSource = dtCondStock;
            break;
    }
};
💡
CommKwRqData를 통해 Request를 보내면 OnReceiveTrData 이벤트가 호출됩니다.
💡
종목코드, 종목명, 현재가, 전일대비, 등락율, 거래량을 읽어온 뒤 DataGridView에 바인딩합니다.

💡
DataGridView에 정상적으로 조건식에 맞는 종목 정보가 읽어온 결과입니다.

3. 조건식 실시간 검색


💡
일반 검색의 경우 검색 버튼을 누른 순간에 해당하는 데이터를 읽어오지만, 실시간 검색을 하면 새로운 종목이 조건식에 편입되거나 기존 종목이 조건식에서 이탈되는 경우에 재갱신을 실시간으로 동작할 수 있습니다.
💡
구현을 위해 실시간 검색, 실시간 중단 Button을 추가합니다.

// 실시간 검색
btnCondRealSearch.Click += (s, e) =>
{
    if (string.IsNullOrEmpty(cboCondition.SelectedItem.ToString())) return;
    string[] condition = cboCondition.SelectedItem.ToString().Split('^');

    var condInfo = conditionInfo.Find(f => f.Index == condition[0].ToInt());
    if (condInfo == null) return;

    if (condInfo.LastRequestTime != null && condInfo.LastRequestTime >= DateTime.Now.AddSeconds(-60))
    {
        int second = 60 - (DateTime.Now - condInfo.LastRequestTime.Value).Seconds;
        WriteLog($"{second}초 후에 조회 가능합니다.");
        return;
    }

    WriteLog("[실시간 검색]");

    condInfo.LastRequestTime = DateTime.Now;
    int result = axKHOpenAPI1.SendCondition(GetScreenNo(), condition[1], condition[0].ToInt(), 1);

    if (result == 1)
        WriteLog("   조건식 실시간 검색 - 성공");
    else
        WriteLog("   조건식 실시간 검색 - 실패");
};
💡
실시간 검색 Button의 Click 이벤트입니다. 앞서서 구현한 일반 검색과 동일하게 구현하되 SendCondition조회 구분1로 전달합니다.
💡
최초 종목 검색을 일반 검색과 동일하게 OnReceiveTrCondition 이벤트가 호출되면서 읽어옵니다.

// 조건식 실시간 검색
axKHOpenAPI1.OnReceiveRealCondition += (s, e) =>
{
    switch(e.strType)
    {
        case "I": // 편입
            axKHOpenAPI1.SetInputValue("종목코드", e.sTrCode);
            axKHOpenAPI1.CommRqData("조건실시간검색", "OPT10001", 0, GetScreenNo());
            break;
        case "D": // 이탈
            DataRow[] findRows = dtCondStock.Select($"종목코드 = {e.sTrCode}");
            if (findRows.Length == 0) return;
            dtCondStock.Rows.Remove(findRows[0]);
            dataGridView1.DataSource = dtCondStock;
            break;
    }
};
💡
실시간 검색의 차이점은 실시간으로 종목이 조건식에 편입, 이탈될 때 OnReceiveRealCondition 이벤트가 호출된다는 점입니다.
💡
e.strType이 “I”인 경우 편입, “D”인 경우 이탈입니다. 종목이 조건식에 편입, 이탈될 때 DataGridView의 Row를 추가 및 삭제 하도록 구현합니다.
💡
이탈되어 Row를 삭제하는 경우에는 간단하게 해당 조건식 번호와 일치하는 Row를 Remove합니다.
💡
편입되는 경우에는 CommRqData를 통해 Request를 전달합니다.

axKHOpenAPI1.OnReceiveTrData += (s, e) =>
{    
    switch (e.sRQName)
    {        
        case "조건실시간검색":
            dtCondStock.Rows.Add(
                axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, 0, "종목코드").Trim(),
                axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, 0, "종목명").Trim(),
                string.Format("{0:#,##0}", axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, 0, "현재가").Trim().ToInt()),
                string.Format("{0:#,##0}", axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, 0, "전일대비").Trim().ToInt()),
                string.Format("{0:#,##0.00}%", axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, 0, "등락율").Trim().ToDecimal()),
                string.Format("{0:#,##0}", axKHOpenAPI1.GetCommData(e.sTrCode, e.sRQName, 0, "거래량").Trim().ToDecimal())
            );
            dataGridView1.DataSource = dtCondStock;
            break;
    }
};
💡
CommRqData을 통해 Request를 전달하면 OnReceiveTrData 이벤트가 호출됩니다.
💡
DataGridView에 해당 Row를 추가하여 바인딩합니다.

💡
DataGridView에 정상적으로 조건식에 맞는 종목 정보가 읽어온 결과입니다. 실시간으로 편입, 이탈되는 경우 DataGridView의 데이터가 변경됩니다.

// 실시간 중단
btnCondRealSearchStop.Click += (s, e) =>
{
    if (string.IsNullOrEmpty(cboCondition.SelectedItem.ToString())) return;
    string[] condition = cboCondition.SelectedItem.ToString().Split('^');

    WriteLog("[실시간 중단]");
    axKHOpenAPI1.SendConditionStop(GetScreenNo(), condition[1], condition[0].ToInt());
};
💡
주의해야할 점은 조건식에 대한 실시간 검색을 수행하다가 멈추는 경우 실시간 검색 중단을 해야합니다.
💡
실시간 중단 버튼을 통해 SendConditionStop을 호출하여 실시간 검색을 중단해줍니다. 파라미터는 화면 번호, 조건식 이름, 조건식 번호 입니다.

마무리


C#을 통해 키움증권 Open API를 활용한 프로그램을 구현하는 방법에 대한 4번째 포스팅이 끝났습니다. 이제 어느정도 실제 투자에 활용할 수 있는 프로그램 기능이 점점 구현이 되고 있습니다. 하지만 조건식을 통해 종목 검색을 하는 경우는 HTS로 보다 더 정확하고 빠르게 읽어올 수 있습니다. 즉 종목을 읽어오기만 할 뿐 아니라 추가적인 자동 매매 기능을 구현해야 의미가 있다고 할 수 있습니다. 다음 포스팅에서는 자동 매매에 대한 기능을 구현하고자 합니다.

C# 키움증권 Open API (3) - 예수금 및 종목 정보 조회 링크

C# 키움증권 Open API (3) - 예수금 및 종목 정보 조회
서론 C# 으로 키움증권 Open API 활용하여 프로그램을 구현하는 방법에 대한 포스팅 3탄입니다. 이번엔 좀 더 응용된 기능을 구현해보려고 합니다. 우선 선택한 계좌의 예수금 조회 를 추가하려고 합니다. 이후 주..
https://joel-helloworld.tistory.com/70

Uploaded by N2T

댓글