'이베스트'에 해당되는 글 3건

  1. 2023.03.03 [Trading] L-ATS 3.0의 시작
  2. 2021.11.07 [Trading] xingAPI - 데이터 표시하기 4
  3. 2020.06.21 [Trading] 프로그램 로그인

L-ATSLim's Automatic Trading System의 약자로 2016년 성(임씨)이 같은 나(프로그램 개발)와 내 친구(검색식 생성)의 합작으로 시작된 프로젝트였다.

 

Trading 프로그램들은 모두 같은 목적을 갖고 있다. 

[ 매매의 자동화 ]

 

프로그램을 필요로하는 사람들마다 매매의 자동화의 방식, 기준은 다 제각각이다

L-ATS은 매매의 자동화를 위해 이러한 기능들을 제공했다.

 

2016년 L-ATS 1.0

2020.05.24 - [Trading] - [Trading] 첫번째 프로그램

 

[Trading] 첫번째 프로그램

프로그램 명 : L-ATS (Lim's Automatic Trading System) 버전 : 1.0 환경 : Visual Basic 주요기능 : 검색식 기반 주식 매수, 설정값 기반 주식 매도, 스탑로스 API : 키움증권 API 제작기간 : 2015.05 ~ 2015.08 키움에서

brandon-dev.tistory.com


  • 키움증권 API 사용
  • 키움증권 [조건검색]의 자체 개발한 검색식을 사용한 자동 매수

손익 수준(%) 및 수량을 단계적으로 커스텀 가능한 자동 매도

 

2020년 L-ATS 2.0

2020.06.07 - [Trading] - [Trading] 두번째 프로그램

 

[Trading] 두번째 프로그램

프로그램 명 : L-ATS (Lim's Automatic Trading System) 버전 : 2.0 환경 : Visual C# 주요기능 : HTS/MTS 매수 종목 정보 실시간 수신, 설정값 기반 주식 매도, Flexable 스탑로스 API : 이베스트 증권 XingAPI 제작기간 : 2

brandon-dev.tistory.com


  • 이베스트증권 XingAPI 사용
  • 자동 매매 프로그램과 텔레그램 봇의 조합으로 텔레그램에서 종목명만 입력하면 설정된 조건으로 알아서 매수해 주는 실시간 매수 자동화
  • 가장 빠르게(!) 매수하는게 주 목적으로 매매를 위한 종목 클릭, 계좌 비번 입력, 매수 수량 입력, 매수 버튼 클릭 등의 작업을 없앤 초 간단 매수
  • 상승하는 종목의 최대 수익을 낼 수 있도록 현재가에 맞춰 유동적으로 변화하는 매도 기준의 실시간 변화
  • 손실을 최소화 할 수 있는 철저한 자동 손절

 

L-ATS 1.0은 사실상 실패한 프로젝트였다. 실패라기 보단 매매의 목적(=수익)을 달성하기에 큰 단점이 있었다.

제대로된 검색식

L-ATS 1.0은 위에 기술했듯이 키움증권의 검색식을 통해 추출된 종목을 즉시 매매하는 방식으로 차트 기반의 수익을 만들어내는 제대로된 검색식을 필요로 했기 때문이다.

아는 사람은 알겠지만...수익을 만들어 내는 제대로된 검색식을 만들기란 정말 쉽지않다. 
제대로된 검색식을 누구나 만들 수 있다면 모든 사람이 부자가 됐겠지...(나 역시도 반년간 검색식에 매달렸지만....)

L-ATS 1.0은 이렇게 약 7개월 정도 운용되다가 폐기되었다.


그렇게 시간이 흘러 2019년.

친구의 권유로 카카x톡 찌라시 매매방에 들어가게 되었고, 약 1년여의 시간동안 관찰해온 찌라시 매매방의 특징으로 나만의 독창적인 매도 방식을 결합하여 L-ATS 2.0을 설계하게 되었다.

 

[ 찌라시 매매방에서 찾아낸 특징 ]

  1. 찌라시 매매방은 진짜 뉴스와 가짜 뉴스가 섞여 나온다.
  2. 찌라시 매매방의 목적 상(= 뉴스 띄우고 개미들이 들러붙어 1~2% 오를때 물량 넘기기 수법) 진짜 뉴스든 가짜 뉴스든 대부분 어느 순간 일정 수준의 상승을 보인다.
  3. 높은 확률로 상한가 직행 종목이 나온다. (=진짜뉴스)
   결론 : 가지 않는 종목을 빠른 손절로 손실을 최소화 하고 상한가 가능 종목 1개만 잡아도 평균적으로 수익이다.

[ 자동 손절로 최대 수익을 낼 수 있는 방법의 추론]

  1. 주식판의 모든 종목은 파형을 그리면서 움직인다.
  2. 세력이 들어와 급등하는 종목도 개미를 떨구거나 더 많은 매물을 모아서 올리기 위해 매량 매도를 통해 순간 가격을 내린다.
  3. 종목의 최고가가 갱신될 때마다 해당 값을 기준으로 일정 수준의 손절 라인을 실시간으로 변경하면서 가져간다면 자동매매로써의 최대 수익을 낼 수 있는 하나의 방법이 된다. (= 내 생각)
절대적 손절 라인을 -5%로 지정했다고 가정해 보자.
내가 1000원에 어떤 종목을 샀으면 그 시점에 손절 라인은 1000원에서 -5%인 950원이다. 
이때 종목이 1000원에서 1100원이 되면 그 시점에 손절 라인은 1045원이 된다. 
이런 식으로 손절 라인을 매 시점의 최고가에 맞게 높여 나간다면 1000원에서 급등했다가 다시 1000원으로 내려와도 무조건 수익이 생기게 된다. 
또한, 급등하는 중간 중간에 세력이 개미를 떨구기 위해 호가를 내리는 경우 설정된 -5% 이내에 들어오면 손절되지 않고 상승 파형을 따라 끝까지 가져갈 수 있는 좋은 조건이 된다.
이것은 고정된 금액. 또는 고정된 수익/손절 %에서 자동 청산 기능을 제공하는 많은 증권사들의 기능과는 확실히 더 나은 차별점이 있다.

이렇게 탄생한 L-ATS 2.0은 약 2년간 기능 업데이트 및 버그 수정을 거듭해가며 나의 증권 계좌를 운용해 주었다.

 

L-ATS 2.0은 찌라시 매매방에서 언급한 종목을 무조건 사는 방식을 채택하였기에 

매수에 한해서는 완전 자동화를 할 수가 없었다.

때문에, 찌라시 매매방에 종목이 올라온 것을 캐치한 후 미리켜둔 증권 앱에서 종목을 검색하여 수동으로 매수를 하였다. 

여기서 L-ATS 2.0의 단점이 발생했다.

 

바로 매수가 느리다는 것.

찌라시 매매방에 올라오는 종목은 말그대로 찌라시에 의한 급등 종목들이기 때문에 빠른 매수가 수익과 직결된다. 

하지만 수동 매수로 인해 증권사 앱에 접속하는 시간, 종목을 검색하는 시간, 계좌 비밀번호를 입력하는 시간, 매수 가격 및 매수금액 입력  시간 등..

 

이를 보완하기 위해 텔레그램 봇을 이용하여 L-ATS 2.0 프로그램과 연동을 통해 수동 매수의 한계점을 극복하였으나

이것 역시 찌라시 매매방의 종목을 내가 인지한 후에 매수가 가능했기에 타이밍을 놓치거나 아예 보지 못해 매수를 하지 못하는 단점들이 생겨났다. 

그래서 결론은,  역시 완전 자동화!!

L-ATS 2.0은 약 7개월 전, 결국 전체 수익률 약 -3% 수준을 기록하고 멈추었다. 

그래도 반자동 매매 치고는 매우 괜찮은 수준이라고 생각한다.

 


최근 애플에서 iOS 업데이트를 하면서 카메라 기능 중 OCR 기능을 기본 탑재하면서 전 세계적으로 인공지능 학습에 의한 OCR 정확도가 평균적으로 매우 상승하였음을 보여주었다. (실제로 써보면 거의 100% 정확하다.)

 

L-ATS 2.0에서 완성하지 못했던 완전 자동화를 이 인공지능 OCR 기능을 통해 구현해 보고자 

다시 Visual Studio를 열게 되었다. (지금 내가 인공지능 회사에 재직 중이니까...)

 

2023년 L-ATS 3.0

L-ATS 3.0은 찌라시 매매방의 채팅 창을 일정 시간 간격으로 캡쳐하여 TEXT만 OCR로 변환 후

종목명을 추출하여 즉시 매수 처리하는 방식으로 구현될 것이다.

 

이 과정에서도 분명 문제점은 있다. 

하지만 완전 자동화가 되는 프로그램을 만드는 것이 우선순위이기 때문에 이슈들은 일단 뒤로 제껴둘 생각이다.

Posted by [ 브랜든 ]
,

바쁘다는 핑계로 오랫만에 포스팅을 한다.

핑계인거 같지만 정말 바쁘긴 했다. (우리회사가 드디어 11월에 코스닥 입성을 하게 됐음.)

고통의 결실이 달달하길 바랄뿐이다.

 

앞 포스팅을 보니 정확히 1년전 게시물이므로...

1년만에 다시 포스팅을 한다.

 

이번 포스팅은 xingAPI를 통해서 가져온 내 계좌의 종목 정보를 

C#의 DataGridView에 Binding하여 미려한 표 형식으로 표시하고, 실시간으로 변하는 [현재가] 를 스스로 갱신하면서

보여주도록 해보자.

 

이전 포스팅에 보면 계좌 잔고 정보가 TEXT 타입의 표로 심플하게 표시되어 있다.

 


2020.10.10 - [Trading] - [Trading] xingAPI - 반복 데이터 조회

 

[Trading] xingAPI - 반복 데이터 조회

이번 포스팅은 단일 데이터 조회에 이어 xingAPI의 [반복 데이터 조회]에 대해 포스팅 해본다. 단일 데이터 조회처럼 C#으로 코드를 짤 것이며, xingAPI COM 개발 가이드에는 엑셀 VBA를 이용한 프로그

brandon-dev.tistory.com


사실, 바빠서 포스팅을 못했을 뿐 계획했던 자동 매매 프로그램은 그 당시 완성하여 (내가 당장 필요한 핵심 기능만 구현하였지만...) 계속적으로 사용하고 있었다.

당시 내가 필요했던 기능은 종목 매수는 직접하고, 설정해 놓은 StopLoss 기준에 부합할때 자동으로 매도 처리하는 프로그램이었다. 

사실 이 프로그램은 찌라시방 용도로 사용하기 위해 설계되었는데 갑자기 급등하는 종목에 대해 최대한의 수익을 끌어내기 위함이었다. 때문에 당일 매시점 갱신되는 최고가 기준으로 StopLoss 기준이 같이 변하도록(Flexible) 해두었기에 'Flex'란 이름을 붙여 만들었다.


DatagridView의 데이터 표시는 이전 예제 프로그램 위에 다시 구현해 보도록 하자.

 

VIEW 테이블 만들기

먼저 [도구상자]에서 [DataGridView]를 선택하여 화면안에 넣고 원하는 크기로 맞춰 준다.

생성된 DataGridView를 선택하고 오른쪽 하단의 [속성]에 [Columns] 항목 오른쪽 끝의 '...'을 클릭하여 [열 편집] 화면을 띄운다.

속성에 [DataSource] 항목을 통해 프로젝트 내의 특정 데이터 집합을 바로 바인딩하여 표현할 수 있는 것 같은데 이것을 시도하다 번번히 실패하여 하나하나 컬럼을 만들어 진행했다. (나는 C#을 이번에 처음 해보는 10년차 개발자이므로....)

[열 편집] - [열 추가]를 선택하여 표시하고자 하는 데이터의 컬럼을 생성해준다.

당연한 얘기지만 프로그램 내에서 표시하기 위한 위치와 실제 데이터 값의 매칭이 필요한데 DataPropertyName이 그에 해당하는 값이다. 

아래 Code라고 지정해 놓은 값은 이후에 만들 잔고 데이터 클래스의 '종목코드'를 저장하는 변수명과 일치시킬 것이다.

속성에 아랫부분으로 내려가면 HeaderText 라는 것이 있는데 이곳에 표시하고 싶은 이름을 입력하면 된다.

그리고 각각의 열에 대해서도 특정 속성을 부여할 수 있다. 예를 들면, 금액일 경우 통화 형태로, 백분율 값일 경우 소수점 자리수 표시 형태로 말이다. 

최초 만들어진 DataGridView의 모습은 이렇다.

DataGridView의 속성에서 가장 앞부분의 무의미한 Column을 숨길 수 있고, 위에서 열 편집했던 창에서 각각의 열에 대해 너비 조정이 가능하므로 본인의 입맛에 맞게 모양을 수정한다. 

 

한 화면에 표시되도록 각각의 열에 대해 너비를 조정하였다. 

(오른쪽 끝에 일부 공간을 남겨둔건 항목이 많으면 스크롤 바가 생기기 때문.)

 

VIEW 테이블과 연결할 데이터 구조체(Class) 만들기

화면에 보여줄 VIEW는 준비가 되었으니 이제 VIEW에 Binding할 데이터 구조를 생성해야 한다. 

데이터는 [클래스 리스트]를 사용했다

먼저 데이터 클래스(class)를 먼저 만들자.

 

Balance_List.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace xingAPI_Test
{
    class Balance_List
    {
        public string Code { get; set; }            // 종목코드
        public string CodeName { get; set; }        // 종목명
        public decimal CurPrice { get; set; }       // 현재가
        public decimal PurchPrice { get; set; }     // 매입가
        public int BalCount { get; set; }           // 잔고수량
        public decimal PurchAmount { get; set; }    // 매입금액
        public decimal EvalAmount { get; set; }     // 평가금액
        public decimal EvalPrice { get; set; }      // 평가손익
        public double EvalRate { get; set; }        // 손익률

        public Balance_List(string Code, string CodeName, decimal CurPrice, decimal PurchPrice, int BalCount, decimal PurchAmount, decimal EvalAmount, decimal EvalPrice, double EvalRate)
        {
            this.Code = Code;
            this.CodeName = CodeName;
            this.CurPrice = CurPrice;
            this.PurchPrice = PurchPrice;
            this.BalCount = BalCount;
            this.PurchAmount = PurchAmount;
            this.EvalAmount = EvalAmount;
            this.EvalPrice = EvalPrice;
            this.EvalRate = EvalRate;
        }
    }
}

이제 한 종목에 대한 정보를 담이 이 클래스를 리스트로 저장할 데이터 공간을 만들어 준다.

...
	List<Balance_List> gBalanceList = new List<Balance_List>(); // 잔고 클래스 리스트
...

gBalanceList라는 글로벌 변수가 Balance_List라는 하나하나의 객체를 갖는 리스트로 선언되었다.

 

솔직히 나는 C#을 다루던 개발자가 아니라 이와 같은 기능을 개발할 때 어떤 방식이 가장 최적화된 방식인지 알지 못한다. 다만, C/C++ 개발자였던 경험으로 데이터 구조체가 가장 다루기 쉽기때문에(본인에게...) 클래서 리스트 방식을 선택했다.

 

클래스 리스트에 데이터를 넣을때는 아래와 같이 객체를 새로 생성하여 리스트에 ADD해 준다.

여기서는 잔고조회 TR을 통해 가져온 결과 데이터 한줄 한줄을 만들때 마다 같이 리스트에 ADD해 준다.

그리고 DataGridView에 Binding 해주면 끝!!

...
    gBalanceList.Add(new Balance_List(expcode, hname, price, pamt, janqty, mamt, appamt, dtsunik, sunikrt));
...
	dataGridView1.DataSource = gBalanceList;
...

이제 결과물을 한번 보자.

이렇게 간단하게 미려한 표 형태의 데이터 표현이 가능하다니 이보다 편리하고도 편리할 수가 없다....

로그 찍는 부분의 어설픈 표형태(?)의 데이터만 봐도 서버 개발자들은 자괴감이 든다...

 

[ 전체 소스 ]

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;        // 자산현황 조회
        XAQueryClass t0424Occurs;       // 잔고조회 - 종목리스트

        List<Balance_List> gBalanceList = new List<Balance_List>(); // 잔고 클래스 리스트

        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의 응답 메시지를 수신할 함수 등록

            // xingAPI 반복 데이터 조회 TR 이벤트 등록
            t0424Occurs = new XAQueryClass();
            t0424Occurs.ResFileName = @"C:\eBEST\xingAPI\Res\t0424.res";
            t0424Occurs.ReceiveData += t0424ReceiveData;
        }
        
        // HTS 서버 접속
        private void Btn_Conn_Server_Click(object sender, EventArgs e)
        {
            string serverType = "demo.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)
        {
            // 잔고 리스트 조회
            string inform = String.Format("-- 잔고 리스트 조회");
            Realtime_Log.Items.Add(inform);

            t0424Occurs.SetFieldData("t0424InBlock", "accno", 0, gAccount);
            t0424Occurs.SetFieldData("t0424InBlock", "passwd", 0, "실제계좌비번");
            t0424Occurs.SetFieldData("t0424InBlock", "prcgb", 0, "1");
            t0424Occurs.SetFieldData("t0424InBlock", "chegb", 0, "2");
            t0424Occurs.SetFieldData("t0424InBlock", "dangb", 0, "0");
            t0424Occurs.SetFieldData("t0424InBlock", "charge", 0, "0");
            t0424Occurs.SetFieldData("t0424InBlock", "cts_expcode", 0, " ");
            int result = t0424Occurs.Request(false);
            if (result < 0)
            {
                inform = string.Format("[보유계좌] 계좌 잔고리스트(t0424) 요청이 실패했습니다. ret={0}", result);
                Realtime_Log.Items.Add(inform);
            }
        }

        private void t0424ReceiveData(string trCod)
        {
            // 전체 잔고 종목 횟수 가져오기
            var count = t0424Occurs.GetBlockCount("t0424OutBlock1");
            string inform = String.Format("잔고조회 종목 리스트 갯수 : {0}", count);
            Realtime_Log.Items.Add(inform);

            Realtime_Log.Items.Add("-------------------------------------------------------------------------------------------------------------------------------");           
            string title = String.Format("| {0,20} | {1,10} | {2,10} | {3,10} | {4,10} | {5,10} | {6,10} | {7,10} | {8,10} |", "종목명", "종목코드", "현재가", "매입가", "잔고수량", "매입금액", "평가금액", "평가손익", "수익률");
            Realtime_Log.Items.Add(title);
            Realtime_Log.Items.Add("-------------------------------------------------------------------------------------------------------------------------------");

            // 가져온 종목 횟수 만큼 반복하면서 실제 종목 정보 가져오기
            for (int index = 0; index < count; ++index)
            {
                var temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "expcode", index); // 종목코드
                string expcode = Convert.ToString(temp_value);
                temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "hname", index);       // 종목명
                string hname = Convert.ToString(temp_value);
                temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "price", index);       // 현재가
                decimal price = Convert.ToDecimal(temp_value);
                temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "pamt", index);        // 매입가
                decimal pamt = Convert.ToDecimal(temp_value);
                temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "janqty", index);      // 잔고수량
                int janqty = Convert.ToInt16(temp_value);
                temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "mamt", index);        // 매입금액
                decimal mamt = Convert.ToDecimal(temp_value);
                temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "appamt", index);      // 평가금액
                decimal appamt = Convert.ToDecimal(temp_value);
                temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "dtsunik", index);     // 평가손익
                decimal dtsunik = Convert.ToDecimal(temp_value);
                temp_value = t0424Occurs.GetFieldData("t0424OutBlock1", "sunikrt", index);     // 수익율
                double sunikrt = Convert.ToDouble(temp_value);

                string contents = String.Format("| {0,20} | {1,10} | {2,10} | {3,10} | {4,10} | {5,10} | {6,10} | {7,10} | {8,10} |",
                    expcode, hname, price, pamt, janqty, mamt, appamt, dtsunik, sunikrt);

                gBalanceList.Add(new Balance_List(expcode, hname, price, pamt, janqty, mamt, appamt, dtsunik, sunikrt));

                Realtime_Log.Items.Add(contents);
            }
            Realtime_Log.Items.Add("-------------------------------------------------------------------------------------------------------------------------------");

            // Data Binding
            dataGridView1.DataSource = gBalanceList;
        }
    }
}

 

