KRX 상장 주식 수량 정보 수집 기능 추가.

GHTS 2023. 1. 13. 12:37 Posted by UnHa Kim

시가 총액을 계산하는 데이터로서 상장 주식 수량은 중요한 지표이지만,

그동안 한 종목씩 수천번의 질의 결과를 모아서 수집해야 했기에 정확성을 유지하기 쉽지 않아서 고민이었다.

 

그러던 중 KRX에서 시가총액 및 상장 주식 수량 정보를 한 번에 제공하는 것을 수집하는 방법을 알게 되었다.

 

참고자료 : https://statools.tistory.com/175

 

링크에 나온 내용을 Go언어로 간단히 변환할 수 있었다.

관련 소스코드는 https://github.com/ghts/ghts 패키지 내 lib/daily_data/krx_listed_quantity.go 파일을 참조하면 된다.

 

'GHTS' 카테고리의 다른 글

파일 로깅 기능 추가.  (0) 2023.01.10
T8428 증시주변자금 단순화  (0) 2023.01.10
호가 단위 변경  (0) 2023.01.10
T8410 TR 추가.  (0) 2023.01.10
Xing API 초기화 문제 해결 (MsgPack -> Gob)  (2) 2022.11.02

파일 로깅 기능 추가.

GHTS 2023. 1. 10. 20:22 Posted by UnHa Kim

콘솔창을 닫으면 실행 기록이 사라져 버리고 이후 문제가 발생한 원인을 추적하는 데 애를 먹은 후,

콘솔창 출력 문자열을 파일에 동시에 저장하는 기능을 추가했다.

 

main() 함수 초반부에 다음과 같이 실행하면 해당 기능을 사용할 수 있다.

 

lib.F로그_설정_화면_파일_동시()
defer lib.F로그_파일_닫기()

 

lib.F문자열_출력(), lib.F에러_출력(), lib.New에러with출력()등의 함수에서 화면에 출력하는 내용이 'log_<연월일시분초>.txt' 파일에 저장된다.

1달이 지난 로그 파일은 삭제하여 저장공간을 꽉 채워서 문제가 생기는 현상을 미연에 방지해 놓았다.

 

 

'GHTS' 카테고리의 다른 글

KRX 상장 주식 수량 정보 수집 기능 추가.  (2) 2023.01.13
T8428 증시주변자금 단순화  (0) 2023.01.10
호가 단위 변경  (0) 2023.01.10
T8410 TR 추가.  (0) 2023.01.10
Xing API 초기화 문제 해결 (MsgPack -> Gob)  (2) 2022.11.02

T8428 증시주변자금 단순화

GHTS 2023. 1. 10. 20:12 Posted by UnHa Kim

T8428 TR은 입력값에 코스피, 코스닥을 구분하게 되어 있어서, 전체 시장 유동성을 파악하지 못한다고 생각하여 거의 사용하지 않고 있다가, DevCenter에서 시장 구분값을 입력하지 않으면 전체 시장 유동성 관련 값이 나오는 것을 뒤늦게 발견하고, T8428 입력값에서 시장 구분을 없애고, 전체 시장 유동성을 파악하는 데 도움이 되도록 단순화 하였다.

 

 

'GHTS' 카테고리의 다른 글

KRX 상장 주식 수량 정보 수집 기능 추가.  (2) 2023.01.13
파일 로깅 기능 추가.  (0) 2023.01.10
호가 단위 변경  (0) 2023.01.10
T8410 TR 추가.  (0) 2023.01.10
Xing API 초기화 문제 해결 (MsgPack -> Gob)  (2) 2022.11.02

호가 단위 변경

GHTS 2023. 1. 10. 20:09 Posted by UnHa Kim

2023년부터 새로운 호가 단위가 적용된다.

 

이에 대응하는 코드는 작년 11월경에 미리 작성해 두었으나, 당시 언론기사에 나온 기준과 2023년부터 실제로 적용된 기준이 미세하게 달라서, 에러가 발생하는 것을 발견하고, 현재 기준에 맞게 수정했다.

 

며칠 전 이미 적용된 내용이지만, 블로그에 기록해 두는 것을 깜빡하고 있다가 뒤늦게 올린다.

'GHTS' 카테고리의 다른 글

파일 로깅 기능 추가.  (0) 2023.01.10
T8428 증시주변자금 단순화  (0) 2023.01.10
T8410 TR 추가.  (0) 2023.01.10
Xing API 초기화 문제 해결 (MsgPack -> Gob)  (2) 2022.11.02
야후! 금융 재무 정보 얻기.  (0) 2022.10.08

