본문 바로가기

영웅문Global 핸들링

지금까지 작업 내용 취합 (반복 로직 수행)

728x90

이제 영웅문Global을 어느 정도 핸들링 할 수 있게 되었습니다. 지금까지 했던 내용을 취합하는 의미로 이번 시간을 생각해보면 좋겠습니다. 아래는 프로그램을 실행했을 때 단계별로 수행 할 내용입니다.

  • 영웅문Global 실행 / 로그인
  • 지금 시점의 실시간 잔고 내역 확인 (스크린샷)
  • 반복 - 영웅문Global 설정한 종목의 현재 가격 조회

최종 수행 결과는 아래 영상을 확인해주세요.

이렇게 동작하도록 만들 겁니다.

이 정도 내용을 엮어서 프로그램을 실행한 후 종목별 조회한 가격을 토대로 매매법을 수행한다면 해외주식 매매를 자동화할 수 있을 겁니다. 그 구조를 만드는 과정을 바로 해보실까요?

 

로그인

로직에 대한 이해가 필요할 경우 앞선 글 확인 후 다시 진행하시는 것을 권장합니다.

2023.12.01 - [영웅문Global 핸들링] - 영웅문Global 로그인

 

모든창 닫기

마찬가지로 로직에 대한 이해가 필요할 경우 아래 글을 확인해주세요.

2023.11.23 - [영웅문Global 핸들링] - 프로그램 실행 전 준비 (실행 중인 영웅문Global 연결하고 "모든창 닫기")

 

실시간 잔고 내역 확인

마찬가지로 로직에 대한 이해가 필요할 경우 아래 글을 확인해주세요.

2023.11.29 - [영웅문Global 핸들링] - 매매 결과 확인 (실시간 잔고 스크린샷과 값 확인 방법)

 

종목 현재 가격 조회 (반복)

2023.11.28 - [영웅문Global 핸들링] - 매수 / 매도 수행하기 - 1 (미니주문 창 호출하기)

2023.11.28 - [영웅문Global 핸들링] - 매수 / 매도 수행하기 - 2 (미니주문 창 핸들링하기)

조회하고 싶은 종목을 정의해야 합니다. 제가 물려있는(?) 종목으로 해 볼까요?!

SOXL, TQQQ, FNGU, UPRO, NAIL 애증의 3배 레버리지 ETF 종목입니다. 이 종목들의 가격을 순차적으로 가져와서 출력하는 부분을 확인해보겠습니다.

user_ticker_list = ['SOXL', 'TQQQ', 'FNGU', 'UPRO', 'NAIL']
while True:
    for user_ticker in user_ticker_list:
        # 내가 입력한 티커가 잘 선택되었는지 확인 (티커를 잘 못 설정할 수 있으므로 10회만 반복)
        for i in range(0, 10):
            edit_controls = find_controls_recursively(mini_order_window, "Edit")
            edit_controls[idx].type_keys(user_ticker, pause=0.3).type_keys('{ENTER}')
            time.sleep(1.5)

            print(f"{idx} 티커 : {edit_controls[idx].get_value()}, 가격 : {edit_controls[idx+2].get_value()}")
            if edit_controls[idx].get_value() == user_ticker:
                break
            # FIXME: 종목, 가격 통한 매매 의사결정 (핵심 로직)
    # FIXME: 종료 조건

유저가 설정한 종목을 돌면서 현재 가격을 출력합니다.

 

전체 소스코드

전체 소스코드는 아래를 참고해주세요.

더보기
import os
import time

import psutil
import pyautogui
import pyperclip
import pywinauto
from pywinauto.application import Application

user_id = "여기에 ID 패스워드"
user_pw = "여기에 인증서 패스워드"


def all_windows_close(screen_area):
    for screen in screen_area.children():
        screen.close()