DataGridView는 데이터가 없는 초기상태에서 회색 바탕의 빈칸이 덩그러니~ 표시된다. (디자인적으로 보기 싫게....)

개인 취향에 맞게 초기에 일정 갯수의 빈칸을 넣고, 실제 데이터가 발생시에 교체하거나 아니면 새로 다시 그리거나 나는 등의 방식으로 개발하면 좀 더 미려한 프로그램이 될 것이다.

 

 

최근 회사의 상장 준비 때문에 바빠서 (드디어 11월에 코스닥 입성!!! 갬동 ㅠㅠ) 퇴근 후라도 조금씩 잡았던 개발을 손놨었는데 이거 조금 손댔다고 다시 개발이 하고 싶어진다.....

현재 사용하고 있는 프로그램의 단점이 하나 있어 새로운 프로그램을 설계 중인데

다음 포스팅에 새로운 프로그램 개발의 시작과 함께 그 내용을 다뤄 봐야 겠다.

 

Posted by [ 브랜든 ]
,

앞서 말한바와 같이 이베스트의 XingAPI는 서버 연결과 HTS 연결이 별로도 동작한다. 

때문에 L-ATS 프로그램을 사용하기 위해서는 아래 두 가지 로그인 절차를 거치게 설계했다.

  • Step 1. L-ATS 프로그램 로그인
  • Step 2. 이베스트 HTS 로그인

