Qt Designer 로 xxx.ui 의 경우 Designer 툴로 편집하는 장점은 있으나 동적 UI 생성이 어려운 점이 있었습니다.

예를 들면 windows 를 확대하거나 축소 시를 고려하는 것이 어렵더군요.

선택 기준 Qt Designer 사용 코드 작성
빠른 UI 개발 ✅ 유리 ❌ 불리
UI 변경이 잦음 ❌ 불리 ✅ 유리
동적 UI 생성 ❌ 불리 ✅ 유리
초보자 접근성 ✅ 유리 ❌ 불리
버전 관리 편의성 ❌ 불리 ✅ 유리
복잡한 애니메이션 ❌ 불리 ✅ 유리

그래서 xxx.ui 를 코드 작성하는 것으로 변경하였습니다.

1) 코드 생성

xxx.ui 파일을 전체를 복사 후에 아래와 같은 ChatGPT 프롬프트를 사용하였습니다.

다음은 python Qt designer 로 만들어졌습니다.
이를 .ui 파일이 아닌 .py 파일로 수정해주세요.
accountBalance, accountStocks 는 우측  layout 으로 표시해주세요.

코드 생성 후 다음과 같이 추가적인 프롬프트를 사용하였습니다.

브라우저를 종료 후에 입력하지 말고 기존 프롬프트 창에 계속 입력하면 됩니다.

accountBalance 의 경우 table 이 2라인입니다. window 확대 시에 table 이 2 라인인 것을 고려하여 확대해주세요.
accountStocks 에 2행 3열의 table 은 보입니다. 그런데 나머지 부분이 횐색으로 표시됩니다. 2행 3열의 table 크기에 맞추어 횐색 부분의 크기를 줄여주세요.

 

전체 코드는 아래 git 에서 확인 할 수 있습니다.

git clone https://github.com/jbpark/JbTraderExample.git
cd JbTraderExample/jbtrader/ch5.12

 

2) 코드 실행

실행 결과 확대 축소 시에 정렬이 잘되는 것을 확인 할 수 있습니다.

Posted by 제이브레인
,

종목 코드만을 표시했더니 종목 코드가 정상적으로 입력했는지 구분이 어려웠습니다.

예를 들면 삼성전자 종목 코드를 입력해도 내가 정말 삼성전자 종목코드를 입력했는지는 명확하지 않았습니다.

그래서 종목 코드를 입력하면 종목 명칭을 업데이트 하는 기능을 추가하였습니다.

1) 코드 생성

ChatGPT 에 사용된 프롬프트는 다음과 같습니다.

python pyqt5 designer 로 guruma_one.ui 를 만들었습니다.

guruma_one.ui 에는 다음과 같은 콤포넌트들이 존재합니다.
name : accountComboBox, type : QComboBox
name : logTextEdit, type : QTextEdit
name : stockName , type : QLineEdit
name : stockCode , type : QLineEdit

다음과 같은 기능을 제공합니다.
1. 키움증권 API 를 연결하고 연결 후 API를 초기화하고 실 서버 연결 시 "실 서버 연결 성공" 이라고 메시지를 logTextEdit 에 출력하고 
모의 투자 서버 연결 시 "모의 투자 서버 연결 성공"이라고 메시지를 logTextEdit 에 출력
2. Kiwoom 계좌 정보를 accountComboBox 에 출력
3. stockCode 에 종목 코드를 입력하면 stockName 에 종목 이름을 입력


파이썬 코드로 작성해주세요. 주석도 자세히 달아주세요. 코드는 오류 테스트 후에 오류가 있으면 수정해주세요.

 

생성된 코드는 다음과 같으며 생성된 코드 중 일부 버그는 수정하였습니다.

아래 코드는 다음 위치에서 다운 받는 것이 가능합니다.

git clone https://github.com/jbpark/JbTraderExample.git
cd JbTraderExample/jbtrader/ch5.11

 

stock_name.py

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
from PyQt5.uic import loadUi
from PyQt5.QtCore import QEventLoop
from pykiwoom.kiwoom import Kiwoom


