주식 자동 매매 API 환경 구축 2

Contents

0. 인공지능을 이용한 주식 자동 매매 코딩 연구
1. 한국투자증권 계좌, KIS Developers 서비스 신청, Python 설치, Visual Studio Code 설치
2. Visual Studio Code 설정, config.yaml 설정, KoreaStockAutoTrade.py 설정, 주식 종목 정리
3. Visual Studio Code 편집기 path 설정, 윈도우(Windows) 10 환경 변수 Path 설정, 주식 종목 발굴과 선택

주식 자동 매매 API 환경 구축 2

주식 자동 매매 API 환경 구축 2 : 주식 매매에서 전략이 70% 전술이 30%입니다. 전략은 종목 선택과 자금 운용 방법이고, 전술은 종목 수익률 결정, 손절 비율과 매도 결정이라 할 수 있습니다. 주식을 하면서 제일 힘든 부분이 “매도”로 수익 또는 손실의 실현입니다. 자동 매매를 적절히 사용하면서 개인의 장점을 잘 살리는 것이 인공지능 시대에 적응하고 경제적 자유를 얻는 방법이라 생각합니다. python 코딩을 이용해서 자동 매매를 하면서 매일 종목 List와 운용 자금 규모는 사용자가 정합니다. 매수 시점은 시가를 기준으로 어느 정도(0.01~3%) 상승 시로 정하며, 매도 시점은 시가를 기준으로 (2~10%) 상승 시로 정해줍니다. 장 종료 시 까지 프로그램을 작동 시키면 그날의 정리 매매가 자동으로 이루어지게 코딩 되어 있습니다. 프로그램을 종료 시키거나, Ctrl+C를 누르면 자동 매매 python API가 종료됩니다. 여러분의 투자 방법을 인공지능 코딩의 도움으로 조금씩 개선해 나간다면 훌륭한 매매 전문가로 만들 수 있습니다.

준우블로그 메뉴> 게시판 > 자료실에  기본 파일config.yaml과 KoreaStockAutoTrade.py이 있습니다.

MS의 챗GPT 4.0, 2024년 구글의 Gemini로 “python 코딩”을 해 보았습니다.

부족한 부분은 계속 보완해 나가며, 지속적인 학습과 관심으로 더 좋은 방법을 만들어 보려 합니다.

챗GPT와 Gemini의 코딩 기술을 이용해 python 코딩을 하고 이를 이용해 주식 자동 매매를 하려 합니다.

인공지능의 코딩(python 코딩) 기술이 전문가 이상으로 좋습니다.

종목 발굴과 당일 상승 종목 유망주를 인공지능이 분석하는 python 코딩도 만들어 보려 합니다.

2-1. 편집 도구 Visual Studio Code 설정

a. 자료실에 있는 python 구동 파일 2개를 사용자 PC의 C:\루트에 압축을 풀면 C:\python>에 config.yaml 과 KoreaStockAutoTrade.py이 생성됩니다.

b. 먼저 Visual Studio Code를 실행합니다.

c. Visual Studio Code 메뉴에서 File>Open Folder 에서 C:\python>를 선택합니다.

d. C:\python>폴더의 config.yaml을 엽니다.

e. 한국투자증권 API 서비스 신청 시 받은 App key, App secret 값을 설정합니다.
APP_KEY: “***”
APP_SECRET: “***”
본인의 계좌번호 앞 8자리를 입력합니다.
CANO: “********”

Ctrl+S key를 눌러 저장합니다.

f. Visual Studio Code 실행창에서 아래 붉은색 부분을 수정 후 Ctrl+S key로 저장합니다.

#홈페이지에서 API서비스 신청시 받은 Appkey, Appsecret 값 설정
APP_KEY: "TEST"
APP_SECRET: "TEST"

#계좌번호 앞 8자리
CANO: "TEST"
#계좌번호 뒤 2자리
ACNT_PRDT_CD: "01"

#실전투자
URL_BASE: "https://openapi.koreainvestment.com:9443"
#모의투자
# URL_BASE: "https://openapivts.koreainvestment.com:29443"

#디스코드 웹훅 URL
DISCORD_WEBHOOK_URL: ""

2-2. KoreaStockAutoTrade.py 설정

a. C:\python>폴더의 KoreaStockAutoTrade.py을 엽니다. 터미널 창에서 설치합니다.

b. 설치 안 된 import request는 C:\python>pip install requests로 설치합니다.

c. 설치 안 된 import yaml은 C:\python>pip install pyyaml로 설치합니다.
(맥 사용자는 C:\python>pip3 install *** 와 같이 pip3 명령어를 사용합니다.)

