본문 바로가기

기능과 기술

파이썬으로 영웅문4 종합시황 자동화 시스템 구축하기

728x90

투자자라면 종합시황 정보는 매일매일 확인해야 하는 중요한 데이터입니다. 특히, 영웅문4는 다양한 금융 정보를 제공하는 프로그램으로 유명하지만, 수작업으로 정보를 얻는 데는 시간과 노력이 소모됩니다. 이를 해결하기 위해 파이썬을 활용해 자동으로 종합시황 정보를 가져올 수 있는 방법을 고민하고 있습니다. 이를 통해 수많은 데이터를 빠르게 확인하고, 의사결정을 효율적으로 내릴 수 있는 도구를 만들 수 있습니다. 과연, 어떤 방식으로 접근하는 것이 효과적일까요?

영웅문4에서 제공해주는 종합시황 창
영웅문4에서 제공해주는 종합시황 창

매매법

지금 뜨겁게 몰린 주식을 한다. 불타기를 한다. 이런 내용들이 의미하는 바는 조금씩 다르겠지만 실시간 정보를 기반으로 빠르게 의사결정을 한다면 수익을 낼 수도 있는 매매법인 것 같습니다. 그러면 어디에서 정보를 얻느냐 인데 제목과 같이 종합시황(뉴스)을 통해 실시간 정보를 얻고 추천한 종목을 매수하는 방법을 고려해 보려 합니다. 매수는 Open API+를 활용하면 되는데 종합시황과 같은 정보는 어떻게 얻을 수 있을까요? 얻은 정보를 어떻게 실시간 처리할 수 있을까요?

 

문제점

많은 투자자들이 여전히 일일이 영웅문4를 통해 시황 정보를 직접 확인하고 있으며, 이것은 비효율적인 과정입니다. 여러 지표를 확인하고 분석하는 데 많은 시간이 소요되며, 실시간으로 변화하는 시장에 발 빠르게 대응하지 못할 수도 있습니다. 이러한 문제는 시간이 지남에 따라 더욱 심각해질 수 있으며, 투자 의사결정의 질이 떨어질 위험도 존재합니다. 또한, 영웅문4의 인터페이스가 사용자 친화적이지 않다고 느끼는 경우가 있어 데이터 접근이 어려울 수도 있습니다. 기존에 제공해 주는 Open API+ 에서는 종합시황 정보를 제공해주지 않기 때문에 눈으로 종합시황을 보고 매매를 하기에는 시차가 발생하게 됩니다. 우리는 완벽하게 실시간을 구현할 수는 없지만 실시간에 가깝게 동작하는 프로그램을 만들고 싶습니다.

 

내가 선택한 방법 pywinauto

결과적으로 파이썬을 이용해 영웅문4에서 제공하는 종합시황 정보를 자동으로 가져오는 시스템을 구축하면, 수작업으로 데이터를 확인하는 불편함을 해소할 수 있습니다. 파이썬 외에도 영웅문4를 핸들링할 수 있는 모든 프로그램은 가능할 겁니다. 그러나 저는 파이썬이 익숙하므로 pywinauto를 활용해서 영웅문4의 종합시황 정보를 익는 것을 만들기로 했습니다. 제한적인 부분은 영웅문4를 자동 로그인 하지 않고 로그인되어서 띄워져 있는 상태에서 시작한다는 점입니다. 완전 자동 매매를 위한 작업은 상당히 많은 노력이 들기 때문에 제한함으로써 원하는 기능 구현에 집중할 수 있습니다. 만약 pywinauto를 통해 영웅문을 핸들링하는 것이 처음이라면 아래 글들을 먼저 보셔도 좋습니다.

2023.11.28 - [영웅문Global 핸들링] - 영웅문Global 핸들링을 위한 환경 설치 (feat. 파이썬)

 

영웅문Global 핸들링을 위한 환경 설치 (feat. 파이썬)

미국주식 자동 매매 프로그램을 21년에 만들어서 운영한 지 벌써 만으로 2년이 되어갑니다. 당시에는 그리드 매매법을 빠르게 구현해서 투자에 뛰어드는 것을 중요하게 생각했어서 RPA 도구를 활