번거로울 수 있을 것이라 생각할 수도 있겠지만, 이베스트 XingAPI의 로그인 구조와

내가 추구하는 (아무나 내프로그램을 사용할 수 없는...?) 방향과 딱 맞아 떨어졌다 생각할 수 있겠다.

 

L-ATS 첫 실행 화면

지난 '두번째 프로그램' 초기화면 설계와 조금 달라진 모습을 볼 수 있다. 

 

우선, 프로그램 실행과 동시에 'L-ATS 로그인' 화면을 제공하여 프로그램 사용자 로그인과 동시에 

이베스트 HTS 서버 연결을 선택하도록 했다. (덕분에 프로그램 사용자 정보도 관리해야할 것 같다.)

 

먼저 구현된 각 화면의 표시 정보를 보자. (기능이 구현되는 순으로 계속 설명을 써야겠다.)

  1. 사용자 정보 : L-ATS 등록 사용자 정보
  2. HTS : 실시간 이베스트 HTS 연동 상태 표시. 빨 : Disconnect, 녹 : Connected. 
  3. 시작 버튼 : HTS 로그인 후 계좌를 선택해야 하므로, ATS의 모든 기능은 이 버튼이 눌린 시점부터 시작된다.
  4. 알림 : 프로그램에서 Event 발생 시, 텍스트로 표시해 준다. (예] 로그인, 매수, 매도 등)
  5. 하단 Status Bar : 이베스트 HTS 사용자 정보 표시

