32/64비트 파이썬 프로세스 통신

필자는 대신증권 API(파이썬)으로 이용하고 있는 사용자 중 하나이다. 실제 파이썬 코드를 이용해 자동으로 매수/매도하는 프로그램을 운영하고 있다. 주식장이 열리는 날이면 매일 HTS 와 파이썬 모듈을 구동시키고 출근한다.

수익률이 크지는 않지만 (계속 보완/테스트 진행 중) 그래도 포기할만큼은 아니고 간간히 매수/매도로 수익이 나면 기분도 좋아지곤 한다.

이제는 2차 버전을 생각하고 있다. 수익률 때문은 꼭 아니지만 계속 새로운 것을 시도하면서 학습하고자 하는 목적도 있다. 바로 강화 학습 기반의 트레이딩 시스템이 다음 목표이다.

32/64비트 파이썬 통신이 필요한 이유

문제는 강화학습(DQN)을 하기 위해서는 텐서플로우(tensorflow)가 필요한데 대신증권 파이썬 모듈하고 돌리려니 문제가 발생했다. 텐서플로우는 64비트 환경에서 돌고 대신 증권은 32비트 환경에서만 구동해야 된다는 것이다. 텐서플로우를 32비트 환경에서 돌리는 것도 문제 해결은 될 수 있으나 32비트 환경에서 텐서플로우를 돌리기 위해서는 소스를 다운받아 컴파일해야 된다. 인터넷을 찾아보면 쉽지 않아 보인다. 한참을 좌절하고 있다가 우연히 IPC(Inter Process Communication)  관련 내용을 활용해보자라는 생각이 들었고 관련 자료를 찾아보기로 했다.

그러나 파이썬 기반 IPC 모듈이 딱히 마음에 드는 것이 없어 내 마음대로 필요한 내용을 구현해보고 사용해보고 있다. IPC도 아닌 fake IPC라고 스스로 명명해놓고 있다.

32/64비트 파이썬 기반 IPC 통신 방법

원리는 간단하다. 64비트 환경의 파이썬 모듈(예. 텐서플로우)에서 32비트 환경의 파이썬 모듈(대신증권 파이썬 모듈)을 호출한다. 그러면 대신증권의 파이썬 모듈에서 처리를 하고 나서 결과를 파일로 저장하고 이를 64비트 파이썬 모듈에서 읽어서 처리한다. 호출부 소스와 실행부 소스 일부는 아래와 같다.

호출부 (64비트 파이썬 환경)

# ...
from sytrap import fakeipc as fipc

sipc = fipc.SytrapFakeIpc()
p = sipc.call('get_item', itcode='A168330')  # get_item는 32비트 환경에서 구동
# ...

실행부 (32비트 파이썬 환경)

# ...
ret = syc.get_item(itcode)   
p.dump(ret, open(ippath, 'wb'))  # 처리 결과를 미리 약속된 경로에 저장
# ...

다만 호출할 때 64비트 환경에서 32비트 파이썬 실행기와 파이썬 모듈 경로를 정확히 제시해주어야 한다. 아래는 호출 방법의 예이다.

 <64비트 환경 python.exe> 64비트.py <32비트 환경 python.exe> 32비트.py

구현해야 할 양이 조금은 많아졌지만 (함수 옵션 처리, 파일 입출력 등) 드디어 64비트 텐서플로우를 이용해 32비트 대신증권 모듈을 호출할 수 있게 되어 마음껏 딥러닝 기반 트레이딩 시스템을 돌릴 수 있게 되었다. 수익률까지 좋아지길 기대하고 있다. ㅎ

저와 비슷한 고민을 하시는 분들에게 조금이나마 도움이 되길 바란다.

LDA 토픽 모델링

가끔 주식 관련 특징주 기사를 보면 주가에 영향을 줄만한 기사가 뜨는 경우가 있다. 물론 좋은 기사가 뜬다고 해서 주가가 항상 오르지는 않는다. 다만 테스트를 해보고 싶었다.

데이터 수집

먼저 기계 학습을 위해 관련 데이터 수집이 필요하다. 특정 사이트 URL를 선정해서 인터넷 기사와 주가와의 연관성 분석을 위해 5년간의 특징주 관련 기사를 수집했다. 그런 다음 기사가 뜬 시점의 주가와 2일 후 변동 주가를 기록하여 데이터를 수집하였다.

데이터 학습