kyeyangdak.tistory.com

 

 

소스소스

이제 파이썬을 활용해 종합시황 정보를 가져오는 작업을 시작할 차례입니다. 먼저 영웅문4의 데이터 구조를 파악하고, 필요한 정보가 어떤 방식으로 제공되는지 분석합니다. 이후 파이썬 라이브러리를 사용해 데이터를 자동으로 불러오고, 이를 가공하여 투자에 필요한 정보로 전환할 수 있습니다. 말처럼 쉽게 만들면 참 좋겠습니다. 그런데 pywinauto는 여전히 핸들링하기가 쉽지 않습니다. 핸들을 찾고 핸들링을 하기 위해서 값을 출력하고 확인하는 노가다 작업이 이뤄집니다. pywinauto를 통해 응용프로그램을 핸들링할 때는 디버깅이 참 쉽지 않은 것 같습니다.

pip install psutil
pip install pywinauto

 

더보기
import re
import time

import psutil
from pywinauto import Application


time_pattern = re.compile(r'^\d{2}:\d{2}:\d{2}$')


class NewsInfo:
    def __init__(self, time, provider, content, code=None):
        self.time = time            # 뉴스 시간
        self.provider = provider    # 뉴스 제공업체
        self.content = content      # 뉴스 내용
        self.code = code            # 종목코드 (옵션)


def print_children(element, depth=0):
    # 현재 요소 출력 (depth에 맞춰 들여쓰기 적용)
    if element.element_info.handle in [5902992, 1119610, 3608778, 857544, 2823642]: # 2823642 <- 메인 내용
        pass
    else:
        indent = ' ' * (depth * 4)  # depth에 따라 4칸씩 들여쓰기
        print(f"{indent}Class: {element.class_name()}, Text: {element.window_text()}, handle: {element.element_info.handle}")

        # 자식 요소가 있으면 재귀 호출로 탐색
        children = element.children()
        for child in children:
            print_children(child, depth + 1)


def find_syslistview32_recursive(element):
    # 현재 요소의 자식들을 가져옴
    children = element.children()

    for child in children:
        # 클래스 이름이 'SysListView32'인 경우 해당 요소 반환
        if child.class_name() == 'SysListView32':
            return child

        # 자식이 있는 경우 재귀적으로 탐색
        found_child = find_syslistview32_recursive(child)
        if found_child:  # 재귀 호출에서 찾았을 경우 반환
            return found_child

    return None  # 찾지 못한 경우 None 반환


# 재귀적으로 클래스가 'SysListView32'인 요소를 찾는 함수
def find_class_name_recursive(element, class_name):
    # 찾은 요소들을 저장할 리스트
    found_elements = []

    # 현재 요소의 자식들을 가져옴
    children = element.children()
    filtered_children = [child for child in children if child.window_text() not in ["신호관리자", "메뉴툴바", "티커툴바", "종목툴바", "화면툴바", "쾌속주문툴바"]]

    for child in filtered_children:
        # 클래스 이름이 일치하면 리스트에 추가
        if child.class_name() == class_name:
            found_elements.append(child)

        # 자식이 있는 경우 재귀적으로 탐색하여 결과 리스트에 병합
        found_elements.extend(find_class_name_recursive(child, class_name))

    return found_elements  # 모든 일치하는 요소를 리스트로 반환


# SysListView32의 자식 중에서 시:분:초 형식의 텍스트를 필터링하는 함수
def filter_time_format_children(element):
    # 시:분:초 형식의 텍스트를 가진 자식 요소들 저장할 리스트
    time_elements = []

    # 모든 자식 요소 탐색
    children = element.children()

    for child in children:
        # 자식 요소의 텍스트가 시:분:초 형식인 경우 필터링
        text = child.window_text()
        if time_pattern.match(text):
            time_elements.append(child)

    return time_elements


# 영웅문 process id 찾기
process_id = 0
for process in psutil.process_iter(['pid', 'name']):
    if process.info['name'] == 'nkrunlite.exe':
        process_id = process.info['pid']
        break

