L-ATS 3.0은 지난 10월 초 모든 서비스 구현이 완료되어 서비스의 기능 테스트 진행중이다.
투자 프로그램 개발 시 유의 사항 프로그램을 테스트 하기 위해 보통 모의투자를 사용한다. 모의투자를 통해 프로그램을 테스트 하는 것은 본인 생각보다 실투자와는 많은 차이가 있다. 먼저 증권사에서 제공하는 API TR은 결과물이 실투자와 모의투자가 다른 경우가 있다. (예를 들어, 같은 TR이지만 실투자에서만 결과물을 보여주는 경우) 그리고 모의투자는 매수/매도 타이밍이 매우 늦는다. 내가 만든 프로그램은 빠른 매수와 빠른 매도가 핵심이기 때문에 이러한 성격의 프로그램에는 모의투자가 적절하지 않다. 그래서 테스트도 소량의 현금으로 진행....한....다....
테스트를 현금으로 [실투자]에서 진행하고 있으므로, 실 운영과 기능 테스트를 함께 진행하고 있는 셈이다.
기능적인 소소한 버그가 존재하긴 하지만 기획했던 서비스의 전체적인 흐름은 생각했던대로 정상적으로 운영되는 것을 확인했다. 더구나 시스템은 수일간 증권 시장이 운영되는 시간동안 장애없이 잘 운영되었다.
이제 준비는 끝난 것일까...?
처음 L-ATS 3.0의 서비스를 기획할 때 캡쳐된 글자 이미지를 OCR 처리하여 종목명을 추출하는 것을 생각해내고 선택한 이유는 현재 내가 인공지능 전문 솔루션 회사에 재직중이었기 때문이었다.
무언가 내가 만든 이 서비스에도 인공지능적인 요소를 넣고 싶었기 때문이다. 그렇게 선택한 것이 OCR 이었고, 최근 큰 이슈가 되었던 ChatGPT를 활용하는 방법을 찾고 싶었지만 서비스 기능들을 아무리 살펴봐도 ChatGPT가 할만한 일은 없었다.
물론 저 생각은 [기획] 단계에서의 생각이었다.
[운영] 단계에서의 발견
인공지능에는 100%는 없다.
이 말은 즉, 글자 이미지가 OCR 처리된 결과물은 100% 정확하지 않을 수 있다는 것이다.
내가 사용한 OCR 엔진인 Tessaract는 한국어 언어 모델을 지정해서 사용했는데 그 때문인지 영어 알파벳이 포함된 종목정보는 영어 알파벳이 아라비아 숫자로 인식되었다.
예를 들면, SK하이닉스는 59하이닉스와 같이 인식되어 결과물을 생성했다.
여기서 ChatGPT가 문득 떠올랐다.
OCR로부터 잘못 인식된 종목정보를 ChatGPT에게 물어서 교정할 수 있지 않을까?
ChatGPT는 아주 다양한, 많은 업무를 해결해주니까 가능할 것이라 생각하긴 했지만, 과연 1750억개의 파라미터에 우리 한국 증권 시장의 KOSPI, KOSDAQ 정보가 있을지는 의문이긴 했다.
그래서 프롬프트 작성에 열을 올리기 시작했다.
프롬프트란? ChatGPT와 같은 LLM 모델에게 질문을 할 때, 더 나은, 더 좋은 답변을 억기 위해서 질문을 최적화 하는 기법을 말한다. 좋은 프롬프트를 만들수록 더 좋고 간결한 정보를 획득할 수 있다.
처음에는 심플하게 ChatGPT에게 물어봤다.
음...역시나 내가 원하는 답변을 얻지는 못했다.
ChatGPT를 잘 활용하는 방법 중에 가장 좋은 방법은 컨텍스트를 명확하게 하는 것인데
예를 들면, 부가적인 정보들을 주어주는 것이다. (갑자기 ChatGPT 활용법으로 길이 새는것 같은데.....)
내가 필요한 정보는 한국 증권 시장에 속한 종목 이름에 대한 것이니 좀 더 정확한 답변을 얻기 위해 Role Prompt 기법을 사용해본다. (ChatGPT에게 역할을 주어주는것!)
ChatGPT가 해야할 일을 보다 더 자세하게 지시해주면 이렇게 내가 원하는 답변을 정확하게 얻을 수 있다.
이제 이것을 내 서비스에 녹아들게 하면 보다 더 많은 종목들에 대해 자동 매매을 처리할 수 있게 되고, 좀 더 인공지능적인 서비스가 되지 않을까?
더 나은 L-ATS를 위해 ChatGPT를 접목시킬 수 있는 서비스의 시스템 설계를 조금씩 진행해 봐야겠다.
두번째 L-ATS 2.0 사용자 화면의 Focus는 '매매 처리중인 종목의 표시' 부분이었다.
세번째 L-ATS 3.0 사용자 화면의 핵심은 이것이다.
캡쳐된 화면 표시와 OCR 처리된 문자열
L-ATS 2.0에서 Flexible 스탑로스를 통한 매도의 자동화를 완성한 상태에서
L-ATS 3.0에서는 매수 행위의 자동화를 구현하여 매매의 완전 자동화 를 완성하는 것이 서비스 기획의 핵심 부분이다.
그렇기 때문에 사용자가 보기 편하도록 캡쳐된 화면과 해당 이미지의 OCR 결과를 실시간으로 표시하는 부분에 Focus를 맞췄다.
3.0 버전의 핵심 기능 부분을 부각시키는 것이 사용자 관점에서 서비스의 장점을 직접적으로 잘 나타낼 수 있기 때문에 그때 그때 이미지 캡쳐한 화면과 OCR 처리된 내용을 즉각적으로 표시해줌으로써 사용자의 만족감을 더 높여 줄수 있다.
① 메인 메뉴
메뉴의 구조는 [HTS 로그인], [시스템 설정], [프로그램 종료]로 구성된다.
HTS 로그인 : 이베스트 증권에 로그인할 수 있도록 기능을 제공한다.
시스템 설정 : 메인 화면에는 보이지 않는 시스템적으로 Critical한 값(예; OCR 엔진 정보, 예외처리 문자열, 문자열 파싱 패턴 등)들을 설정할 수 있는 화면을 제공한다.
프로그램 종료 : 시스템의 안정적인 운영을 위해 비정상 종료 Case를 없애고 이 버튼으로만 종료될 수 있도록 했다.
② 상태 확인
HTS 서버 연동 : 증권사(이베스트 증권) API를 통해 HTS와 연동 상태 표시
서버 연결 : LINUX Server의 L-ATS Main Server와 연동 상태 표시
③ 프로그램 조작
캡쳐 영역 [Button]: 캡쳐할 화면의 영역을 설정할 수 있는 투명한 Form 생성
시작 [Button] : 사용자 설정 값으로 자동 매매를 시작하고 정지하는 버튼
캡쳐타임(초) : 실시간으로 화면을 캡쳐할 시간 간격
[ 캡쳐 타임이 필요했던 이유 ] 자동매매의 최초 서비스 기획의 핵심은 '누군가' 실시간으로 특정 종목을 언급해 주는 조건이 되어야 하는 것이었다. 근데 과연 누가...? 그렇게 찾고 찾아 선택한 것이 카카오톡과 텔레그램의 일명 뉴스방이었다. 무료로 불특정 다수에게 특정 종목의 당일 Good/Bad 뉴스를 실시간으로(?) 알려주는 고마운 곳. 하지만 이 방들은 소프트웨어적으로 개인이 메시지를 받을 수 없었다. (캡쳐 + OCR 방식을 선택한 이유.) 때문에 이 뉴스방에 새로운 뉴스가 올라온 것을 알 수 있는 방법이 없다. 그래서 일정 간격으로 화면을 지속적으로 캡쳐해서 OCR 결과를 처리하는 방식으로 처리했다. (OCR 기술이 많이 발전했듯 이런것도 해결이 가능한 좋은 방향의 방법이 곧 생기길...)
스탑로스(%) : 자동 매도를 위한 손실률 설정
④ 자동매수 설정
보유계좌 : 자동 매매에 사용할 사용자의 증권사 계좌
매수금 : 자동으로 매수할 경우 1회 매수가
1회 매수가가 10만원일 경우, 1주당 10만원 이상인 종목은 매수하지 않는다. 1주당 10만원 이하인 종목은 10만원 이내에서 최대 매수량을 계산하여 매수한다. 예) 1주당 9천원인 종목이면 11주 매수
⑤ 잔고조회
자산총액 : 현재 선택된 사용자 증권사 계좌의 잔고 확인
예수금 : 현재 선택된 사용자 증권사 계좌의 예수금 확인
⑥ 캡쳐 이미지
캡쳐 영역으로 선택된 화면의 캡쳐된 이미지를 표시
⑦ OCR 결과
캡쳐된 이미지의 OCR 변환 결과를 표시
⑧ 실시간 잔고
매수가 완료되서 현재 계좌에 보유하고 있는 종목 리스트 표시
표시되는 종목 리스트들은 실시간으로 현재가를 갱신하여 표시하고 스탑로스를 계산
⑨ 거래 완료
당일 거래 완료된 종목 리스트 표시
정말 이베스트 증권의 거래 완료 종목 표시를 위한 API는 최악이다. HTS나 MTS에서 확인할 수 있는 '잔고 조회'의 화면에서 조회되는 값들과 동일한 값들을 표시하기 위해 정말 많은 시행착오를 겼었다. 이 내용은 따로 포스팅 예정이다.
⑩ 종목 추출 결과
OCR 결과에서 패턴 분석으로 종목명을 추출하고 LINUX Server program으로 전달하여 매수를 위한 해당 종목의 Code 정보를 요청/수신하여 표시
⑪ 실시간 건수
현재 계좌에 보유하고 있는 종목의 갯수와 당일 거래 완료된 종목의 갯수 표시
⑫ 거래 완료
당일 거래 완료된 종목들의 금액 정보 표시
⑬ 접속 정보
사용자의 증권사 계정 접속 정보 표시
L-ATS 시스템이 버전업 되면서 UX/UI는 일부분들이 계승되어 구성되는것 같다.
아무래서 버전 마다 기획하고 설계하고 구현하고 운영하다보면 배우는 것이 있기때문에 버릴건 버리고, 활용할 건 활용하게 되는것 같다.
'DevCenter'의 TR 목록 중, 계좌 자산 현황 정보를 볼 수 있을 것 같은 TR은 아래처럼 4가지가 가능해 보인다.
위의 4가지 종류의 TR을 하나하나 눌러가며 'OutBlock'의 항목들을 잘 살펴보고 내가 필요한 정보들을 모두 획득할 수 있는 TR을 선택한다.
내가 필요로 하는 정보들은 아래와 같다.
실자산총액
D+1 예수금
D+2 예수금
총 매입금액
평가손익
평가금액
손익률
나는 저 위의 4가지 TR을 모두 사용해보고 많은 시간을 삽질(?)하며 결국 [CSPAQ12200] TR을 선택했다.
나와 같은 삽질을 하지 않도록...각 TR에 대한 내용을 조금 정리해 본다.
1. CSPAQ12200 현물계좌 예수금/주문가능금액/총평가조회(API)
이름만 보면...내가 필요한 정보들이 다~있을 것만 같은 TR 이름이다. 하지만 '총 매입금액'과 '평가손익'에 대한 정보는 존재하지 않아 존재하는 나머지 정보들을 이용해서 두 개 정보를 얻어냈다. 간단한 수학 공식으로 얻어낼 수 있다.
다시 CSPAQ12200에 대해 얘기해 보자. 나의 경우는 이 TR을 내가 원할 때 1회 조회함으로써 내 자산 정보를 확인할 수 있도록 하는 하는데에 사용하였는데, 의외로 주기적으로 이 정보를 요청하도록 개발하는 사람이 많은지 TR 요청 폭주로 인해서 잦은 장애가 발생한다고 한다. 그래서 이베스트 측에서는 이 TR 대신 CSPAQ22200을 새로 만들어 사용하게 했다고 했다. 하지만....CSPAQ22200은 정말 적은 정보를 제공하여 나에게는 적합하지 않은 TR이었다.
2. CSPAQ12300 현물계좌 잔고내역 조회(API)
이 TR이야말로 내가 원하는 정보들을 다~ 제공해주는 TR이다. 하지만 공교롭게도 이 TR은 모의투자에서만 지원하고 실투자에서는 지원하지 않는다. 이유는 나도 모르겠다. 이것은 이베스트에서 공식적으로 실투자에서는 제공되지 않는다고 한 사항이며, 그걸 알기 전까지 정말 엄청난 시간 소모와 삽질을 했다...실투자용 자동매매 프로그램을 만들 경우 이 TR은 사용하지 않는다.
3. t0424 주식잔고2
이 TR도 CSPAQ12300과 마찬가지로 내가 원하는 정보들은 다~ 제공해 주는 TR이다. t0424 TR 정보를 보면 알겠지만, 이 TR 하나로 자산현황과 해당 계좌에 존재하는 체결된 종목 리스트들을 함께 전달해 준다. 설계한 내 프로그램의 기능상 이 두 가지 기능이 분리되어 사용되어야 하기에 이 TR은 자산현황 조회 시 사용하지 않고 오로지 계좌 잔고 조회에만 사용하였다. 설계한 프로그램이 굳이 이 두 가지 기능을 따로 분리할 필요가 없다면, t0424 TR을 사용하는 것을 추천한다. 이 TR을 사용하는 법은 다음 포스팅에...
이제 구현 후 테스트를 해보자.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using XA_SESSIONLib;
using XA_DATASETLib;
namespace xingAPI_Test
{
public partial class xingAPI_Test : Form
{
// Global 변수
bool gIsConnHTS = false;
string gAccount = string.Empty;
// xingAPI Session 클래스 선언
XASessionClass myXASessionClass;
// xingAPI TR 클래스 선언
XAQueryClass CSPAQ12200; // 자산현황 조회
public xingAPI_Test()
{
InitializeComponent();
// xingAPI Session 객체 생성
myXASessionClass = new XASessionClass();
}
// Form Load될 때 초기화
private void xingAPI_Test_Load(object sender, EventArgs e)
{
Realtime_Log.Items.Add("====== xingAPI Test 프로그램 시작 ======");
// HTS 로그인 핸들러 등록
myXASessionClass._IXASessionEvents_Event_Login += new XA_SESSIONLib._IXASessionEvents_LoginEventHandler(myXASessionClass_Login);
// xingAPI TR 이벤트 등록
CSPAQ12200 = new XAQueryClass();
CSPAQ12200.ResFileName = @"C:\eBEST\xingAPI\Res\CSPAQ12200.res"; // xingAPI 서버로부터 수신한 응답 메시지를 COM 버전의 API에서 사용할 수 있도록 Format을 맞춰주는 파일
CSPAQ12200.ReceiveData += CSPAQ12200ReceiveData; // xingAPI 서버로부터 CSPAQ12200의 응답 메시지를 수신할 함수 등록
}
// HTS 서버 접속
private void Btn_Conn_Server_Click(object sender, EventArgs e)
{
string serverType = "hts.ebestsec.co.kr";
gIsConnHTS = myXASessionClass.ConnectServer(serverType, 20001); // 20001은 HTS 서버 고정 Port 값
if (gIsConnHTS == true)
{
Realtime_Log.Items.Add("이베스트 실투자 서버 접속 완료");
}
else // 연결 실패했을 경우
{
int ErrCode = myXASessionClass.GetLastError();
var ErrMsg = myXASessionClass.GetErrorMessage(ErrCode);
string err_log = String.Format("[서버연결] ERR - {0}({1})", ErrMsg, ErrCode);
}
}
// HTS 서버 로그인
private void Btn_Login_Click(object sender, EventArgs e)
{
if (gIsConnHTS == true) // HTS 서버 로그인은 서버 접속이 완료된 이후에만 가능.
{
bool reqLogin = ((XA_SESSIONLib.IXASession)myXASessionClass).Login(TB_User_ID.Text, TB_User_Passwd.Text,
"", 0, false); // 세번째 인자는 공인인증비번을 입력하는 칸이지만 보안을 위해 입력 받지 않고 보안 프로그램에서 입력하는 것이 좋다.
// reqLogin의 결과 값은 로그인 완료 여부가 아닌, 서버에 로그인 요청을 전송 완료 여부이다. 때문에 Login Handler 함수를
// 별도 추가하여 요청한 로그인 정보가 정상적으로 성공했는지 확인해야한다.
if (reqLogin == true)
{
Realtime_Log.Items.Add("HTS 로그인 요청 완료");
}
else
{
Realtime_Log.Items.Add("HTS 로그인 요청 실패");
int ErrCode = myXASessionClass.GetLastError();
var ErrMsg = myXASessionClass.GetErrorMessage(ErrCode);
string err_log = String.Format("[로그인] ERR - {0}({1})", ErrMsg, ErrCode);
}
}
}
// HTS 서버 로그인 결과
void myXASessionClass_Login(string code, string msg)
{
if (code == "0000") // code 0000은 성공을 의미
{
Realtime_Log.Items.Add("HTS 로그인 완료");
// 보유 계좌 정보 Loading
int AccountCount = myXASessionClass.GetAccountListCount();
for (int i = 0; i < AccountCount; i++)
{
string AccountNo = string.Empty;
string AccountName = string.Empty;
string AccountDetailName = string.Empty;
string AccountNickName = string.Empty;
string AccountFinal = string.Empty;
AccountNo = myXASessionClass.GetAccountList(i);
AccountName = myXASessionClass.GetAccountName(AccountNo);
AccountDetailName = myXASessionClass.GetAcctDetailName(AccountNo);
AccountNickName = myXASessionClass.GetAcctDetailName(AccountNo);
AccountFinal = AccountNo + "(" + AccountName + ")";
Realtime_Log.Items.Add(AccountFinal);
CB_Account_List.Items.Add(AccountNo);
}
Realtime_Log.Items.Add("보유 계좌 정보 Loading 완료");
}
}
// 보유 계좌 선택
private void Btn_Input_Account_Click(object sender, EventArgs e)
{
if (CB_Account_List.Text != "")
{
gAccount = CB_Account_List.Text;
string str_log = String.Format("[보유계좌선택] {0} 선택완료.", gAccount);
Realtime_Log.Items.Add(str_log);
}
}
// 자산현황 조회 [단일데이터]
private void Btn_Get_Balance_Click(object sender, EventArgs e)
{
// CSPAQ12200 TR의 InBlock 데이터 세팅
CSPAQ12200.SetFieldData("CSPAQ12200InBlock1", "RecCnt", 0, "00001");
CSPAQ12200.SetFieldData("CSPAQ12200InBlock1", "MgmtBrnNo", 0, " ");
CSPAQ12200.SetFieldData("CSPAQ12200InBlock1", "AcntNo", 0, gAccount);
CSPAQ12200.SetFieldData("CSPAQ12200InBlock1", "Pwd", 0, "실제비밀번호");
CSPAQ12200.SetFieldData("CSPAQ12200InBlock1", "BalCreTp", 0, "0");
// TR 요청
var result = CSPAQ12200.Request(false);
if (result > 0)
MessageBox.Show("CSPAQ12200 요청 완료");
else
MessageBox.Show("CSPAQ12200 요청 실패");
}
private void CSPAQ12200ReceiveData(string trCode)
{
// 요청한 CSPAQ12200 TR에 대한 응답 메시지 수신
// CSPAQ12200 TR의 OutBlock2에서 필요한 데이터 추출
// 1. 예탁자산 총액
var temp_value = CSPAQ12200.GetFieldData("CSPAQ12200OutBlock2", "DpsastTotamt", 0);
decimal estimated_assets = Convert.ToDecimal(temp_value);
// 2. D+1 예수금
temp_value = CSPAQ12200.GetFieldData("CSPAQ12200OutBlock2", "D1Dps", 0);
decimal d1_deposit = Convert.ToDecimal(temp_value);
// 3. D+2 예수금
temp_value = CSPAQ12200.GetFieldData("CSPAQ12200OutBlock2", "D2Dps", 0);
decimal d2_deposit = Convert.ToDecimal(temp_value);
// 4. 손익률
temp_value = CSPAQ12200.GetFieldData("CSPAQ12200OutBlock2", "PnlRat", 0);
double valuation_rate = Convert.ToDouble(temp_value);
// 5. 잔고평가금액
temp_value = CSPAQ12200.GetFieldData("CSPAQ12200OutBlock2", "BalEvalAmt", 0);
decimal evaluation_amounts = Convert.ToDecimal(temp_value);
string text = String.Format("[예탁자산총액 : {0:#,0}] [D+1 예수금 : {1:#,0}] [D+2 예수금 : {2:#,0}] [손익률 : {3:f2}%] [잔고평가금액 : {4:#,0}]",
estimated_assets, d1_deposit, d2_deposit, valuation_rate, evaluation_amounts);
MessageBox.Show(text);
}
// 계좌잔고 조회 [반복데이터]
private void Btn_Get_Items_Click(object sender, EventArgs e)
{
}
}
}
TR을 사용할 때 어떤 값을 입력해야 하는지, 그리고 응답 메시지에서 어떤 값을 추출해야 하는 지는 'DevCenter'에서 TR 정보를 보면 쉽게 알 수 있다.
TR을 사용하기 전에 반드시 해야하는 작업이 있다. 바로 res 파일들을 다운로드하여 저장해 두고 있어야 하는데 해당 작업은 'DevCenter'에서 제공한다.
필요한 TR만 다운로드할 수 있긴 하지만, 그냥 전부다 다운로드하여두면 편하다. 다운로드하면 아래 경로에 자동적으로 저장이 되고, 개발할 때 해당 경로의 .res 파일을 ResFileName에 넣어 주면 된다.
위의 단계가 완료된 이후부터 종목을 검색하고, 내가 가진 종목 확인을 하고, 주식 주문을 할 수 있다.
자동매매 프로그램 시작의 3단계를 간단한 테스트 프로그램으로 구현해보자.
테스트 프로그램이므로 화면 구성은 매우 간단하다.
서버 연결을 할 수 있는 버튼, 아이디/비밀번호를 입력할 수 있는 텍스트 박스와 로그인할 수 있는 버튼, 로그인 후 불러오기가 완료된 보유 계좌 목록 캄보 박스와 사용할 계좌를 선택할 수 있는 버튼.
그리고 이후 포스팅하게 될 [단일데이터 조회]용 자산현황 조회 버튼과 [반복데이터 조회]용 계좌잔고 조회 버튼이 있다.
실제로 xingAPI를 이용한 자동매매 프로그램을 설계해보면 위의 기능들외의 데이터 조회 기능을 굳이 사용하지 않아도 충분하다는 것을 깨달을 것이다.
아래는 계좌 선택까지의 Full 소스다.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using XA_SESSIONLib;
using XA_DATASETLib;
namespace xingAPI_Test
{
public partial class xingAPI_Test : Form
{
// Global 변수
bool gIsConnHTS = false;
string gAccount = string.Empty;
// xingAPI Session 클래스 선언
XASessionClass myXASessionClass;
public xingAPI_Test()
{
InitializeComponent();
// xingAPI Session 객체 생성
myXASessionClass = new XASessionClass();
}
// Form Load될 때 초기화
private void xingAPI_Test_Load(object sender, EventArgs e)
{
Realtime_Log.Items.Add("====== xingAPI Test 프로그램 시작 ======");
// HTS 로그인 핸들러 등록
myXASessionClass._IXASessionEvents_Event_Login += new XA_SESSIONLib._IXASessionEvents_LoginEventHandler(myXASessionClass_Login);
}
// HTS 서버 접속
private void Btn_Conn_Server_Click(object sender, EventArgs e)
{
string serverType = "hts.ebestsec.co.kr";
gIsConnHTS = myXASessionClass.ConnectServer(serverType, 20001); // 20001은 HTS 서버 고정 Port 값
if (gIsConnHTS == true)
{
Realtime_Log.Items.Add("이베스트 실투자 서버 접속 완료");
}
else // 연결 실패했을 경우
{
int ErrCode = myXASessionClass.GetLastError();
var ErrMsg = myXASessionClass.GetErrorMessage(ErrCode);
string err_log = String.Format("[서버연결] ERR - {0}({1})", ErrMsg, ErrCode);
}
}
// HTS 서버 로그인
private void Btn_Login_Click(object sender, EventArgs e)
{
if (gIsConnHTS == true) // HTS 서버 로그인은 서버 접속이 완료된 이후에만 가능.
{
bool reqLogin = ((XA_SESSIONLib.IXASession)myXASessionClass).Login(TB_User_ID.Text, TB_User_Passwd.Text,
"", 0, false); // 세번째 인자는 공인인증비번을 입력하는 칸이지만 보안을 위해 입력 받지 않고 보안 프로그램에서 입력하는 것이 좋다.
// reqLogin의 결과 값은 로그인 완료 여부가 아닌, 서버에 로그인 요청을 전송 완료 여부이다. 때문에 Login Handler 함수를
// 별도 추가하여 요청한 로그인 정보가 정상적으로 성공했는지 확인해야한다.
if (reqLogin == true)
{
Realtime_Log.Items.Add("HTS 로그인 요청 완료");
}
else
{
Realtime_Log.Items.Add("HTS 로그인 요청 실패");
int ErrCode = myXASessionClass.GetLastError();
var ErrMsg = myXASessionClass.GetErrorMessage(ErrCode);
string err_log = String.Format("[로그인] ERR - {0}({1})", ErrMsg, ErrCode);
}
}
}
// HTS 서버 로그인 결과
void myXASessionClass_Login(string code, string msg)
{
if (code == "0000") // code 0000은 성공을 의미
{
Realtime_Log.Items.Add("HTS 로그인 완료");
// 보유 계좌 정보 Loading
int AccountCount = myXASessionClass.GetAccountListCount();
for (int i = 0; i < AccountCount; i++)
{
string AccountNo = string.Empty;
string AccountName = string.Empty;
string AccountDetailName = string.Empty;
string AccountNickName = string.Empty;
string AccountFinal = string.Empty;
AccountNo = myXASessionClass.GetAccountList(i);
AccountName = myXASessionClass.GetAccountName(AccountNo);
AccountDetailName = myXASessionClass.GetAcctDetailName(AccountNo);
AccountNickName = myXASessionClass.GetAcctDetailName(AccountNo);
AccountFinal = AccountNo + "(" + AccountName + ")";
Realtime_Log.Items.Add(AccountFinal);
CB_Account_List.Items.Add(AccountNo);
}
Realtime_Log.Items.Add("보유 계좌 정보 Loading 완료");
}
}
// 보유 계좌 선택
private void Btn_Input_Account_Click(object sender, EventArgs e)
{
if (CB_Account_List.Text != "")
{
gAccount = CB_Account_List.Text;
string str_log = String.Format("[보유계좌선택] {0} 선택완료.", gAccount);
Realtime_Log.Items.Add(str_log);
}
}
// 자산현황 조회 [단일데이터]
private void Btn_Get_Balance_Click(object sender, EventArgs e)
{
}
// 계좌잔고 조회 [반복데이터]
private void Btn_Get_Items_Click(object sender, EventArgs e)
{
}
}
}
이제 한 단계씩 프로그램을 실행해 보자.
STEP 1. 서버 연결
에러 발생 없이 정상적으로 이베스트 HTS 실투자 서버에 접속했다.
STEP 2. 로그인
아이디와 비밀번호를 입력하고 로그인을 누르면, HTS 로그인 요청이 완료되었다는 문구와 함께 공인인증서 비밀번호를 입력하는 팝업이 하나 뜬다. 많은 xingAPI 자동매매 프로그램들이 아이디/비밀번호와 함께 공인인증서 비밀번호를 입력하도록 만들어져 있는 걸 보았는데 이렇게 보안 프로그램에서 입력하는 것이(조금 번거로울 수 있겠지만...) 더 안전하겠다.
STEP 3. 보유 계좌 선택
로그인이 성공하면 보유계좌 목록에 보유한 계좌들이 저장되게 되고, 그중에서 하나를 골라 선택 버튼을 누르면 비로소 해당 계좌에 대해 자동 매매를 진행할 준비가 완료된다.
이 이후부터는 xingAPI의 TR들을 적절히 조합하여 자신만의 자동 매매 프로그램을 만들어 적용하면 된다.
다음은 선택한 계좌의 자산현황을 조회하는 기능으로 xingAPI의 [단일데이터 조회] 기능을 구현해본다.
xingAPI를 사용해서 실전 코드를 만들기 전에 xingAPI 소개자료의 일부를 짚고 넘어가야겠다.
이베스트 투자증권에서 제공하는 [xingAPI 도움말]은 생각보다 꽤 자세하게(?)는 아니고 개발을 어느 정도 해본 사람에겐 한번 훑어보고 이해가 되는 정도는 되는 수준 같았다.
목차를 보면 생각보다 매우 세분화되어 있고 자세히 설명되어 있을 것만 같다.
하지만 실제로 열어보면~ 비개발자가 보기엔 쉽게 이해하기 어려운 부분들이 많이 있다. 게다가 [프로그램 개발 가이드]는 엑셀 VBA로 개발하는 과정만이 소개되어 있다. (생각해보니...비개발자는 저것만 보고 엑셀 VBA로 개발하면 되긴 하겠다....굳이 다른 개발언어 쓸 필요없이...)
xingAPI 데이터 조회 종류
위의 [xingAPI 도움말] 을 보면 5가지의 데이터 조회 방식이 보인다.
일반 데이터 조회
단일 데이터 조회
반복 데이터 조회 (Occurs)
연속 데이터 조회
실시간 데이터 조회
위에서 실제로 사용할 수 있게끔 설명되어 있는 것은 '일반 데이터 조회'를 제외한 나머지다.
아래는 xingAPI 소개자료의 데이터 조회에 대한 내용이다.
일단 데이터 조회
xingAPI에서의 데이터 조회는 미리 정해진 형식의 입력값을 서버로 전송하고 그에 대한 데이터를 응답받는 것을 의미.
1) TR (Transaction)
xingAPI에서의 Transaction은 서버로부터 데이터를 주고받는 행위.
예를 들어, 종목코드를 입력해서 현재가를 받아오는 것, 계좌번호를 입력해서 잔고를 받아오는 것.
2) TR Code
TR 요청할 경우 서버 입장에서 어떤 종류의 데이터를 요청하는 TR인지 구별할 수 있도록 TR코드를 같이 입력해 주어야 함. TR코드는 5자리인 경우와 10 자린 경우가 있음.
3) TR Layout
TR 요청을 통한 데이터 조회는 미리 정의된 입력값과 결과값들로 구성되어 있는데 이 구성된 형태를 TR Layout이라고 부름. 이베스트 xingAIP의 DevCenter를 이용하여 확인 가능.
Layout은 크게 [InBlock] 입력값 - [OutBlock] 결과값으로 구성되어 있다. 사용자는 InBlock을 만들어 서버에 요청하면, 서버는 OutBlock 형태로 결과값을 돌려줌.
4) RES
서버에서 보낸 OutBlock 형태는 당연히 그들만의 Protocol Interface에 의한 데이터 형식으로 이루어져 있음. 이 결과값을 COM 버전의 xingAPI에서 인식할 수 있는 형식으로 변경한 구조를 의미.
때문에 COM 버전 사용 시 반드시 Local PC에 사용한 TR Code의 RES 파일이 저장되어 있어야 함. (DevCenter에서 다운로드할 수 있음. - 다음 포스팅에서 자세히...)
Architecture-Design에 속하는 이 작업은 Front-End의 User Interface(UI)적 편의성과 간결함, 그리고 정확성을 바탕으로 하는 사용자 친화적 경험 및 지식이 필요하며, Back-End의 Call-Flow(메시지 흐름)와 가장 중요한 Data Structure가 가능해야한다.
현재 L-ATS Flex v2.0은 약 50% 정도 완성이 되었지만, 실제 게시글은 그때마다 작성하지 못했다. 이유는 위에서 '가장 중요한'이라고 말한 Data Structure가 불안정했기 때문이다.
처음 사용해 보는 이베스트 투자증권의 xingAPI에서 얻을 수 있는 Data가 생각보다 제한적이고 API의 사용성 또한 제한적이라 프로그램이 만들어지면서 데이터 구조가 많이 변경되었다.
지금 시점은 xingAPI로부터 얻을 수 있는 데이터가 모두 파악되고, 그 데이터들을 어떤 부분에 사용하고 응용할지 정리가 되었기에, 잠시 개발을 멈추고 글을 작성한다.
1. L-ATS 화면 구성
크게 총 15개의 구역으로 구분이 되며, 각 구역 마다의 설명은 아래 정리되어 있다.
L-ATS v1.0과 가장 큰 차이점은 '매수' 기능이 없어져 그에따른 '미체결' 관련 화면 구역이 없어졌다. 그리고 L-ATS v2.0의 핵심 기능인 '스탑로스 Flex' 관련 설정 및 결과 창이 생겼다.
실은 '스탑로스 Flex'보다 '스탑로스'가 더 표시할 정보가 많이 있는데(설정 조건이 복잡해서...)...결과 창의 위치를 바꾸어야 할 수도 있을 것 같다.
2. L-ATS 상세 화면 구성
① L-ATS v2.0의 주 메뉴 Bar
② L-ATS v2.0 프로그램 사용자 정보
: L-ATS v2.0을 사용하기 위한 사용자 가입 정보.
③ 이베스트 투자증권 HTS 서버 접속 정보
: 접속 (초록색), 미접속 (빨간색)
④ 자동 매매 시작 버튼
: 이 버튼이 눌리지 않는한 자동매매가 시작되지 않음.
⑤ 프로그램 알림 Bar
: 프로그램 동작 중 사용자에게 알림이 필요한 수준의 메시지가 표시됨.
⑥ 사용자 계좌 목록
: 사용자가 보유한 이베스트 투자증권의 계좌 목록 표시. HTS 로그인이 완료되어야 표시됨.
⑦ 실시간 지수 정보
: 실시간으로 증권 시장의 코스피/코스닥 지수를 표시. 약 3초마다 데이터 수신. HTS 로그인이 완료되어야 표시됨.
: 현재 사용자가 보유한 종목들의 목록. 이 목록에서 종목별 스탑로스 처리 여부를 정할 수 있음.
⑪ 자동 스탑로스 환경 설정
: 자동 매매 시작 이후 적용될 스탑로스 설정 값.
⑫ 스탑로스 처리 종목 목록
: 실시간 잔고에서 스탑로스 처리를 선택한 종목들에 대한 목록 표시.
⑬ 거래 완료 종목 목록
: 스탑로스 또는 스탑로스 Flex에서 매도처리가 완료된 종목들에 대한 목록 표시.
⑭ 스탑로스 Flex 처리 종목 목록
: 실시간 잔고에서 스탑로스 Flex 처리를 선택한 종목들에 대한 목록 표시.
⑮ 이베스트 투자증권 사용자 정보
: HTS 로그인 후 표시되는 사용자의 이베스트 증권 사용자 정보.
3. xingAPI 사용 구역
위의 15개 구역 중 xingAPI를 통해 얻은 데이터를 표시하는 구역에 대한 설명이다.
생각보다 xingAPI를 사용하는 부분은 많지 않다. 그리고 단순하다.
실시간으로 계속 데이터를 요청하고 수신하여 반영해야하는 '실시간 잔고' 부분을 제외하고는 단순 Request-Response 처리이기에 크게 어려운 부분은 없다.
xingAPI에서 제공하는 TR처리 기능 중 한번 Binding 해두고 값의 변화가 있을 때마다 서버 측에서 데이터를 내려주는 TR 처리 방식이 있는데 실시간으로 변화되는 잔고 정보를 갱신하는 데에 매우 유용할 것 같지만, 경험상 굳이 세션 유지를 신경 쓰며 보다 세세한 예외처리를 하기 위한 노력을 기울이는 것보다 주기적으로 정보를 요청하여 처리하는 것이 더 심플할 것 같다. 프로그램의 기능은 심플한 것이 최고다.
앞서 말한바와 같이 이베스트의 XingAPI는 서버 연결과 HTS 연결이 별로도 동작한다.
때문에 L-ATS 프로그램을 사용하기 위해서는 아래 두 가지 로그인 절차를 거치게 설계했다.
Step 1. L-ATS 프로그램 로그인
Step 2. 이베스트 HTS 로그인
번거로울 수 있을 것이라 생각할 수도 있겠지만, 이베스트 XingAPI의 로그인 구조와
내가 추구하는 (아무나 내프로그램을 사용할 수 없는...?) 방향과 딱 맞아 떨어졌다 생각할 수 있겠다.
지난 '두번째 프로그램' 초기화면 설계와 조금 달라진 모습을 볼 수 있다.
우선, 프로그램 실행과 동시에 'L-ATS 로그인' 화면을 제공하여 프로그램 사용자 로그인과 동시에
이베스트 HTS 서버 연결을 선택하도록 했다. (덕분에 프로그램 사용자 정보도 관리해야할 것 같다.)
먼저 구현된 각 화면의 표시 정보를 보자. (기능이 구현되는 순으로 계속 설명을 써야겠다.)
사용자 정보 : L-ATS 등록 사용자 정보
HTS : 실시간 이베스트 HTS 연동 상태 표시. 빨 : Disconnect, 녹 : Connected.
시작 버튼 : HTS 로그인 후 계좌를 선택해야 하므로, ATS의 모든 기능은 이 버튼이 눌린 시점부터 시작된다.
알림 : 프로그램에서 Event 발생 시, 텍스트로 표시해 준다. (예] 로그인, 매수, 매도 등)
하단 Status Bar : 이베스트 HTS 사용자 정보 표시
우선 로그인을 해보자.
내 계정은 이미 등록이 되어 있는 상태이며, 추후 사용자 관리관련 포스팅 예정이다.
그리고 로그인 버튼을 누르면 다음과 같은 L-ATS 시작 화면이 된다.
L-ATS 프로그램 사용자의 정보가 왼쪽 상단에 표시되고, 실시간 이베스트 HTS 접속 상태가 'HTS' 부분에 빨간색->초록색으로 표시가 된다. '모의투자'를 선택했을 경우 '모투', '실투자'를 선택했을 경우 '실투'라고 표시된다.
그리고 '알림' 부분에 현재 발생한 로그인 이벤트에 대한 내용이 표시된다.
여기까지는 아직 이베트스 HTS에 로그인 하긴 전 상태라 계좌정보, 자산현황 등을 알 수 없다. (다음 게시물 예정.)
자동 매매 프로그램에 있어서 HTS 연결은 매우 중요하다.
정전, 네트워크 장애 등등. 어떠한 이유에 있어 프로그램이 돌아가고 있는 상태에서 HTS 연결만 끊기는 경우가 발생할 수 있으므로, HTS 연결 상태를 지속적으로 확인(Heart-Beat)하고 재연결(Reconnect)를 할 수 있도록 개발해야하는 것이 기본이다. 이건 Database 서버와의 연결도 마찬가지이다.
L-ATS v1.0 때보다 많은 기능이 설계에 들어가고 있어...매우 정신이 없다...
그래도 재밌는걸 어떡하나 싶다...
만들다보니 회원가입 절차가 필요해져서 '비밀번호'는 당연히 입력 즉시(나도 알 수 없도록) 암호화하여 DB에 저장하도록 해야겠다 생각했지만, 이베스트 XingAPI 특징상 HTS 로그인시에 아이디/비번/공인인증서비번을 입력 받도록 되어있다.
보안에 굉장히 취약할 수도 있는 부분이라 이 부분을 어떻게 해야 사용자들이 안심하고 사용할 수 있을지 고민이다.