class KiwoomApp(QMainWindow):
    def __init__(self):
        super().__init__()
        loadUi("guruma_one.ui", self)  # UI 파일 로드

        self.kiwoom = Kiwoom()
        self.kiwoom.CommConnect()  # 키움 API 연결

        # 이벤트 처리 연결
        self.kiwoom.OnEventConnect = self.on_event_connect  # 연결 이벤트 핸들러

        self.stockCode.editingFinished.connect(self.update_stock_name)

    def on_event_connect(self, err_code):
        """
        키움 API 연결 이벤트 처리
        실서버와 모의서버에 따른 로그 메시지를 출력함
        """
        if err_code == 0:
            server_type = self.kiwoom.GetLoginInfo("GetServerGubun")
            if server_type == "1":
                self.logTextEdit.append("모의 투자 서버 연결 성공")
            else:
                self.logTextEdit.append("실 서버 연결 성공")

            # 계좌 정보 가져오기
            self.update_account_info()
        else:
            QMessageBox.critical(self, "오류", "키움 API 연결 실패")

    def update_account_info(self):
        """
        계좌 정보를 가져와서 accountComboBox에 출력
        """
        accounts = self.kiwoom.GetLoginInfo("ACCNO").split(';')
        self.accountComboBox.clear()
        self.accountComboBox.addItems([acc.strip() for acc in accounts if acc.strip()])

    def update_stock_name(self):
        """
        stockCode에 입력된 종목 코드에 따라 stockName을 자동으로 설정
        """
        code = self.stockCode.text().strip()
        if code:
            stock_name = self.kiwoom.GetMasterCodeName(code)
            self.stockName.setText(stock_name)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = KiwoomApp()
    window.show()
    sys.exit(app.exec_())

 

위 코드의 핵심은 다음과 같이 종목 코드로 종목명을 구하는 부분입니다.

stock_name = self.kiwoom.GetMasterCodeName(code)

 

Qt Designer 로 종목명도 추가해줍니다.

2) 코드 실행

위 코드를 실행 후에 종목 코드에 주식 종목 코드를 입력하면 아래와 같이 종목명이 출력됩니다.

종목명이 표시되므로 내가 어떤 종목 코드를 입력했는지 좀더 확실해져서 좋네요.

3) JbTrader 코드 통합

JbTrader 에 종목명 부분 통합 시에 변경된 부분입니다.

Class init 함수에 stockCode 가 edit 가 종료되면 stockName 을 업데이트하도록 이벤트 추가되어 있습니다.

stockName 은 readOnly 로 설정하고 background color  를 회색으로 변경하였습니다.

class StockTrader(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

...

        self.stockName.setReadOnly(True)
        self.stockName.setStyleSheet("background: lightgray; color: black;")

        self.stockCode.editingFinished.connect(self.update_stock_name)

    def update_stock_name(self):
        """
        stockCode에 입력된 종목 코드에 따라 stockName을 자동으로 설정
        """
        code = self.stockCode.text().strip()
        if code:
            stock_name = self.kiwoom.GetMasterCodeName(code)
            self.stockName.setText(stock_name)

 

Posted by 제이브레인
,

시장가로 매수하거나 지정가로 매수하려고 할 때 현재 가격이 표시되어 있지 않고 입력만 하도록 되어 있어서 잘못 입력할 수도 있고 가격을 다른 곳에서 확인 후에 입력해야 해서 많이 불편하네요.

그래서 매수/매도 가격에 현재가와 상하호가를 입력하여 직관적으로 선택하도록 기능추가하려고 합니다.

1) 코드 생성

ChatGPT 에 사용한 프롬프트입니다.

매수/매도 가격에 현재가를 기준으로 상하호가를 계산하여 입력하도록 했습니다.

그리고 5초에 한번 자동 업데이트하게 했고 만약 사용자 매수/매도 가격을 선택하면 자동 업데이트를 멈추게했습니다.

python pyqt5 designer 로 guruma_one.ui 를 만들었습니다.