# 실행 중인 어플리케이션에 연결
app = Application(backend="uia").connect(process=process_id)

# 메인 윈도우 핸들링
main_window = app.window(title='영웅문4')
main_window.set_focus()
# main_window.maximize()
time.sleep(1)

# 각 자식 컨트롤의 정보 출력
screen_area = None
for child in main_window.children():
    if child.class_name() == "MDIClient": # 작업 영역
        screen_area = child
        break

# 각 자식 컨트롤의 정보 출력
screen_area = None
menutoolbar = None
for child in main_window.children():
    if child.window_text() == "메뉴툴바":
        menutoolbar = child

    if child.class_name() == "MDIClient": # 작업 영역
        screen_area = child

# 메뉴툴바로 부터 메뉴를 호출 할 예정
menus = None
if menutoolbar:
    menutoolbar_children = menutoolbar.children()
    for child in menutoolbar_children:
        if len(child.children()) == 4:
            menus = child
            break

input_field = None
if menus:
    input_field = menus.children()[0].children()[0]


syslistview32_child = None
for i in range(5):
    all_news_window = None
    for child in screen_area.children():
        if child.window_text().startswith("[0700]"):
            all_news_window = child
            break

    if all_news_window:
        all_news_window.close()

    # 0700
    input_field.set_focus()
    input_field.type_keys('0700')
    time.sleep(2)

    all_news_window = None
    for child in screen_area.children():
        if child.window_text().startswith("[0700]"):
            all_news_window = child
            break

    time.sleep(2)
    button_list = find_class_name_recursive(all_news_window, "Button")
    for button in button_list:
        if button.window_text() == "조회":
            button.click()
            time.sleep(2)
            break

    # main_window = app.window(title='영웅문4')
    syslistview32_child = find_syslistview32_recursive(app.window())
    if syslistview32_child is None:
        print(f"{i+1} try")
        time.sleep(1)
        pass
    else:
        print("found syslistview32")
        break

if syslistview32_child:
    time_children = filter_time_format_children(syslistview32_child)

    # 필터링된 요소 출력
    for elem in time_children:
        # print(f"Found: Class: {elem.class_name()}, Text: {elem.window_text()}, Handle: {elem.element_info.handle}")
        news_info = elem.children()
        if len(news_info) >= 5:
            print(f"{news_info[0].window_text()} {news_info[2].window_text()} {news_info[3].window_text()} | {news_info[4].window_text()}")
else:
    print("No 'SysListView32' class child found.")

 

남은 문제점

종합시황 정보가 엄청나게 빠르게 지나갑니다. 이 정보를 캐치해서 실시간 매매를 수행하려면 더 많은 처리가 필요할 것 같습니다. 솔직히 실시간, 누락없이 처리하는 것은 기술적으로 불가능해 보이기도 합니다. 그럼에도 세상의 누군가는 이 방법을 적용해서 돈을 벌고 있을 수도 있지 않을까? 란 쓸데없는 생각을 해봅니다. 어쨌든 필터를 설정하든 몇 개 누락이 되든 올라온 종합시황을 빠르게 캐치하고 해당 종목이 오르고 있는데 거래량이 일정 수준을 넘어가면 매수한다 등의 복합 조건을 적용할 수 있으면 정말 투자가 잘 될 것 같습니다. 그리고 익절 매도도 잘 설정해야겠죠?

 

급 마무리

시장 정보는 실시간으로 변화하며, 이를 적시에 확보하는 것이 무엇보다 중요합니다. 늦은 정보 수집은 손실로 이어질 수 있습니다.

지금 바로 파이썬을 활용해 영웅문4의 종합시황 정보를 자동으로 수집하고 분석하는 시스템을 구축해 보세요. 궁금한 내용은 언제든지 댓글 또는 ssjokelife@naver.com으로 메일 주세요. (소스도 드립니다. 부끄러워서 github에 올린 내용은 공유를 못하고 있습니다.)

 

 

 

반응형