def close_risk_notice_popup(main_window):
    popup_button = find_controls_recursively(main_window, "Button")
    temp_checkbox = [control for control in popup_button if "위험고지 확인창" in control.window_text()]
    if len(temp_checkbox) > 0:
        temp_checkbox[0].set_focus()
        temp_checkbox[0].click()
    temp_button = [control for control in popup_button if "닫  기" in control.window_text()]
    if len(temp_button) > 0:
        temp_button[0].set_focus()
        temp_button[0].click()


def check_meme_gubun():
    button_controls = find_controls_recursively(mini_order_window, "Button")
    meme_gubun = ""
    idx = 3
    meme_button = None
    for button in button_controls:
        if button.window_text().startswith("매수"):
            meme_button = button
            meme_gubun = "매수"
            idx = 3
            break
        elif button.window_text().startswith("매도"):
            meme_button = button
            meme_gubun = "매도"
            idx = 4
            break
        else:
            meme_gubun = ""
    return meme_gubun, idx, meme_button


def find_process_id(process_name):
    process_id = 0
    for process in psutil.process_iter(['pid', 'name']):
        if process.info['name'] == process_name:
            process_id = process.info['pid']
            break
    return process_id


def hero_global_login(app):
    login_window = app.window(title='영웅문Global Login')
    id_field = None
    pw_field = None
    for i, child in enumerate(login_window.children()):
        if child.window_text().startswith("영문+숫자 5"):
            id_field = login_window.children()[i - 1]
        elif child.window_text().startswith("영문+숫자+특수"):
            pw_field = login_window.children()[i - 1]

        if id_field and pw_field:
            break

    if id_field and pw_field:
        id_field.type_keys(user_id)
        pw_field.type_keys(user_pw).type_keys('{ENTER}')
        time.sleep(5)

    for i in range(10):
        app_main, msg = check_hero_global_is_running()
        if msg.startswith("영웅문Global"):
            break
        time.sleep(5)

    return app_main


def find_controls_recursively_friendly(control, class_name):
    found_controls = []
    for child in control.children():
        if child.friendly_class_name() == class_name:
            found_controls.append(child)
        # 자식 컨트롤들에 대해서도 재귀적으로 탐색
        found_controls.extend(find_controls_recursively_friendly(child, class_name))
    return found_controls


def find_controls_recursively(control, class_name):
    found_controls = []
    for child in control.children():
        if child.class_name() == class_name:
            found_controls.append(child)
        # 자식 컨트롤들에 대해서도 재귀적으로 탐색
        found_controls.extend(find_controls_recursively(child, class_name))
    return found_controls


def check_hero_global_is_running():
    process_id = find_process_id("nfrunlite.exe")
    process_login_id = find_process_id("nfstarter.exe")

    msg = ""
    if process_id == 0:
        if process_login_id == 0:
            app = Application().start('C:/KiwoomGlobal/bin/nfrunlite.exe')
            time.sleep(3)
            msg = "로그인창 실행"
        else:
            app = Application().connect(process=process_login_id)
            msg = "로그인창 연결"
    else:
        app = Application(backend="uia").connect(process=process_id)
        msg = "영웅문Global 연결"

    return app, msg