데이터 학습은 LDA(Latent Dirichlet Allocation)라는 토픽 모델링 방법을 사용하였다. 신규 기사가 뜰 경우 LDA 토픽 벡터와 유사한 기사들을 검색하고 검색된 기사들의 주가 전후 변동값을 비교한 후, 신규 기사에 대한 주가 변동량으로 추정하는 방법을 이용하였다.

아래는 pseudo code


if new_article:
    tpv = lda_topic_vec(new_article)
    sims = find_similarity(tpv)
    ss = sort(sims, 10)  # get top 10 of similarity
    for article in ss:
        sumret += calc_change_rate(article)
    avgret = sumret/N
    if avgret > 0.02
        buy(ar)

LDA 란…

LDA는 잠재 디리클레 할당이라고 하는데, 문서에 잠재되어 있는 토픽(topic)을 이용해 분류(cluster)하는 방법론이다. 비지도 학습(unsupervised learning)의 일종이다. 나이브분류기(Naive Classifier)와 같은 예전 방법에는  문서를 포함하고 있는 단어들의 확률적인 분포로 문서를 분류했는데 LDA에서는 보이지 않은 토픽이라는 잠재(latent, hidden) 변수를 더 활용하는 점이 다르다. 단어는 직접적으로 확인 가능하지만 토픽은 추상적인 존재이기에 잠재라는 단어를 사용한다. 또 LDA는 디리클레라는 확률적인 분포를 가정하는 것이 특징이다. 반면 pLSA 와 같은 방법론은 확률적인 분포를 가정하지 않는다.

LDA 의 확률 분포는 다시 말해, 토픽 벡터의 요소가 양수이며 모든 요소를 더한 값이 1인 경우에 대해서 확률값이 정의되는 분포이다.

가령 토픽이 경제, 정치 2가지 뿐이라면 경제와 정치 2가지 토픽을 더해서 1이 되는 경우라 이해할 수 있다. 즉, 경제가 0.9, 정치가 0.1 정도의 토픽 비율의 문서는 가능한데, 경제 0.9, 정치 0.9 비율로 토픽이 구성된 문서는 사전 가정에서 배제하는 것이다.

위키에서 발췌한 아래 그림은 토픽 3개인 경우로 이해하면 되고 중앙이 큰 값을 가진다는 의미는 토픽이 어느정도 골고루 섞힌 문서가 많이 존재한다.(확률적으로 높은 값을 가짐)라는 의미로 해석할 수 있다.

(위키 그림 발췌)

 

LDA 토픽 모델링 결과, 주가에 긍적적인 기사 예

결과로만 보면, 신약 개발 관련 기업들의 기사 관련 내용들이 주가에 단기적으로 긍정적인 영향을 주는 것으로 파악되었다. 그래서 필자는 LDA 토픽 모델링 기법을 써서 특정 사이트를 모니터링 하다가 신약 개발 관련 기사들이 뜰 때는 자동으로 매수하도록 프로그램을 돌리기도 했다. 하지만 역시 소문에 팔고 기사에 팔라는 말이 맞는 듯…ㅎ

순환 신경망 (LSTM) 활용 주가 예측

시계열 데이터 예측에 좋은 성능을 낸다는 순환 신경망을 통해 제가 현재 보유하고 있는 내츄럴엔도텍 주식에 대한 예측을 해보고자 합니다. 내츄럴엔도텍은 한 때 백수오로 알려진 업체이며 최근 홈쇼핑 판매 개시로 많은 관심을 받고 있습니다.

 LSTM이란…

순환 신경망은 보통 RNN(Recurrent Neural Network)라고 하는데 그레디언트 소실(Gradient Vanishing Problem)을 해결하고자 메모리를 도입한 LSTM(Long-Short Term Memory)을 주로 이용합니다.

LSTM은 시계열 데이터 처리에 있어서는 ARIMA 등 기존 알고리즘보다 좋은 성능을 내는 것으로 알려져 있습니다. LSTM에 대한 자세한 내용은 여기를 참조하시기 바랍니다. 순환 신경망의 놀라운 효과 라는 안드레 카파시 블로그 글 (영문)도 참고하시기 바랍니다. 순환 신경망으로 세익스피어 글도 짓고 자바 프로그래밍도 합니다.

LSTM 소개에 대한 이미지 검색결과

기존 MLP 신경망은 은닉층의 노드값을 계산할 때 입력에 가중치를 곱해서 은닉층의 노드값(상태)값을 업데이트 합니다. 그러나 순환 신경망은 그와 달리 은닉층의 노드값을 계산할 때 이전 히든(hidden) 노드에 대한 상태값과 입력을 모두 활용합니다. (아래 수식 참조)