d. 코드 실행은 오른쪽 위 삼각형 ▷ 모양을 누르면 실행됩니다.

e. 자동 매매 종료는 터미널 창에서 Ctrl+C를 누르거나 프로그램을 종료합니다.

2-3. KoreaStockAutoTrade.py 코드 내용 살펴보기


import requests
import json
import datetime
import time
import yaml

with open('config.yaml', encoding='UTF-8') as f:
    _cfg = yaml.load(f, Loader=yaml.FullLoader)
APP_KEY = _cfg['APP_KEY']
APP_SECRET = _cfg['APP_SECRET']
ACCESS_TOKEN = ""
CANO = _cfg['CANO']
ACNT_PRDT_CD = _cfg['ACNT_PRDT_CD']
DISCORD_WEBHOOK_URL = _cfg['DISCORD_WEBHOOK_URL']
URL_BASE = _cfg['URL_BASE']

def send_message(msg):
    """디스코드 메세지 전송"""
    now = datetime.datetime.now()
    message = {"content": f"[{now.strftime('%Y-%m-%d %H:%M:%S')}] {str(msg)}"}
    requests.post(DISCORD_WEBHOOK_URL, data=message)
    print(message)

def get_access_token():
    """토큰 발급"""
    headers = {"content-type":"application/json"}
    body = {"grant_type":"client_credentials",
    "appkey":APP_KEY, 
    "appsecret":APP_SECRET}
    PATH = "oauth2/tokenP"
    URL = f"{URL_BASE}/{PATH}"
    res = requests.post(URL, headers=headers, data=json.dumps(body))
    ACCESS_TOKEN = res.json()["access_token"]
    return ACCESS_TOKEN
    
def hashkey(datas):
    """암호화"""
    PATH = "uapi/hashkey"
    URL = f"{URL_BASE}/{PATH}"
    headers = {
    'content-Type' : 'application/json',
    'appKey' : APP_KEY,
    'appSecret' : APP_SECRET,
    }
    res = requests.post(URL, headers=headers, data=json.dumps(datas))
    hashkey = res.json()["HASH"]
    return hashkey

def get_current_price(code="005930"):
    """현재가 조회"""
    PATH = "uapi/domestic-stock/v1/quotations/inquire-price"
    URL = f"{URL_BASE}/{PATH}"
    headers = {"Content-Type":"application/json", 
            "authorization": f"Bearer {ACCESS_TOKEN}",
            "appKey":APP_KEY,
            "appSecret":APP_SECRET,
            "tr_id":"FHKST01010100"}
    params = {
    "fid_cond_mrkt_div_code":"J",
    "fid_input_iscd":code,
    }
    res = requests.get(URL, headers=headers, params=params)
    return int(res.json()['output']['stck_prpr'])

def get_target_price(code="005930"):
    """변동성 돌파 전략으로 매수 목표가 조회"""
    PATH = "uapi/domestic-stock/v1/quotations/inquire-daily-price"
    URL = f"{URL_BASE}/{PATH}"
    headers = {"Content-Type":"application/json", 
        "authorization": f"Bearer {ACCESS_TOKEN}",
        "appKey":APP_KEY,
        "appSecret":APP_SECRET,
        "tr_id":"FHKST01010400"}
    params = {
    "fid_cond_mrkt_div_code":"J",
    "fid_input_iscd":code,
    "fid_org_adj_prc":"1",
    "fid_period_div_code":"D"
    }
    res = requests.get(URL, headers=headers, params=params)
    stck_oprc = int(res.json()['output'][0]['stck_oprc']) #오늘 시가
    stck_hgpr = int(res.json()['output'][1]['stck_hgpr']) #전일 고가
    stck_lwpr = int(res.json()['output'][1]['stck_lwpr']) #전일 저가
    target_price = stck_oprc + (stck_hgpr - stck_lwpr) * 0.1
    return target_price