for i in range(10):
    try:
        # =========================================================
        # 로그인
        # =========================================================
        app, msg = check_hero_global_is_running()
        if msg.startswith("로그인창 실행"):
            process_login_id = find_process_id("nfstarter.exe")
            app = Application().connect(process=process_login_id)
            app_main = hero_global_login(app)
        elif msg.startswith("로그인창 연결"):
            app_main = hero_global_login(app)
        else:
            app_main = app

        main_window = app_main.window(title='영웅문Global')
        # main_window.maximize()
        time.sleep(1)
        # =========================================================

        # =========================================================
        # 영웅문Global 핸들링 설정
        # =========================================================
        # result 폴더 만들기
        if not os.path.exists("result"):
            os.mkdir("result")

        # 각 자식 컨트롤의 정보 출력
        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

            if menutoolbar and screen_area:
                break

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

        if menus:
            input_field = menus.children()[0].children()[0]
        # =========================================================
        
        # =========================================================
        # 모든 창 닫기
        # =========================================================
        all_windows_close(screen_area)
        # =========================================================
        
        # =========================================================
        # 실시간 잔고 기록
        # =========================================================
        # 2150 계좌정보
        input_field.set_focus()
        input_field.type_keys('2150')
        time.sleep(2)

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

        pane_control = find_controls_recursively_friendly(account_info_window, "Pane")[0]
        pywinauto.mouse.right_click(coords=(pane_control.rectangle().left + 50, pane_control.rectangle().bottom - 50))
        pywinauto.keyboard.send_keys('Z')
        print(pyperclip.paste())
        time.sleep(1)
        main_window.set_focus()
        # =========================================================

        # =========================================================
        # 종목별 현재가 실시간 조회
        # =========================================================
        # 2012 미니주문
        input_field.set_focus()
        input_field.type_keys('2102')
        time.sleep(2)

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

        if mini_order_window:
            user_meme_gubun = "매수"
            user_ticker_list = ['SOXL', 'TQQQ', 'FNGU', 'UPRO', 'NAIL']

            meme_gubun, idx, meme_button = check_meme_gubun()

            while True:
                if meme_gubun != user_meme_gubun:
                    if user_meme_gubun == "매수":
                        image_filename = 'images/mesu.png'
                    else:
                        image_filename = 'images/medo.png'

                    # 모니터가 여러개일 경우 이미지를 못 찾을 수 있음.
                    try:
                        menu_location = pyautogui.locateCenterOnScreen(image_filename, confidence=0.9)
                        if menu_location:
                            pyautogui.click(menu_location)
                            time.sleep(1)
                    except:
                        break
                # 체크
                temp_meme_gubun, _, _ = check_meme_gubun()
                if temp_meme_gubun == user_meme_gubun:
                    break

            while True:
                if user_meme_gubun == "매수":
                    image_filename = 'images/auto_mesu.png'
                else:
                    image_filename = 'images/auto_medo.png'
                try:
                    check_location = pyautogui.locateCenterOnScreen(image_filename, confidence=0.95)
                    button_controls = find_controls_recursively(mini_order_window, "Button")
                    if check_location:
                        for button in button_controls:
                            if "현재가" in button.window_text():
                                button.click()
                                time.sleep(0.5)
                                break
                except:
                    break

            while True:
                for user_ticker in user_ticker_list:
                    # 내가 입력한 티커가 잘 선택되었는지 확인 (티커를 잘 못 설정할 수 있으므로 10회만 반복)
                    for i in range(0, 10):
                        edit_controls = find_controls_recursively(mini_order_window, "Edit")
                        edit_controls[idx].type_keys(user_ticker, pause=0.3).type_keys('{ENTER}')
                        time.sleep(1.5)

                        print(f"{idx} 티커 : {edit_controls[idx].get_value()}, 가격 : {edit_controls[idx+2].get_value()}")
                        if edit_controls[idx].get_value() == user_ticker:
                            break
                        # FIXME: 종목, 가격 통한 매매 의사결정 (핵심 로직)
                # FIXME: 종료 조건

        # =========================================================
    except Exception as e:
        print(e)

 

결론

앞서서 작업했던 로직들을 잘 엮어서 반복적으로 가격을 가져오는 형태로 구성했습니다. 물론 이 상태가 자동 매매를 수행하기에는 부족한 점이 많지만 레고처럼 블록을 잘 엮으면 좋은 결과물을 만들어 낼 수 있다는 것을 보여드리고 싶었습니다. 그리고 이 단계까지 보여드리면 그 이후에는 여러분들의 머리 속에 있는 엄청난 매매 로직을 만들어 볼 수 있다고 생각했습니다.

최종 수행 내용

여기까지 내용을 직접 실행해 보셨나요? 부족한 점이 있거나 어려운 점이 있다면 언제든지 댓글, 이메일로 문의주세요. 문의를 주시는데 시간을 쏟으신 만큼 최선을 다해 답변드리겠습니다.

관심의 표현은 저에게 큰 힘이 됩니다. 😍 도움을 주셔서 정말 감사합니다.

반응형