Alt text

 

 

이런 방법으로 은닉층 상태를 업데이트 하기에 시계열 데이터에서의 패턴 변화를 감지할 수 있게 됩니다. 또한 LSTM은 순환신경망에 메모리까지를 가지고 있어 과거 시계열 데이터의 패턴 변화를 더 잘 잡아내는 특징을 가지고 있습니다.

좀 더 자세히 설명을 드리면 LSTM은 메모리가 있으며 메모리 입출력 컨트롤을 위한 소자(논리적인 장치)가 있는데 게이트(gate)라고 합니다. 게이트에는 입력(input), 출력(output) 게이트 그리고 망각(forget) 게이트가 있습니다.

LSTM 게이트에 대한 이미지 검색결과

LSTM을 학습하는 방법은 조금 복잡합니다. 기본적으로는 BPTT (Backpropagation Through Time) 을 사용합니다. 알고리즘은 BP와 유사하지만 순환 신경망에서는 시계열 데이터를 다루다보니 그리고 은닉층이 이전 은닉층 상태값까지 고려하다보니 그로 인해 조금은 복잡합니다. 순환 신경망 그래프 안에 네트워크가 연결된 구조를 펼쳐(unfold) 오차에 준한만큼 역으로 따라가며 전파하여 가중치를 학습합니다. 자세한 내용은 밑바닥부터 시작하는 딥너링이라는 책을 한번 읽어보시면 좋을 듯 합니다. 또는 Graphically Determining Backpropagation Equations 블로그 자료를 참고하시길 바랍니다.

 학습 데이터

LSTM 학습에 사용한 데이터로는 아래와 같은 형태로 저장된 csv 포맷을 읽어서 처리했습니다(다운로드:  A168330) 미리 아래와 같은 포맷으로 데이터를 저장해두고 나서 학습한 것입니다. 그리고 간단히 종가(Close) 데이터만을 이용해서 학습했습니다.

Date Open High Low Close Volume
20170816 27250 32450 27100 31500 3341469
20170814 25700 26800 24400 26750 1384477
20170811 25300 25750 24050 24950 2019038

 케라스 활용 LSTM 구현

LSTM을 파이썬으로 돌리는 방법은 여러 가지가 있지만 많이 사용되는 케라스(Keras) 라이브러리를 이용했습니다. 케라스는 텐서플로우를 기반으로 쉽게 사용할 수 있도록 하기 위한 일종의 래핑(wrapping) 라이브러리 입니다.

import os
import pandas as pd
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt
import math
from sklearn.metrics import mean_squared_error

look_back = 1
def create_dataset(dataset, look_back=1):
    dataX, dataY = [], []
    for i in range(len(dataset)-look_back-1):
        a = dataset[i:(i + look_back)]
        dataX.append(a)
        dataY.append(dataset[i + look_back])
    return np.array(dataX), np.array(dataY)

# file loader
sydtpath = "D:sydt"
naturalEndoTekCode = "A168330"
fullpath = sydtpath + os.path.sep + naturalEndoTekCode + '.csv'
pandf = pd.read_csv(fullpath, index_col="Date")

# convert nparray
nparr = pandf['Close'].values[::-1]
nparr.astype('float32')
print(nparr)

# normalization
scaler = MinMaxScaler(feature_range=(0, 1))
nptf = scaler.fit_transform(nparr)

# split train, test
train_size = int(len(nptf) * 0.9)
test_size = len(nptf) - train_size
train, test = nptf[0:train_size], nptf[train_size:len(nptf)]
print(len(train), len(test))

# create dataset for learning
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)

# reshape input to be [samples, time steps, features]
trainX = np.reshape(trainX, (trainX.shape[0], 1, trainX.shape[1]))
testX = np.reshape(testX, (testX.shape[0], 1, testX.shape[1]))

# simple lstm network learning
model = Sequential()
model.add(LSTM(4, input_shape=(1, look_back)))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(trainX, trainY, epochs=100, batch_size=1, verbose=2)

# make prediction
testPredict = model.predict(testX)
testPredict = scaler.inverse_transform(testPredict)
testY = scaler.inverse_transform(testY)
testScore = math.sqrt(mean_squared_error(testY, testPredict))
print('Train Score: %.2f RMSE' % testScore)

# predict last value (or tomorrow?)
lastX = nptf[-1]
lastX = np.reshape(lastX, (1, 1, 1))
lastY = model.predict(lastX)
lastY = scaler.inverse_transform(lastY)
print('Predict the Close value of final day: %d' % lastY)  # 데이터 입력 마지막 다음날 종가 예측