def get_stock_balance():
    """주식 잔고조회"""
    PATH = "uapi/domestic-stock/v1/trading/inquire-balance"
    URL = f"{URL_BASE}/{PATH}"
    headers = {"Content-Type":"application/json", 
        "authorization":f"Bearer {ACCESS_TOKEN}",
        "appKey":APP_KEY,
        "appSecret":APP_SECRET,
        "tr_id":"TTTC8434R",
        "custtype":"P",
    }
    params = {
        "CANO": CANO,
        "ACNT_PRDT_CD": ACNT_PRDT_CD,
        "AFHR_FLPR_YN": "N",
        "OFL_YN": "",
        "INQR_DVSN": "02",
        "UNPR_DVSN": "01",
        "FUND_STTL_ICLD_YN": "N",
        "FNCG_AMT_AUTO_RDPT_YN": "N",
        "PRCS_DVSN": "01",
        "CTX_AREA_FK100": "",
        "CTX_AREA_NK100": ""
    }
    res = requests.get(URL, headers=headers, params=params)
    stock_list = res.json()['output1']
    evaluation = res.json()['output2']
    stock_dict = {}
    send_message(f"====주식 보유잔고====")
    for stock in stock_list:
        if int(stock['hldg_qty']) > 0:
            stock_dict[stock['pdno']] = stock['hldg_qty']
            send_message(f"{stock['prdt_name']}({stock['pdno']}): {stock['hldg_qty']}주")
            time.sleep(0.1)
    send_message(f"주식 평가 금액: {evaluation[0]['scts_evlu_amt']}원")
    time.sleep(0.1)
    send_message(f"평가 손익 합계: {evaluation[0]['evlu_pfls_smtl_amt']}원")
    time.sleep(0.1)
    send_message(f"총 평가 금액: {evaluation[0]['tot_evlu_amt']}원")
    time.sleep(0.1)
    send_message(f"=================")
    return stock_dict

def get_balance():
    """현금 잔고조회"""
    PATH = "uapi/domestic-stock/v1/trading/inquire-psbl-order"
    URL = f"{URL_BASE}/{PATH}"
    headers = {"Content-Type":"application/json", 
        "authorization":f"Bearer {ACCESS_TOKEN}",
        "appKey":APP_KEY,
        "appSecret":APP_SECRET,
        "tr_id":"TTTC8908R",
        "custtype":"P",
    }
    params = {
        "CANO": CANO,
        "ACNT_PRDT_CD": ACNT_PRDT_CD,
        "PDNO": "005930",
        "ORD_UNPR": "65500",
        "ORD_DVSN": "01",
        "CMA_EVLU_AMT_ICLD_YN": "Y",
        "OVRS_ICLD_YN": "Y"
    }
    res = requests.get(URL, headers=headers, params=params)
    cash = res.json()['output']['ord_psbl_cash']
    send_message(f"주문 가능 현금 잔고: {cash}원")
    return int(cash)

def buy(code="005930", qty="1"):
    """주식 시장가 매수"""  
    PATH = "uapi/domestic-stock/v1/trading/order-cash"
    URL = f"{URL_BASE}/{PATH}"
    data = {
        "CANO": CANO,
        "ACNT_PRDT_CD": ACNT_PRDT_CD,
        "PDNO": code,
        "ORD_DVSN": "01",
        "ORD_QTY": str(int(qty)),
        "ORD_UNPR": "0",
    }
    headers = {"Content-Type":"application/json", 
        "authorization":f"Bearer {ACCESS_TOKEN}",
        "appKey":APP_KEY,
        "appSecret":APP_SECRET,
        "tr_id":"TTTC0802U",
        "custtype":"P",
        "hashkey" : hashkey(data)
    }
    res = requests.post(URL, headers=headers, data=json.dumps(data))
    if res.json()['rt_cd'] == '0':
        send_message(f"[매수 성공]{str(res.json())}")
        return True
    else:
        send_message(f"[매수 실패]{str(res.json())}")
        return False

def sell(code="005930", qty="1"):
    """주식 시장가 매도"""
    PATH = "uapi/domestic-stock/v1/trading/order-cash"
    URL = f"{URL_BASE}/{PATH}"
    data = {
        "CANO": CANO,
        "ACNT_PRDT_CD": ACNT_PRDT_CD,
        "PDNO": code,
        "ORD_DVSN": "01",
        "ORD_QTY": qty,
        "ORD_UNPR": "0",
    }
    headers = {"Content-Type":"application/json", 
        "authorization":f"Bearer {ACCESS_TOKEN}",
        "appKey":APP_KEY,
        "appSecret":APP_SECRET,
        "tr_id":"TTTC0801U",
        "custtype":"P",
        "hashkey" : hashkey(data)
    }
    res = requests.post(URL, headers=headers, data=json.dumps(data))
    if res.json()['rt_cd'] == '0':
        send_message(f"[매도 성공]{str(res.json())}")
        return True
    else:
        send_message(f"[매도 실패]{str(res.json())}")
        return False