T8410 TR 추가.

GHTS 2023. 1. 10. 20:05 Posted by UnHa Kim

그동안 일일 가격 정보 수집에 T8413 TR을 애용해 왔으나,

2023년 1월 5일에 이베스트투자증권 API게시판에 올라온 공지에 따르면 T8413 TR은 종가가 수정종가로 바뀌고, 게다가 이후 삭제될 예정이라고 한다.

대체품으로 새로 생긴 TR이 'T8410'이다.

 

'T8410'은 수정주가 적용 여부를 선택할 수 있고, 연봉 데이터도 제공한다는 점에서 기능 개선이 있다.

xing.util 패키지 내 일일 가격 정보 수집 기능에서 (T8413 대신) T8410을 사용하도록 수정했다.

 

Xing API 초기화 문제 해결 (MsgPack -> Gob)

GHTS 2022. 11. 2. 22:20 Posted by UnHa Kim

Go 1.19로 업그레이드 이후 MsgPack 변환을 위한 외부 의존성 라이브러리(github.com/ugorji/go/codec)에서 종종 에러가 발생하였다.

해당 라이브러리 개발자는 깃허브 issue의 버그 보고에 대응을 제대로 못하고 있다.

 

이 문제를 해결하기 위해서 문제를 일으키는 외부 코드에 대한 의존성을 삭제하기로 했다.