guruma_one.ui 에는 다음과 같은 콤포넌트들이 존재합니다.
name : accountComboBox, type : QComboBox
name : logTextEdit, type : QTextEdit
name : stockCode , type : QLineEdit
name : orderType, type : QComboBox
name : buyPrice, type : QComboBox
name : buyAmount, type : QLineEdit
name : sellPrice, type : QComboBox
name : sellAmount, type : QLineEdit
name : buyButton, type : QPushButton

다음과 같은 기능을 제공합니다.
1. 키움증권 API 를 연결하고 연결 후 API를 초기화하고 실 서버 연결 시 "실 서버 연결 성공" 이라고 메시지를 logTextEdit 에 출력하고 
모의 투자 서버 연결 시 "모의 투자 서버 연결 성공"이라고 메시지를 logTextEdit 에 출력
2. Kiwoom 계좌 정보를 accountComboBox 에 출력
3. stockCode 에 종목 코드를 입력하면 호가 단위를 현재 종목코드의 현재가를 기준으로 계산
4. buyPrice 에 -20호가 부터 +20호가까지 추가합니다. 
QSpinBox 의 각 item 은 -+호가 : 가격을 표시하고 현재가격은 현재가 : 가격을 표시합니다.
기본값은 현재가를 선택합니다.
5. sellPrice 에도 buyPrice와 동일한 값을 선택합니다.
기본값은 +1호가를 선택합니다.
6. 5초에 한번씩 가격을 업데이트하고 만약 buyPrice 또는 sellPrice 의 가격을 선택하면 업데이트를 멈춥니다.

파이썬 코드로 작성해주세요. 주석도 자세히 달아주세요. 코드는 오류 테스트 후에 오류가 있으면 수정해주세요.

 

Qt Designer 에서 buyPrice, sellPrice 를 QLineEdit -> QComboBox 로 변경합니다.

2) 생성된 코드

생성된 코드이며 일부 버그는 수정하였습니다.

아래 코드는 다음 위치에서 다운 받는 것이 가능합니다.

git clone https://github.com/jbpark/JbTraderExample.git
cd JbTraderExample/jbtrader/ch5.10

price_update.py

import sys

from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.uic import loadUi
from pykiwoom.kiwoom import Kiwoom


def get_cell_value_or_error(df, row_idx, col_name):
    try:
        return df.at[row_idx, col_name]  # 특정 위치의 값 반환
    except KeyError:
        raise ValueError(f"Invalid index '{row_idx}' or column '{col_name}'")