우선 로그인을 해보자.

내 계정은 이미 등록이 되어 있는 상태이며, 추후 사용자 관리관련 포스팅 예정이다.

ID와 비밀번호를 입력 후, 모의투자/실투자 선택.

그리고 로그인 버튼을 누르면 다음과 같은 L-ATS 시작 화면이 된다.

L-ATS 사용자 로그인 및 이베스트 HTS 접속 상태.

L-ATS 프로그램 사용자의 정보가 왼쪽 상단에 표시되고, 실시간 이베스트 HTS 접속 상태가 'HTS' 부분에 빨간색->초록색으로 표시가 된다. '모의투자'를 선택했을 경우 '모투', '실투자'를 선택했을 경우 '실투'라고 표시된다.

그리고 '알림' 부분에 현재 발생한 로그인 이벤트에 대한 내용이 표시된다. 

여기까지는 아직 이베트스 HTS에 로그인 하긴 전 상태라 계좌정보, 자산현황 등을 알 수 없다. (다음 게시물 예정.)

 

자동 매매 프로그램에 있어서 HTS 연결은 매우 중요하다. 

정전, 네트워크 장애 등등. 어떠한 이유에 있어 프로그램이 돌아가고 있는 상태에서 HTS 연결만 끊기는 경우가 발생할 수 있으므로, HTS 연결 상태를 지속적으로 확인(Heart-Beat)하고 재연결(Reconnect)를 할 수 있도록 개발해야하는 것이 기본이다. 이건 Database 서버와의 연결도 마찬가지이다. 

 

L-ATS v1.0 때보다 많은 기능이 설계에 들어가고 있어...매우 정신이 없다...

 

그래도 재밌는걸 어떡하나 싶다...

 

만들다보니 회원가입 절차가 필요해져서 '비밀번호'는 당연히 입력 즉시(나도 알 수 없도록) 암호화하여 DB에 저장하도록 해야겠다 생각했지만, 이베스트 XingAPI 특징상 HTS 로그인시에 아이디/비번/공인인증서비번을 입력 받도록 되어있다.

 

보안에 굉장히 취약할 수도 있는 부분이라 이 부분을 어떻게 해야 사용자들이 안심하고 사용할 수 있을지 고민이다. 

아니면....내가 예상하는 것과 다를 수도 있고....

'Trading' 카테고리의 다른 글

[Trading] L-ATS 사용자 시나리오  (0) 2020.08.02
[Trading] 사용자 정보 구조  (0) 2020.07.18
[Trading] HTS 로그인 설계, 구현  (0) 2020.07.08
[Trading] 두번째 프로그램  (0) 2020.06.07
[Trading] 첫번째 프로그램  (0) 2020.05.24
Posted by [ 브랜든 ]
,