Go언어에는 'Gob'이라는 변환 기능이 내장되어 있다. (참고 : https://pkg.go.dev/encoding/gob)

Gob은 MsgPack보다 범용성/호환성은 떨어지지만, 사용 편의성과 성능에서 크게 뒤처지지 않는다.

 

MsgPack 변환을 이용하는 모든 기능을 Gob변환 형식으로 바꾸어서 외부 코드를 더 이상 사용하지 않도록 하니 더 이상 에러가 발생하지 않는다.

대신, ghts/xing/dll32 패키지는 Go언어에서만 사용할 수 있게 제한되었다.

하지만, 아마도 다른 언어에서 dll32 패키지만 쏙 빼서 사용하는 경우는 무척 드물 것이므로 크게 문제 되지는 않을 것으로 예상된다.

 

야후! 금융 재무 정보 얻기.

GHTS 2022. 10. 8. 13:46 Posted by UnHa Kim

다음 동영상에 '야후! 금융'에서 재무 정보를 추출하는 방법이 잘 설명되어 있다.

https://youtu.be/fw4gK-leExw

 

동영상 내용이 파이썬 기준이지만, 내용만 이해하면 Go언어에서도 구현은 쉽다.

chromedp(https://github.com/chromedp/chromedp) 모듈을 사용하면 자바스크립트를 이용해서 동적으로 생성되는 '야후! 금융' 웹페이지도 문제없이 불러들일 수 있다.

이렇게 읽어온 HTML에서 JSON 데이터 부분만 추출해서 map형태로 저장한 후 적절히 활용하면 된다.

애플의 재무 데이터를 읽어들이는 Go언어 예제 코드는 다음과 같다.

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/chromedp/chromedp"
	"log"
	"regexp"
	"testing"
	"time"
)

// 참고자료 : https://youtu.be/fw4gK-leExw
// 애플 재무 정보 수집 예제
func main() {
	const url템플릿 = `https://finance.yahoo.com/quote/%v/financials`
	종목코드 := "AAPL" // 애플

	// create chrome instance
	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()

	// create a timeout
	ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	var html string
	url := fmt.Sprintf(url템플릿, 종목코드)

	if err := chromedp.Run(ctx,
		chromedp.Navigate(url),
		chromedp.InnerHTML("body", &html, chromedp.ByQuery),
	); err != nil {
		log.Fatal(err)
	}

	// HTML에서 JSON 데이터를 맵 형태로 추출
	html = regexp.MustCompile(`(?s)\s--\sData\s--\s.+?</script>`).FindString(html)
	html = regexp.MustCompile(`(?s)root.App.main.+</script>`).FindString(html)
	html = html[16 : len(html)-21]

	var 맵 map[string]interface{}

	json.Unmarshal([]byte(html), &맵)

	손익계산서_연도 := f2맵_모음(맵, []string{"context", "dispatcher", "stores", "QuoteSummaryStore", "incomeStatementHistory", "incomeStatementHistory"})
	f맵_모음_출력("손익계산서_연도", 손익계산서_연도)

	손익계산서_분기 := f2맵_모음(맵, []string{"context", "dispatcher", "stores", "QuoteSummaryStore", "incomeStatementHistoryQuarterly", "incomeStatementHistory"})
	f맵_모음_출력("손익계산서_분기", 손익계산서_분기)

	재무상태표_연도 := f2맵_모음(맵, []string{"context", "dispatcher", "stores", "QuoteSummaryStore", "balanceSheetHistory", "balanceSheetStatements"})
	f맵_모음_출력("재무상태표_연도", 재무상태표_연도)

	재무상태표_분기 := f2맵_모음(맵, []string{"context", "dispatcher", "stores", "QuoteSummaryStore", "balanceSheetHistoryQuarterly", "balanceSheetStatements"})
	f맵_모음_출력("재무상태표_분기", 재무상태표_분기)

	현금흐름표_연도 := f2맵_모음(맵, []string{"context", "dispatcher", "stores", "QuoteSummaryStore", "cashflowStatementHistory", "cashflowStatements"})
	f맵_모음_출력("현금흐름표_연도", 현금흐름표_연도)

	현금흐름표_분기 := f2맵_모음(맵, []string{"context", "dispatcher", "stores", "QuoteSummaryStore", "cashflowStatementHistoryQuarterly", "cashflowStatements"})
	f맵_모음_출력("현금흐름표_분기", 현금흐름표_분기)
}

func f2맵_모음(맵 map[string]interface{}, 키_모음 []string) (맵_모음 []map[string]interface{}) {
	for _, 키 := range 키_모음 {
		if 맵2, ok := 맵[키].(map[string]interface{}); ok {
			맵 = 맵2
			continue
		} else if 값_모음, ok := 맵[키].([]interface{}); ok {
			맵_모음 = make([]map[string]interface{}, len(값_모음))

			for i, 값 := range 값_모음 {
				맵_모음[i] = 값.(map[string]interface{})
			}

			return 맵_모음
		} else {
			panic(fmt.Errorf("예상하지 못한 자료형 %T\n", 맵[키]))
		}
	}

	return
}

func f맵_모음_출력(제목 string, 맵_모음 []map[string]interface{}) {
	for i, 맵 := range 맵_모음 {
		for 키, 값 := range 맵 {
			fmt.Printf("%v %v %v %v\n", 제목, i, 키, 값)
		}
	}
}

 

 

 

 

네이버에서 상당히 많은 국가의 주식 정보를 얻을 수 있다는 것을 알았으나,

좀 더 많은 국가의 정보를 수집하고 능력을 키우고 싶어서,

인베스팅닷컴에서 종목 정보를 수집을 시도해 봤다.

 

다음 URL에서 베트남 종목 목록 수집하는 것을 출발점으로 잡았다.

(https://www.investing.com/equities/vietnam)

 

해당 페이지는 기본적으로 'HNX 30'에 포함된 종목만을 보여준다.

모든 종목의 목록을 얻고 싶다면, 선택 상자에서 'Vietnam all stocks' 항목을 선택해야 하는 데, 이것을 프로그래밍적으로 자동으로 수행하도록 구현하는 방법을 찾느라 한참 헤맸다.

 

시행착오를 거듭한 결과, Go언어 기준으로 chromedp(https://github.com/chromedp/chromedp) 라는 모듈을 이용해서 다음과 같이 하면 모든 종목의 식별 데이터가 포함된 HTML을 추출할 수 있다.

// create chrome instance
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()

// create a timeout
ctx, cancel = context.WithTimeout(ctx, lib.P1분)
defer cancel()

const url = `https://www.investing.com/equities/vietnam`

var html string

if 에러 := chromedp.Run(ctx,
   chromedp.Navigate(url),
   chromedp.SetAttributeValue(`#all`, "value", "ALL"),
   chromedp.SetValue(`//select[@id="stocksFilter"]`, "ALL", chromedp.BySearch),      
   chromedp.WaitVisible("cross_rate_markets_stocks_1", chromedp.ByID),
   chromedp.InnerHTML("marketInnerContent", &html, chromedp.ByID),
); 에러 != nil {
   log.Fatal(에러)
}

이렇게 추출해 낸 HTML을 GoQuery(https://github.com/PuerkitoBio/goquery)등의 모듈을 이용해서 내용을 분석하면, pare_id(인베스팅닷컴 독자적인 일종의 종목 구분 코드), 종목 이름, 추가 정보 URL등의 정보를 추출할 수 있다.

 

문서 := lib.F확인2(goquery.NewDocumentFromReader(strings.NewReader(html)))
문서.Find("tbody tr").Each(func(i int, s *goquery.Selection) {
    pair_id, _ := s.Attr("id")
    href, _ := s.Find("a").Attr("href")

    <... 중략 ...>    
})

 

여기서부터 문제가 시작되는 데, 인베스팅닷컴에서 사용하는 종목 구분 코드인 Pair ID는 인베스팅닷컴 사이트 내에서만 의미가 있고, 다른 곳에서는 통하지 않는다.

Pair ID와 연결된 범용적인 '종목 코드(ticker, symbol)'을 알아내려면 모든 종목에 대해서 추가 정보 URL을 일일이 질의해야 한다.

즉, 인베스팅닷컴은 전세계 주식 종목에 대한 정보를 가장 광범위하게 제공하지만, 대신에 인베스팅닷컴 고유의 독자적인분류 코드로만 제공해서 범용성이 떨어지고, 일반적으로 사용되는 종목 코드로 변환하려면, 상당히 많은 추가 웹 질의를 해야한다.

 

이러한 범용성 문제가 없는 '야후! 금융'의 경우 제공하는 종목의 범위가 훨씬 좁은 문제가 있다.

광범위한 정보를 쉽게 수집하는 방법은 아직 못 찾았고, 편리성과 광범위함은 트레이드오프 선택 관계에 있는 것 같다.

 

주식 투자도 해외 분산이 필요한 것 같아서 이리저리 조사하던 중,

한국투자증권에서 Open API에서 지원되는 국가 중 '베트남'이 왠지 만만해 보여서,

베트남 주식 시장 데이터 수집을 시도하고 있다.

 

한국 증시가 '코스피', '코스닥'으로 나누어지는 것처럼,

베트남 증시는 '호치민(HOCHIMINH)', '하노이(HANOI)'으로 나누어져 있다.

 

'야후! 금융'의 경우 호치민 증시 종목 정보만 제공하는 듯 하고,

'인베스팅닷컴'의 경우 (종목코드 대신) 자체적인 pair_id 위주로 정보가 제공되어서 애를 먹다가,

'네이버 금융'을 이용하면 간편하게 베트남 주식 정보를 구할 수 있다는 것을 알게 되었다.

 

다음 페이지의 3번 항목을 참고해서 상장된 주식 종목 리스트를 구했다.

(R을 이용한 해외 주식용 데이터 수집 (hyunyulhenry.github.io))

 

간단히 요약하면 다음 URL에 대해서 <페이지 번호> 자리에 1,2,3,4..를 바꾸어 넣어가면서,

HTTP GET 질의를 하면 JSON 형태로 예쁘게 응답이 온다.

(1번째 URL은 호치민, 2번째 URL은 하노이)

https://api.stock.naver.com/stock/exchange/HOCHIMINH/marketValue?pageSize=60&page=<페이지_번호>
https://api.stock.naver.com/stock/exchange/HANOI/marketValue?pageSize=60&page=<페이지_번호>

현재 2022년 10월 기준 총 748개의 종목이 상장되어 있다.

해외 주식 API. (한국투자증권 Open API)

GHTS 2022. 1. 21. 18:56 Posted by UnHa Kim

그동안 이베스트투자증권의 Xing API를 이용해서 국내 주식만 매매해 왔다.

한국 증시는 부진을 거듭하는 반면, 해외 증시(특히, 미국 증시)는 상승세를 이어가는 걸 보면서,

한국 주식 시장에만 투자하는 것은 분산 투자 면에서도 별로 좋은 생각이 아니라는 것을 깨달았다.

그리하여, 해외 주식 투자도 가능한 API를 찾던 중 발견한 게 

한국투자증권의 Open API이다. ('efriend Expert'로 검색해도 된다.)

 

API 기능을 설명하는 'efriend Expert Viewer'의 화면 일부를 캡쳐했는 데,

미국 뿐만 아니라, 중국, 일본 주식도 매매가 가능한 것을 알 수 있다.

문제는 이게 OCX형태로 구현되어 있어서 Go언어에서는 사용하기 무척 까다롭다는 것이다.

 

그렇다고 해서 모든 로직을 C#으로 옮겨가려고 하니, 정든 Go언어를 손에서 내려놓고 싶지 않다.

결국, OCX호출에 편한 C#로 API를 호출하는 독립된 프로세스를 두고,

Go언어로 작성된 매매 전략 모듈에서 윈도우 소켓을 통해서 호출하는 구조를 구상 중이다.

(투자 교육 때 뵌 현직 옵션 시스템 트레이더 분에게서 윈도우 소켓 프로그래밍에 대해서 들었던 게 중요한 힌트가 되었다.)

 

역시 끊임없이 삽질을 거듭하다보면 길을 찾게 되는 것 같다.