# 자동매매 시작
try:
    ACCESS_TOKEN = get_access_token()

    symbol_list = ["036540","001780","900250","252990","033230"] # 매수 희망 종목 리스트
    bought_list = [] # 매수 완료된 종목 리스트
    total_cash = get_balance() # 보유 현금 조회
    stock_dict = get_stock_balance() # 보유 주식 조회
    for sym in stock_dict.keys():
        bought_list.append(sym)
    target_buy_count = 3 # 매수할 종목 수
    buy_percent = 0.33 # 종목당 매수 금액 비율
    buy_amount = total_cash * buy_percent  # 종목별 주문 금액 계산
    soldout = False
    profit_percent = 0.03 # 매도 기준 수익률

    send_message("===국내 주식 자동매매 프로그램을 시작합니다===")
    while True:
        t_now = datetime.datetime.now()
        t_9 = t_now.replace(hour=9, minute=0, second=0, microsecond=0)
        t_start = t_now.replace(hour=9, minute=5, second=0, microsecond=0)
        t_sell = t_now.replace(hour=15, minute=15, second=0, microsecond=0)
        t_exit = t_now.replace(hour=15, minute=20, second=0,microsecond=0)
        today = datetime.datetime.today().weekday()
        if today == 5 or today == 6:  # 토요일이나 일요일이면 자동 종료
            send_message("주말이므로 프로그램을 종료합니다.")
            break
        if t_9 < t_now < t_start and soldout == False: # 잔여 수량 매도
            for sym, qty in stock_dict.items():
                sell(sym, qty)
            soldout == True
            bought_list = []
            stock_dict = get_stock_balance()
        if t_start < t_now < t_sell :  # AM 09:05 ~ PM 03:15 : 매수
            for sym in symbol_list:
                if len(bought_list) < target_buy_count:
                    if sym in bought_list:
                        continue
                    target_price = get_target_price(sym)
                    current_price = get_current_price(sym)
                    if target_price < current_price: buy_qty = 0 # 매수할 수량 초기화 buy_qty = int(buy_amount // current_price) if buy_qty > 0:
                            send_message(f"{sym} 목표가 달성({target_price} < {current_price}) 매수를 시도합니다.") result = buy(sym, buy_qty) if result: soldout = False bought_list.append(sym) get_stock_balance() elif current_price >= target_price * (1+profit_percent) and sym in bought_list:
                        sell_qty = int(stock_dict[sym] * 0.4)
                        sell(sym, sell_qty)
                        send_message(f"{sym} 수익률({current_price} / {target_price}) 달성 매도를 시도합니다.")            
                        get_stock_balance()
            time.sleep(1)
            if t_now.minute == 30 and t_now.second <= 5: 
                get_stock_balance()
                time.sleep(5)
        if t_sell < t_now < t_exit:  # PM 03:15 ~ PM 03:20 : 일괄 매도
            if soldout == False:
                stock_dict = get_stock_balance()
                for sym, qty in stock_dict.items():
                    sell(sym, qty)
                soldout = True
                bought_list = []
                time.sleep(1)
        if t_exit < t_now:  # PM 03:20 ~ :프로그램 종료
            send_message("프로그램을 종료합니다.")
            break
except Exception as e:
    send_message(f"[오류 발생]{e}")
    time.sleep(1)

빨간색으로 표시된 부분을 중심으로 설명합니다.

target_price = stck_oprc + (stck_hgpr - stck_lwpr) * 0.1

0.1의 값은 시가 보다 0.1% 상승 시 매수로 정한 값입니다.

profit_percent = 0.03 # 매도 기준 수익률

0.03의 값은 매수가 기준 3% 수익 돌파 시 매도로 정한 값입니다.

sell_qty = int(stock_dict[sym] * 0.4)

0.4의 값은 매수 값 0.1% + 매도 값 0.3% = 0.4% 값입니다.

위 세 가지 값은 매일 종목의 예상 변동 폭에 따라 장 시작 전 오전 8시경 정합니다.

값 변경 후 Ctrl+S로 저장한 후 실행 시켜 봅니다.

테스트 후 Ctrl+C로 프로그램을 중지합니다.

2-4. 주식 종목 정리 방법

symbol_list = ["036540","001780","900250","252990","033230"] # 매수 희망 종목 리스트

종목 리스트는 엑셀이나 메모장에 정리해서 매매 당일 오전 8시경 수정합니다.

“900250”,”027580″,”033230″,”036540″,”252990″,”001780″

크리스탈신소재 900250
상보 027580
인성정보 033230
SFA반도체 036540
샘씨엔에스 252990
알루코 001780

target_buy_count = 3 # 매수할 종목 수

당일 매매할 종목의 수를 나타내며 전략에 따라서 1~200정도로 합니다.

buy_percent = 0.33 # 종목 당 매수 금액 비율

매매 종목당 비율로 100만 원 운용 시 종목당 33만 원이 정해집니다.

주식 자동 매매 API 환경 구축 3

Leave a Comment