class GurumaApp(QMainWindow):
    def __init__(self):
        super().__init__()
        loadUi("guruma_one.ui", self)  # UI 파일 로드

        self.kiwoom = Kiwoom()  # Kiwoom 인스턴스 생성
        self.kiwoom.CommConnect()  # API 연결

        self.kiwoom.OnEventConnect = self.event_connect  # 연결 이벤트 핸들러

        # 버튼 클릭 이벤트 연결
        self.buyButton.clicked.connect(self.update_stock_price)

        # 주기적으로 가격 업데이트를 위한 타이머 설정
        self.price_update_timer = QTimer(self)
        self.price_update_timer.timeout.connect(self.update_stock_price)
        self.price_update_timer.start(5000)  # 5초마다 실행

        self.buyPrice.installEventFilter(self)
        self.sellPrice.installEventFilter(self)

    def eventFilter(self, obj, event):
        if (obj == self.buyPrice or obj == self.sellPrice) and event.type() == event.MouseButtonPress:
            self.on_combobox_clicked()
        return super().eventFilter(obj, event)

    def on_combobox_clicked(self):
        print("QComboBox가 클릭되었습니다!")  # 특정 함수 실행
        self.stop_price_update()

    def event_connect(self, err_code):
        """
        키움증권 API 연결 이벤트 처리
        """
        if err_code == 0:
            server_type = self.kiwoom.GetLoginInfo("GetServerGubun")
            if server_type == "1":
                self.logTextEdit.append("모의 투자 서버 연결 성공")
            else:
                self.logTextEdit.append("실 서버 연결 성공")
            self.load_accounts()
        else:
            self.logTextEdit.append("API 연결 실패")

    def load_accounts(self):
        """
        계좌 정보를 가져와서 accountComboBox에 출력
        """
        accounts = self.kiwoom.GetLoginInfo("ACCNO").split(';')
        self.accountComboBox.addItems([acc for acc in accounts if acc])

    def update_stock_price(self):
        """
        stockCode 입력 시 현재 가격 및 상하 몇 호가 가격을 buyPrice, sellPrice에 반영
        """
        stock_code = self.stockCode.text().strip()
        if not stock_code:
            self.logTextEdit.append("종목 코드를 입력하세요.")
            return

        self.kiwoom.SetInputValue("종목코드", stock_code)
        self.kiwoom.CommRqData("주식기본정보", "opt10001", 0, "0101")
        QTimer.singleShot(5000, self.process_stock_price)

    def get_price_tick(self, price):
        """
        현재가를 기반으로 호가 단위를 계산하는 함수
        """
        if price < 1000:
            return 1
        elif price < 5000:
            return 5
        elif price < 10000:
            return 10
        elif price < 50000:
            return 50
        elif price < 100000:
            return 100
        elif price < 500000:
            return 500
        else:
            return 1000

    def process_stock_price(self):
        """
        조회한 주식 정보를 buyPrice와 sellPrice에 반영
        """
        """
                조회한 주식 정보를 buyPrice와 sellPrice에 반영
                """

        stock_code = self.stockCode.text().strip()
        if not stock_code:
            self.logTextEdit.append("종목 코드를 입력하세요.")
            return

        df = self.kiwoom.block_request("opt10001",
                                       종목코드=stock_code,
                                       output="주식기본정보",
                                       next=0)

        current_price_str = get_cell_value_or_error(df, 0, '현재가')
        if not current_price_str:
            self.logTextEdit.append("현재가 정보를 가져올 수 없습니다.")
            return

        current_price_str = str(int(current_price_str))

        try:
            current_price = abs(int(current_price_str))
        except ValueError:
            self.logTextEdit.append("현재가 변환 오류: {current_price_str}")
            return

        price_tick = self.get_price_tick(current_price)

        self.buyPrice.clear()
        self.sellPrice.clear()

        for i in range(-20, 21):  # -20호가부터 +20호가까지 추가
            price = current_price + i * price_tick
            label = f"{i}호가: {price}" if i != 0 else f"현재가: {price}"
            self.buyPrice.addItem(label, price)
            self.sellPrice.addItem(label, price)

        # 기본값 설정: buyPrice는 현재가, sellPrice는 +1호가
        self.buyPrice.setCurrentIndex(20)
        self.sellPrice.setCurrentIndex(21)

        self.logTextEdit.append(f"현재가 {current_price}원부터 ±20호가까지 설정 완료")

    def stop_price_update(self):
        """
        사용자가 가격을 선택하면 타이머 정지
        """
        print("stop_price_update")
        self.price_update_timer.stop()
        self.logTextEdit.append("가격 선택 완료. 자동 업데이트 중지")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = GurumaApp()
    window.show()
    sys.exit(app.exec_())

3) 코드 실행

종목 코드에 삼성전자를 입력하고 실행 시 아래와 같이 매수 금액이 표시되는 것을 확인 할 수 있습니다.

매도 금액은 현재가 보다 1호가 위를 기본 선택하도록 했습니다.

 

4) JbTrader 에 가격 정보 업데이트 통합

위 가격 정보를 표시하는 기능을 JbTrader 에 통합시켰습니다.

통합된 코드는 다음 위치에 있습니다.

git clone https://github.com/jbpark/JbTraderExample.git
cat JbTraderExample/jbtrader/ch5.10/guruma_one.py

통합 후에 실행한 모습니다.

뭔가~ 그럴 듯한 모습을 갖춰가는 것 같지 않나요?

모의 계좌이긴한데 삼성전자를 사두었었는데 수익률이 1.32%네요.

실계좌도 빨리 수익이 났으면 좋겠어요.

Posted by 제이브레인
,