# plot
plt.plot(testPredict)
plt.plot(testY)
plt.show()

결과 화면

아래 차트 오른쪽 부분이 내츄럴엔도텍이 최근(2017년 8월) 홈쇼핑 판매 개시로 주가가 오르고 있는 부분입니다. 실제 주가(파란색)와 예측 주가(노란색)로 출력되어 있습니다. 예측은 현재일 (1일)을 이용해서 다음날 (1일)을 예측합니다. 마지막 날 예측이 어긋나는게 아쉽습니다

stocksLSTM2

위 소스에 대해 문의가 있거나 틀린 점, 보완할 점이 있으면 아래 댓글 남겨주시면 감사하겠습니다. 그리고 위 소스 내용은 아래 사이트(Machine Learning Mastery) 자료를 참고해서 개발한 것입니다.
Machine Learning Mastery

참고

가치 투자 및 관련 종목 검색 (대신증권 API 기반 파이썬 활용)

가치 투자라는 것은 성장 가능성만을 보고 투자하지는 않는다. 전망을 신뢰하기 보다 객관적인 자료로 현재 상황을 판단하여 투자하는 방식이다. 필자는 성장 가능성을 주로 보긴 하지만 가치 투자에 근거하여 일부 종목을 매매해보고자 자료를 수집하고 정리해봅니다. 투자에 참고하시길 바랍니다.

가치 투자 정의

기업 가치의 산출에는 여러 가지 방법이 있지만 금융감독원의 ‘유가증권 인수업무에 관한 규정’에서 제시하는 식을 살펴보면 아래와 같다

본질가치 = (자산가치 * 2+ 수익가치 * 3) /5

여기서 자산 가치는 재무상태상의 자본 총계를 의미하며 부채를 뺀 순 자산가치를 의미한다.

자산가치 = 자산총계 – 부채총계 = 순자산가치 = 청산가치

그리고 수익 가치는 기업활동으로 이익으로 발생 가능한 가치이다.

자산가치와 수익가치를 기업의 주식수로 나누면 각각 BPS(=자산가치)와 EPS(=수익가치)로 표현된다.

파이썬 활용 종목 검색

시가총액 2000억 이상이며 1/PER(=기대수익률) > 0.12 이상 그리고 PBR < 1.0 이하이면서 자산가치가 현재 주가보다 높은 종목(가치투자에 따른 상승 기대 종목)을 검색하기 위해 파이썬 코드로 검색해보았다.

대신증권 API 활용, Python 3.6.1(x32)

# find valuable items
import win32com.client

# declare Cybosplus API
cpStockCode = win32com.client.Dispatch("CpUtil.CpStockCode")
cpStockMst = win32com.client.Dispatch("dscbo1.StockMst") # not CpDib.StockMst

# total num of stock items
itNum = cpStockCode.GetCount()
print(itNum)

for it in range(0, itNum):

   # get item info
   itCode = cpStockCode.GetData(0, it)
   itName = cpStockCode.GetData(1, it)

   # request info
   cpStockMst.SetInputValue(0, itCode)
   cpStockMst.BlockRequest()

   # get header values
   managedStatus = chr(cpStockMst.GetHeaderValue(66)) # 관리구분
   alarmInvestStatus = chr(cpStockMst.GetHeaderValue(67)) # 투자 경고 구분
   tradeStopStatus = chr(cpStockMst.GetHeaderValue(68)) # 거래 정지 구분
   unfaithfulNoticeStatus = chr(cpStockMst.GetHeaderValue(69)) # 불성실 공시 구분
   exchangeType = chr(cpStockMst.GetHeaderValue(45)) # 소속구분 '1' : 거래소, '5': 코스닥

   # filtering items
   if managedStatus == 'N' and alarmInvestStatus == '1' and 
         tradeStopStatus == 'N' and unfaithfulNoticeStatus == '0':

      # only to consider KOSPI or KOSDAQ
      if exchangeType == '1' or exchangeType == '5':

         currentPrice = int(cpStockMst.GetHeaderValue(11)) # current price (현재가)
         stockNum = int(cpStockMst.GetHeaderValue(31)) # 발행주식수
         marketCap = currentPrice * stockNum
         EPS = float(cpStockMst.GetHeaderValue(20)) # EPS
         BPS = float(cpStockMst.GetHeaderValue(70)) # BPS
         PER = float(cpStockMst.GetHeaderValue(28)) # PER

         if BPS != 0:
            PBR = float(currentPrice/BPS) # PBR calculation
         else:
            PBR = float('inf')

         # calc essencial value (본질 가치)
         assetValue = BPS
         profitValue = EPS / 0.01 # 10% 할인률 적용
         eValue = (assetValue * 2 + profitValue * 3) / 5

         if marketCap > 200000000000 and PER != 0 and 1 / PER > 0.12 and PBR < 1.0:
            if eValue > currentPrice:
               print(itName + "<" + itCode + ">")

