성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# XingAPI - 주식 종목에 따른 PBR, PER, ROE, ROA 등의 재무정보 구하는 방법(t3320, t8430 예제)</h1> <p> ^^ 먼지 쌓인(?) XingAPINet을 다시 꺼냈는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > eBEST XingAPI의 C# 래퍼 버전 - XingAPINet Nuget 패키지 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12134'>https://www.sysnet.pe.kr/2/0/12134</a> </pre> <br /> 그새 뭐가 많이 바뀌었는지 기존 코드로는 로그인도 안 되는군요. ^^; 일단 XingAPINet을 업데이트했고 1.1.5 버전으로 nuget에 올려두었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > XingAPINet ; <a target='tab' href='https://www.nuget.org/packages/XingAPINet/'>https://www.nuget.org/packages/XingAPINet/</a> </pre> <br /> 자, 그럼 이것을 참조 추가해서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Install-Package XingAPINet </pre> <br /> 다음과 같이 로그인 과정까지 완료할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using XingAPINet; internal class Program { static void Main(string[] args) { bool useDemoServer = true; Program pg = new Program(); if (args.Length == 1) { if (args[0] == "hts") { useDemoServer = false; } } pg.Main(useDemoServer); } void Main(bool useDemoServer) { LoginInfo user = LoginInfo.FromPlainText("...id...", "...password...", "...인증서 password..."); // 만약 demo 서버 접속이라면 인증서 계정은 필요 없습니다. using (XingClient xing = new XingClient(useDemoServer)) { if (xing.ConnectWithLogin(user) == false) { Console.WriteLine(xing.ErrorMessage); return; } } } } </pre> <br /> 그다음, 해야 할 일은 KODAQ과 KOSPI에 상장된 주식 종목을 가져오면 되는데요, 이것은 t8430 API를 이용해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > BEGIN_FUNCTION_MAP .Func,주식종목조회(t8430),t8430,block,headtype=A; BEGIN_DATA_MAP t8430InBlock,기본입력,input; begin 구분(0:전체1:코스피2:코스닥),gubun,gubun,char,1; end t8430OutBlock,출력1,output,occurs; begin 종목명,hname,hname,char,20; 단축코드,shcode,shcode,char,6; 확장코드,expcode,expcode,char,12; ETF구분(1:ETF),etfgubun,etfgubun,char,1; 상한가,uplmtprice,uplmtprice,long,8; 하한가,dnlmtprice,dnlmtprice,long,8; 전일가,jnilclose,jnilclose,long,8; 주문수량단위,memedan,memedan,char,5; 기준가,recprice,recprice,long,8; 구분(1:코스피2:코스닥),gubun,gubun,char,1; end END_DATA_MAP END_FUNCTION_MAP </pre> <br /> 다음과 같이 간단하게 해결할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private SHCodeInfo[] GetStockList() { List<SHCodeInfo> list = new List<SHCodeInfo>(); foreach (var block in XQt8430.Get(XQt8430Gubun.전체)) { if (block.etfgubun != '0') // ETF/ETN 유형은 제거 { continue; } if (block.shcode[5] != '0') // <a target='tab' href='http://m.itooza.com/view.php?ud=2015041017193086401'>보통주만 허용</a> { continue; } SHCodeInfo code = new SHCodeInfo(block.hname, block.shcode, block.expcode, false, false); list.Add(code); } return list.ToArray(); } </pre> <br /> 위의 코드를 보면, PBR과 PER 등의 정보는 주식과 관련이 있기 때문에 ETF/ETN에 해당하는 종목은 걸러내고 있습니다. 또한 우선주의 경우에는 이후 정보를 가져올 t3320 API로는 값이 안 나오기 떄문에 이것 역시 보통주만 선택하도록 필터링합니다.<br /> <a name='t3320'></a> <br /> 자, 그다음 마지막으로 이제 해당 주식을 발행하는 회사의 PBR과 PER 등의 정보를 가져와야 하는데요, 이것은 t3320 API를 이용해 가져올 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > BEGIN_FUNCTION_MAP .Func,FNG_요약(t3320),t3320,attr,block,headtype=A; BEGIN_DATA_MAP t3320InBlock,기본입력,input; begin 종목코드,gicode,gicode,char,7; end t3320OutBlock,기업기본정보,output; begin 업종구분명,upgubunnm,upgubunnm,char,20; 시장구분,sijangcd,sijangcd,char,1; 시장구분명,marketnm,marketnm,char,10; 한글기업명,company,company,char,100; 본사주소,baddress,baddress,char,100; 본사전화번호,btelno,btelno,char,20; 최근결산년도,gsyyyy,gsyyyy,char,4; 결산월,gsmm,gsmm,char,2; 최근결산년월,gsym,gsym,char,6; 주당액면가,lstprice,lstprice,long,12; 주식수,gstock,gstock,long,12; Homepage,homeurl,homeurl,char,50; 그룹명,grdnm,grdnm,char,30; 외국인,foreignratio,foreignratio,float,6.2; 주담전화,irtel,irtel,char,30; 자본금,capital,capital,float,12.0; 시가총액,sigavalue,sigavalue,float,12.0; 배당금,cashsis,cashsis,float,12.0; 배당수익율,cashrate,cashrate,float,13.2; 현재가,price,price,long,8; 전일종가,jnilclose,jnilclose,long,8; 위험고지구분1_정리매매,notice1,notice1,char,1; 위험고지구분2_투자위험,notice2,notice2,char,1; 위험고지구분3_단기과열,notice3,notice3,char,1; end t3320OutBlock1,기업재무정보,output; begin 기업코드,gicode,gicode,char,7; 결산년월,gsym,gsym,char,6; 결산구분,gsgb,gsgb,char,1; PER,per,per,float,13.2; EPS,eps,eps,float,13.0; PBR,pbr,pbr,float,13.2; ROA,roa,roa,float,13.2; ROE,roe,roe,float,13.2; EBITDA,ebitda,ebitda,float,13.2; EVEBITDA,evebitda,evebitda,float,13.2; 액면가,par,par,float,13.2; SPS,sps,sps,float,13.2; CPS,cps,cps,float,13.2; BPS,bps,bps,float,13.0; T.PER,t_per,t_per,float,13.2; T.EPS,t_eps,t_eps,float,13.0; PEG,peg,peg,float,13.2; T.PEG,t_peg,t_peg,float,13.2; 최근분기년도,t_gsym,t_gsym,char,6; end END_DATA_MAP END_FUNCTION_MAP </pre> <br /> 따라서, 전체 주식 종목에 대해 다음과 같이 PER, PBR 정보를 가져올 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SHCodeInfo[] stocks = GetStockList(); foreach (var item in GetStockInfo(stocks)) { Console.WriteLine($"{item.Name}: PER == {item.per}, PBR == {item.pbr}"); } private IEnumerable<StockInfo> GetStockInfo(SHCodeInfo[] stocks) { using (XQt3320 query = new XQt3320()) { foreach (var item in stocks) { query.SetFieldData(XQt3320InBlock.BlockName, XQt3320InBlock.F.gicode, 0, item.SHCode); int resultCode = query.Request(); if (resultCode < 0) { Console.WriteLine($"[{resultCode}] XQt3320: request failed at {item.Name}"); break; } XQt3320OutBlock1 outBlock = query.GetBlock1(); if (outBlock.IsValidData == false) { Console.WriteLine($"XQt3320: invalid block at {item.Name}"); break; } yield return new StockInfo(item.Name, outBlock); } } } </pre> <br /> 하지만, 실제로 저렇게 해보면 얼마 동안 정보를 가져오더니 어느 순간부터는 요청이 실패합니다. 이때의 resultCode 값은 -32가 나오는데요, 이것을 XingAPI의 DevCenter 도구에서 "보기" / "에러 찾기" 메뉴로 확인해 보면 다음과 같은 식의 오류 메시지를 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Request() == -34인 경우 TR의 10분당 최대 전송 가능 횟수를 초가하였습니다. 이후부터는 전송가능횟수가 5배로 제한됩니다. </pre> <br /> 이러한 제약은 DevCenter의 "TR 속성" 창을 통해서도 확인할 수 있습니다.<br /> <br /> <img onclick='toggle_img(this)' class='imgView' alt='xingapi_tr_time_limit_1.png' src='/SysWebRes/bbs/xingapi_tr_time_limit_1.png' /><br /> <br /> 이러한 제약은 각각의 API마다 다를 수 있으므로 저렇게 꼭 "TR 속성" 창을 확인해 적절한 시간 지연을 해야 합니다. (아쉽게도 이러한 시간 정보가 RES 파일에는 없어서 이것을 소스 코드 자동화에 반영할 수가 없습니다. 혹시 이 정보를 어떻게 구할 수 있는지 방법을 아시는 분은 덧글 부탁드립니다. ^^)<br /> <br /> 암튼, 그래서 이런 경우에는 10분당 200건이니까, (10 * 60초 / 200 = ) 3초마다 한 번씩 정보를 요청하는 식으로 바꿔야 합니다. 이를 위해 QueryPerTime 속성을 넣어두었으니, 다음과 같이 설정해 주시면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private IEnumerable<StockInfo> GetStockInfo(SHCodeInfo[] stocks) { using (XQt3320 query = new XQt3320()) { // QueryPerTime에는 밀리 초 단위로 지정 <span style='color: blue; font-weight: bold'>query.QueryPerTime = 3 * 1000;</span> // 3초마다 한 번씩 전송 foreach (var item in stocks) { // ...[생략]... yield return new StockInfo(item.Name, outBlock); } } } </pre> <br /> 오늘 날짜 기준으로 XQt8430.Get 메서드가 반환하는 전체 코드는 3,333개, 그중에서 ETF/ETN을 제거하면 2,726개, 다시 그중에서 보통주만을 선별하면 2,370개 정도의 종목이 나옵니다. 정확히 3초씩 가져온다고 가정했을 때 7,110초(약 118분), 전체 가져오는데 약 2시간 정도의 시간이 걸립니다.<br /> <br /> 어차피, 프로그래밍으로 하는 거니까 DB에만 적절하게 저장해 두도록 그냥 실행해 두고 잊어버리면 되겠습니다. 또한, PER과 PBR 등의 수치들이 주식 가격에 따른 기업 실적의 결과로 산출되는 것이니 매일 장 마감 후 한 번만 하면 될 것입니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1922&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> (<a target='tab' href='https://github.com/stjeong/XingAPI/tree/master/DevCenterSample/TR/%EC%97%85%EC%A2%85/t3320'>XingAPI github에도 t3320 프로젝트</a>로 올렸습니다.) <br /> <hr style='width: 50%' /><br /> <br /> 이번 글에서는 기업별로 재무 정보를 가져오기 위해 (시간제한이 걸린) Request를 보내야 하는 불편함이 있었는데요, 다행히 이런 문제점을 해결하는 API가 있습니다. ^^ 그건 다음번 글에서 설명하겠습니다.<br /> <br /> 참고로, 웹 검색을 해보면 네이버 증권 등에서 웹 페이지로 제공하는 데이터를 HTML 파싱으로 PBR, PER 등의 정보를 가져오는 방법도 볼 수 있습니다. 그런 경우 시간제한이 걸린 증권사의 API 사용보다 빠르게 이용할 수 있지만 HTML 파싱의 특징상 홈페이지 개편이 있을 때마다 틀어질 수 있다는 단점이 있습니다. 다들 장/단점이 있군요. ^^<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3548
(왼쪽의 숫자를 입력해야 합니다.)