검색 결과 (2017년 8월 13일)

대림산업<A000210>
한국타이어월드와이드<A000240>
기아차<A000270>
대원강업<A000430>
동아쏘시오홀딩스<A000640>
강남제비스코<A000860>
한화<A000880>
대한제분<A001130>
유진투자증권<A001200>
부국증권<A001270>
종근당홀딩스<A001630>
신영증권<A001720>
오리온홀딩스<A001800>
삼호<A001880>
KISCO홀딩스<A001940>
한국유리<A002000>
아세아<A002030>
쌍용양회<A003410>
대신증권<A003540>
현대제철<A004020>
세방<A004360>
대림씨엔에스<A004440>
현대차<A005380>
넥센<A005720>
풍산홀딩스<A005810>
에스엘<A005850>
동아타이어<A007340>
서연<A007860>
현대모비스<A012330>
동원개발<A013120>
유니드<A014830>
부산가스<A015350>
예스코<A015360>
한국전력<A015760>
SK가스<A018670>
유진기업<A023410>
기업은행<A024110>
원익홀딩스<A030530>
교보증권<A030610>
LG디스플레이<A034220>
평화정공<A043370>
세아홀딩스<A058650>
세이브존I&C<A067830>
현대백화점<A069960>
GS<A078930>
휠라코리아<A081660>
대한제강<A084010>
한화생명<A088350>
현대EP<A089470>
SK이노베이션<A096770>
메리츠금융지주<A138040>
BNK금융지주<A138930>
DGB금융지주<A139130>
JB금융지주<A175330>
아세아시멘트<A183190>
광주은행<A192530>
서연이화<A200880>
코라오홀딩스<A900140>
GRT<A900290>

참조

 

파이썬을 활용한 주가 회귀분석 예

파이썬을 활용하여 주가 회귀분석을 한 예를 정리해보고자 한다. 구글 주가 데이터를 가져와 3일봉(3일전 데이터), 2일봉(2일전), 1일봉을 활용해 주가를 예측하는 예이다. 상관관계가 없는 데이터간 회귀분석을 하는 것이 적당하지 않을 지 몰라고 데이터 분석을 위한 pandas와 주가 데이터 등을 쉽게 가져올 수 있는 quandl (https://www.quandl.com/)이라는 라이브러리를 이용한 단순한 예로만 이해하면 된다.

import numpy as np #벡터와 행렬 계산을 위한 함수 라이브러리
import pandas as pd #데이터 분석을 위한 라이브러리
import quandl, math #주가 데이터를 가져오기 위한 라이브러리
from sklearn import cross_validation #학습과 테스트를 위한 데이터 분리
from sklearn.linear_model import LinearRegression #회귀분석 라이브러리

#구글 주가 데이터를 가져와 Quandl데이터 셋 구성
#Quandl 데이터셋에는 일단위 시가, 종가, 고가, 저가, 거래량 등이 포함되어 있음
df = quandl.get('WIKI/GOOGL')

df['Close3'] = df['Close'].shift(3)
df['Close2'] = df['Close'].shift(2)
df['Close1'] = df['Close'].shift(1)
df.dropna(inplace=True)

X = np.array(df[['Close3', 'Close2', 'Close1']]) #회귀분석 라이브러리 활용을 위한 데이터셋 구성
y = np.array(df['Close']) #타겟  구성

X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size=0.2) #데이터 분리

clf = LinearRegression() #회귀 분석 객체 생성
clf.fit(X_train, y_train) #회귀 분석 학습

#만약 Close3: 812 ~Close1: 799일 경우 종가 예측 방법
clf.predict([[812, 813, 807]])

실제 회귀 분석을 해보면 구글 주식의 경우 회귀 계수 (파라미터)가 아래와 같다

array([3.31584798e-04, -4.98201976e-02,
1.03048347e+00])

참고로 구글의 종가(Close)에는 근접한 일봉(Close1 > Close2 > Close3)일수록 영향을 많이 받는 것을 알 수 있다