2. 해외선물/2-4. 해외선물 API (사용)

(키움증권 해외선물 OpenAPI-W) 스토캐스틱(Stochastic) 값 구하기 (4) 스토캐스틱 오실레이터(Stochastic Oscilla

봄이오네 2023. 10. 4. 08:02
반응형
목 차
1. 들어가며
2. 사전설명
   1) 산출식
      ① 오실레이터 패스트 %D 구하기
      ② 오실레이터 슬로우 %D 구하기
   2) 오실레이터 계산하기(엑셀)
      ① fast %k(12분) 구하기
      ② fast %d(26분)
      ③ slow %k(26분)
3. 코드 설명
4. 전체코드
   1) Stochastic Oscillator 관련 코드
   2) Stochastic 전체코드
5. 마치며

 

1. 들어가며

지난 글에서는 스토캐스틱 Slow %F, %D에 대해 알아보았다. Fast %F, %D를 구할 때 어떻게 결과값을 얻을지 고민을 해봐서인지, Slow %F, %D 값은 수월하게 그 값을 구할 수 있었다. 키움측 자체 Slow %F를 구하는게 약간 힘들었지만, 그래도 Fast 값 얻을 때의 막막함은 없었다.
 
이번 글에서는 스토캐스틱 관련 마지막 글이 될 것 같다. 스토캐스틱 오실레이터에 알아보자.


2. 사전설명

1) 산출식

스토캐스틱 오실레이터는 아래와 같이 구할 수 있다.

  • 스토캐스틱 오실레이터 : 패스트 %D - 슬로우 %D

< 그림1-2 >를 먼저 확인해 보면, 스토캐스틱 오실레이터의 기간은 sto1 = 12분, sto2 = 26분, sto3 = 9분으로 구성된다.

  • 오실레이터 패스트 %D = eavg[(stc_fast_%k, 12분), 26분] → fast_%k(12분)을 구하고, 26분으로 지수이동평균한 값
  • 오실레이터 슬로우 %D = eavg[(stc_slow_%k, 12분, 26분), 9분] → 키움자체 slow_%k(12분)을 구하고, 26분에 분자와 분모를 각각 합산한 후, 9분에 지수이동평균 값을 구함

 
개념이 어렵게 느껴질 때는 하나씩 쪼개서 생각해보자.
 
① 오실레이터 패스트 %D 구하기
오실레이터 패스트 %D는 fast %k(12분)을 먼저 구하고, 각각의 fast %k(12분)에 대해 26분을 가중하여 지수이동평균을 하면 된다. @.@ 그래도 어렵다. 어쨋든 먼저 구해야 하는 것은 fast %k(12분)이다.
 
② 오실레이터 슬로우 %D 구하기
오실레이터 슬로우 %D는 slow_%k는 키움 자체 slow_%k(26분)의 값이다. 이 값의 분자(각각의 현재값 - 12분중 최소값)와 분모(12분중 최고값 - 12분중 최소값)을 구하여, slow_%k(26분) = 26개의 sum(각각의 분자) / sum(각각의 분모) * 100을 해준다. 그러고 나서 slow_%D(9분) = eavg(slow_%k(26분)), 즉 slow_%k(26분)의 9개를 지수이동평균하여 값을 구해준다.
 

그림1-1. 스토캐스틱 오실레이터의 수식

 
스토캐스틱 오실레이터 기간은 sto1 = 12분, sto2 = 26분, sto3 = 9분으로 구성된다.

그림1-2. 스토캐스틱 오실레이터의 기간설정(12분, 26분, 9분)

 

2) 오실레이터 계산하기(엑셀)

오실레이터 계산이 어렵게 느껴지는건 어쩔 수 없다. 이럴 땐, 엑셀로 계산해보면 빠르다.

해외선물(미니 나스닥) (230929, 금) (NQZ23).xlsx
0.19MB

 
차근차근 엑셀로 계산해보자. < 그림1-3 >은 오실레이터가 계산된 값이다.
 
① fast %k(12분) 구하기
여기서 fast %k(12분)을 구해보자. fast %k를 구하는 방법이 기억이 안난다.ㅎㅎㅎ

  • 스토캐스틱 패스트 %K : (현재가격 - n분(일) 중 최저가) / ( n분(일) 중 최고가 - n분(일) 중 최저가) * 100

필자도 기억이 안나서, 앞의 글을 찾아보았다. < 그림1-3 >의 V13열에 계산된 41.51은 아래와 같이 계산되었다. 엑셀에서 찾오보면 알겠지만, 현재가는 14953.25, min(E2 : E13) = 14947.75이며, max(D2 : D13) = 14961이다.
41.51 = (F13열 - min(E2 : E13)) / (max(D2 : D13) - min(E2 : E13)) * 100
          = (14953.25 - 14947.75) / (14961 - 14947.75) * 100 = (5.5) / (13.25) * 100
 
② fast %d(26분)
< 그림1-3 >에서 삭제(W열13줄~W열37줄)하기 했지만, 지수이동평균을 해주면 된다.
엑셀 W38줄 = 3.05 *  (2 / (26+1)) + 0 * (1- 2/(26+1)) = 0.23
 
물론 현재 W열38줄 값은 0.23이다. 결론은 W601줄의 숫자는 37.18이다.
 
③ slow %k(26분)
키움자체 slow %k(26분)를 구하기 위해서는 분자와 분모를 구해야 한다.
X13줄(분자)의 5.5는 현재가 - MIN(E2 : E13)이고, Y13줄(분모)의 13.25는  MAX(E2 : E13) - MIN(E2 : E13)이다. 그 아래에는 동일한 엑셀 함수를 적용받는다.
 
slow %k(26분)은 Z38줄의 값(17.8)을 의미한다.
Z38줄(17.8) = SUM(X13:X38)/SUM(Y13:Y38)*100으로 계산된다.
 

그림1-3. 오실레이터 계산하기(엑셀)

 
slow %d(9분)은 값은 아래와 같이 계산된다.
slow %d(9분) 의 AA46줄(5.85) = Z46*(2/10)+AA45*(1-2/10)이다.  (그림1-4에서 AA45줄에는 아무것도 없으므로 0이다)
 
스토캐스트 오실레이터 = fast %d(26분) - slow %d(9분) = W46줄(24.24) - AA46줄(5.85) = 18.39

그림1-4. 스토캐스틱 오실레이터 구하는 방법

3. 코드 설명

라이브러리 및 로그인 등 이전 글에서 설명한 내용은 생략하도록 한다.
 

그림2-1. 활용된 라이브러리 및 로그인, 변수 선언 현황

1줄~18줄 : 라이브러리 및 로그인 관련 내용은 여기에서 생략한다.
20줄~31줄 : Stochastic Fast, Slow, Oscillator 등의 결과값을 얻기 위해 변수를 미리 선언한다.
 

그림2-2. 지수이동평균을 위한 가중치 및 로그인, 1분봉 데이터 조회 현황

 
33줄~36줄 : Stochastic Fast, Slow, Oscillator 등에서 활용될 "가중치"를 미리 선언한다.
43줄~62줄 : 로그인 및 1분봉 조회함수(50줄~62줄)에 대한 내용이며, 설명을 생략한다.
64줄 : 66줄의 def stochastic_searching(self): 함수를 실행한다.
 

그림2-3. Stochastic Oscillator의 Fast %D를 구하는 내용

66줄부터 상세히 설명할 것이다.
66줄 : 오실레이터 결과값을 만들기 위해 함수를 선언한다. 오실레이터는 "Fast %D - Slow %D"의 결과값이다.
 
69줄~92줄 : Fast %D는 Fast %K(12분)을 통해 얻을 수 있다. 아래 n분(일)에 12를 넣어서 계산하면 Fast %K 값이 된다.

  • 스토캐스틱 패스트 %K : (현재가격 - n분(일) 중 최저가) / ( n분(일) 중 최고가 - n분(일) 중 최저가) * 100
  • 스토캐스틱 패스트 %D : 패스트 %K를 m분(일) 로 지수이동평균한 값

70줄~76줄 : 이 글의 첫번째 핵심이다. 70줄을 생각해보자. 69줄의 0이 70줄의 i에 들어가면, 70줄은 for j in range(0, 12)가 된다. for j in range(0, 12) 의미는 0은 현재시간을 뜻하고, 12는 (현재시간-11분)을 뜻한다. 즉 "현재시간 ~ (현재시간-11분)" 내에서 최대값/최소값(72줄~73줄)을 찾아서 리스트(74줄~75줄)에 넣어달라는 이야기이다. @.@
 
78줄~80줄 : i=0 일때, 현재가격은 78줄에 넣어주고, "현재~(현재시간-11분)"의 데이터 중 최대값은 79줄, 최소값을 80줄에 넣는다.
82줄~83줄 : 20줄~21줄에서 미리 선언한 리스트 내에서 최대값/최소값을 구했으므로, 리스트를 초기화 시킨다.
85줄~86줄 : i=0 일때, Fast %K(12분)으로 계산(85줄)한다. 소수 셋째자리에서 반올림(86줄)한다.
88줄 : Fast %D를 구하기 위해 85줄에서 계산된 Fast %K(12분)를 23줄에서 선언한 리스트(self.stc_fast_d_list)에 추가(append)한다.
 
88줄에서 69줄(for i in range(0,150))의 첫번째 i=0일때 계산된 값( Fast %K(12분, i=0))이 산출되었다. 두번째로 i=1일때 Fast %K(12분)값을 구하기 위해 70줄~88줄이 실행되어  Fast %K(12분, i=1)이 계산되면, 88줄(self.stc_fast_d_list) 리스트에 추가된다. 언제까지 실행될 것인가? i=149일때까지 계속 69줄의 for문이 실행된다.
 
69줄의 변수 j가 0~149가 순차적으로 들어가는건 150번이 실행되면 88줄의 리스트(self.stc_fast_d_list)에 포함되는 Fast %K 결과값 갯수가 150개가 된다.
 
90줄 : 88줄의 리스트(self.stc_fast_d_list) 리스트에는 i=0(현재 시간) ~ i=149(현재시간-150분)으로 시간 역순(최근 → 과거)으로 데이터가 들어가 있다. 지수이동평균을 구하기 위해 리스트를 정배열 순(과거 → 최근)으로 변경해준다.
 
92줄 : 88줄(self.stc_fast_d_list)는 역순으로 바꾸어주고, 더이상 활용하지 않으므로 초기화 해준다.
 
94줄~99줄 : 이글의 두번째 핵심이다. 패스트 %D를 구하기 위해서는 패스트 %K를 지수이동평균해 주어야 한다. < 그림1-1 >에서 패스트 %D는 12분, 26분이 적용되는 것을 확인하였다. 패스트 %K에 12분을 적용하였다. 그렇다면 패스트 %D에는 26분을 적용(= 패스트%K 결과값에 가중치(26)을 적용하여 패스트%D 1개를 산출)한다는 것을 알 수 있다. 가중치(26)은 이미 35줄(self.stc_oscillator_d_weighting)에서 가중치(2/(26+1))를 정의하였다.

  • 스토캐스틱 패스트 %D : 패스트 %K를 m분(일) 로 지수이동평균한 값

 
94줄 : 90줄에서 정배열한 self.stc_fast_d_list_final에 len을 붙이면, 정수 150(int형)이 된다. len(리스트)는 "리스트 내 항목 갯수"를 카운팅한다.
95줄~96줄 : i=0 일 때, 지수이동평균시 첫번째  숫자는 그 값 자체가 지수이동평균이다.
97줄~98줄 : i=1 일 때, 지수이동평균 = [(최근값 * 가중치) + (이전 지수평균값 * (1-가중치))] * 100로 계산된다.
99줄 : 다음값(i=2)을 계산할 때는 최근값(i=2일때)에는 가중치를 곱하고, "이전 지수평균(i=1)"에는 (1-가중치)를 곱한다.
 * 95줄~99줄의 for문은 i=149일 때까지 실행된다.
101줄 : 리스트를 초기화 해준다.
103줄 : Stochastic Oscillator Fast %를 구한다. (소수 셋째자리에서 반올림한다)
 

그림2-4. Stochastic Oscillator의 Slow %D를 구하는 내용

 
106줄~166줄 : Stochastic Oscillator을 구하기 위해 Slow %D(12, 26, 9)를 계산한다.
 
※ 먼저는 3중 for문에 대해 설명한다. Slow %D를 구하기 위해서는 Slow %K를 구해야 하며, Slow %K를 구하기 위해서는 현재가격에서의 분자와 분모를 26개씩을 구해야 한다. 어렵다. @.@
 
107줄~109줄 : 3중 for문이다. (가장 안쪽에 있는 for문인 109줄 → 107줄 순서로 설명한다)
109줄 : Slow %D를 구하기 위해서는 Slow %K의 분자(12분)와 분모(12분)를 구한다. 분자는 (현재값 - 12분중 최소값)이고, 분모는 (12분중 최대값 - 12분중 최소값)을 각각의 현재값을 기준으로 구해준다.
108줄 : 리스트에 109줄의 결과값을 각각 담아서 키움자체 산식으로 Slow %K(26개의 각각의 분자와 분모를 더해준다)을 구해준다. 이전 글에서도 설명하였지만, Slow %K는 키움자체 산식을 적용한다. (키움 자체 산식 = sum(각각의 분자) / sum(각각의 분모) * 100)

  • 스토캐스틱 슬로우 %K : 패스트 %D를 m분(일)로 이동평균한값 → 키움증권식 계산한 값
  • 스토캐스틱 슬로우 %D : 슬로우 %K를 m분(일)로 지수이동평균한 값

107줄 : 108줄에서 계산된 슬로우 %K 값에 대해 지수이동평균(9분, 가중치 9)하여 Slow %D를 구해주기 위해 가장 바깥쪽의 for문을 돌려준다.
 

※ for문에서 i가 가리키는 시간
< 그림1-1 >에서 Slow %D는 (12, 26, 9) 기간을 가진다. 구체적인 시간을 예로 들어보자.
키움증권에서 자료를 시간 역순으로 가지고 온다. 간단한 설명을 위해 현재시간을 10:30분이라고 생각하자.
i=0이면 10:30분을 가리키고, i=1이면 10:29분을 뜻하며, 
i=5이면 10:25분을 가리키고, i=11이면 10:19분을 의미한다.
→ 리스트에서 담겨진 최초 자료는 이렇듯 시간의 역순으로 자료가 담겨 있다.
     ex) aaa = [1030의 현재값, 1029의 현재값, 1028의 현재값, ... , 1019의 현재값]

지수이동평균을 구하기 위해 역순을 정배열로 변경한다. aaa = aaa[::-1]으로 시간을 정배열 한다.
     ex) aaa = [1019의 현재값, 1020의 현재값, 1019의 현재값, ... , 1030의 현재값]

1019의 현재값과 1020의 현재값을 지수이동평균하여 결과값을 얻는다.
그러고나서 1020의 현재값과 1021의 현재값을 지수이동평균한다.

 
109줄~115줄 : Slow %D(12, 26, 9)에서 "12"는 "현재 ~ (현재-11분)"까지의 최고값, 최저값, 현재가격의 세가지 값을 얻을 수 있다. 즉, 12는 "현재 ~ 현재-11분"을 뜻한다.
 ※ 만약, 109줄~115줄의 i=0일 때 계산이 끝났으면, 108줄 i=1이 109줄~115줄에 들어가서, j는 "1~13"을 뜻한다. 1~13은 0이 현재시간을 말하므로, 1은 "현재시간-1분"을 말하고, 13은 "현재시간-12분"을 말한다.
 
 ※ 시간 역순으로 계산하니 상당히 헷갈릴 것이다. 처음부터 자료를 정배열로 받아오면, 이런 고민도 없을거 같은데... 자료를 어떤 시간순으로 받아올지 약간의 고민은 필요해 보인다.
 
117줄~119줄 : 109줄~115줄에서 받아온 현재가, 최고가, 최저가를 리스트에 담는다.
121줄~122줄 : 114줄~115줄에서 최대값/최소값을 구했으므로, 리스트를 초기화 한다.
124줄~125줄 : 124줄에서 분자(현재가-12일중 최저가), 125줄에서 분모(12일중 최고가-12일중 최저가)를 구한다.
 
127줄 : 124줄에서 구한 각각의 분자를 127줄의 리스트(38줄에서 선언)에 담는다.
128줄 : 125줄에서 구한 각각의 분모를 128줄의 리스트(39줄에서 선언)에 담는다.
 
130줄~131줄 : 127줄(각각의 분자) 및 128줄(각각의 분모)의 합계를 각각 구한다.
133~134줄 : 127줄과 128줄에서 사용한 리스트를 초기화 한다.
 

그림2-5. Stochastic Oscillator의 Slow %D를 구하기 위해 지수이동평균을 구한다.

 
136줄~137줄 : Fast %K(26분)를 구하기 위해 130줄, 131줄에서 합계된 결과값을 리스트에 담는다.
139줄~140줄 : 시간 역순(현재 → 과거)의 순서로 리스트가 들어 있으므로, 정배열(과거→현재)로 바꾸어준다.
142줄~143줄 : 139줄(분자) 및 140줄(분모) 리스트 내의 항목들을 합친다.
145줄~146줄 : 리스트를 초기화 한다.
 
148줄~149줄 : Slow %K를 여기서 구한다. 소수 셋째자리에서 반올림(149줄)한다.
※ 148줄에서 현재시간에 해당하는 Slow %K를 구했다(하나 구하려고... 이렇게 긴 코드가 나온다)
이제 1분전의 Slow %K를 구해야 한다. 107줄의 for문으로 돌아가서 i=1, i=2....i=99까지 실행된다. ㅠㅠ

  • 키움자체 Slow %K 계산법 = sum(현재가-최저가) / sum(최고가-최저가) * 100

153줄 : 107줄의 for문이 i=99까지 실행되어 리스트에 담겨있다면, 그 내용은 시간 역순일 것이다. 정배열(과거→현재)로 바꾸어준다.
 
155줄~160줄 : Slow %D를 구하기 위해 각각의 Slow %K를 담은 리스트(151줄)의 항목들 간 지수이동평균 값을 구한다. < < 그림1-2 >에서 Slow %D의 기간을 확인하였다.  Slow %D(12, 26, 9)이다. Slow %D(9)는 Slow %K 9개를 지수이동평균하라는 의미이다. 9개를 지수이동평균하라니??? 가중치는 2/(n+1)이다. 9개의 지수이동평균은 2/(9+1) = 2/10로 결정된다. (36줄, self.stc_oscillator_d_9_weighting에서 이미 가중치를 정의하였다)
 
아래 < 접은 글>은 지수이동평균에 대해 설명한 내용이다.
 

더보기

① Slow %D(9)를 더 자세히 이야기해 보면, 현재시간이 10:00라고 하고, Slow %D(9)를 구하면, 10:00~10:08분까지의 각각의 값을 지수이동평균하라는 의미이다.

 

② 한가지 더! 155줄에서 for문의 숫자는 100이다. 필자가 임의로 정한 값이다. 지수이동평균은 그 이전 값의 영향을 받을 수 밖에 없다. 100개 정도 설정하니, 현재 값이 키움증권의 Slow %D(9)와 일치하였다.

 

예를 들어보자. 현재 시간이 11:40분이라고 하면, 과거 100분은 몇시일까? 10:01분으로 생각된다.

Slow %D(9)를 for문으로 100회 계산한다면,

10:01~10:09분의 값을 계산(a)하고, 10:02~10:10분의 값(b)을 누적 계산..... 11:32~11:40분의 결과값이 누적 계산된다. (누적 계산된다는 것은 이전 계산값 a가 b에 영향을 미친다는 이야기이다)

 

※ 지수이동평균 = [(최근값 * 가중치) + (이전값 * (1-가중치)] / 100

 

그림2-6.Stochastic Oscillator를 구한다.

 
169줄 : Stochastic Oscillator = Fast %D - Slow %D로 계산된다.
177줄 : 50줄(rq_data_opc10002) 함수를 실행한다.
 


4. 전체코드

아래 코드를 실행하기 위해서는 키움증권에 시세 이용료(월 $185)를 지불하여야 한다.
 

1) Stochastic Oscillator 관련 코드

Stochastic Oscillator(본문)의 코드는 아래와 같다.
 

더보기
import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class btl_system():
    def __init__(self):
        self.kiwoom = QAxWidget("KFOPENAPI.KFOpenAPICtrl.1")
        print("로그인 시작!")

        self.kiwoom.dynamicCall("CommConnect(1)")  # CommConnect() : 괄호 안에 자동(1)을 넣는다.
        self.kiwoom.OnEventConnect.connect(self.login_Connect)
        self.kiwoom.OnReceiveTrData.connect(self.trdata_get)

        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec_()

        self.kiwoom.dynamicCall("GetCommonFunc(QString, QString)", "ShowAccountWindow", "")  # 계좌번호 입력창을 띄우는 내부함수

        self.nq_max_price_list = []
        self.nq_min_price_list = []

        self.stc_fast_d_list = []

        self.stc_slow_k_list_close_min = []
        self.stc_slow_k_list_max_min = []

        self.stc_slow_d_list_row = []
        self.stc_slow_d_list = []

        self.stc_slow_k_forth_list = []

        self.stc_fast_k_weighting = 2 / (3 + 1)
        self.stc_slow_d_weighting = 2 / (5 + 1)
        self.stc_oscillator_d_weighting = 2 / (26+1)
        self.stc_oscillator_d_9_weighting = 2 / (9+1)

        self.stc_slow_oscillator_k_26_close_min_list = []
        self.stc_slow_oscillator_k_26_max_min_list = []

        self.stc_slow_oscillator_final = []

    def login_Connect(self, err_code):
        if err_code == 0:
            print('로그인 성공했습니다!')
        else:
            print('로그인 실패했습니다!')
        self.login_event_loop.exit()

    ########## 키움서버에 TR 요청하는 함수 모음 ##########
    def rq_data_opc10002(self, stock_code_num, time_unit):  # 1분봉 데이터 조회
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목코드", stock_code_num)
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "시간단위", time_unit)
        self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opc_10002", "opc10002", "", "1002")
        self.tr_event_loop = QEventLoop()
        self.tr_event_loop.exec_()

    ########## OnReceiveTrData을 통해 수신받은 데이터 함수  ##########
    def trdata_get(self, scrno, rqname, trcode, recordname, prenext):
        if rqname == "opc_10002":

            self.tr_event_loop.exit()

            btl.stochastic_searching()

    def stochastic_searching(self):
        ########## 스토캐스틱 오실레이터 ##########
        ### 오실레이터_fast_%D 구하기 ###
        for i in range(0, 150):  # 현재 분의 STC_오실레이터 구하기
            for j in range(i, i + 12):
                self.close_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "현재가").strip()))
                max_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "고가").strip()))
                min_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "저가").strip()))

                self.nq_max_price_list.append(max_price_row_data)
                self.nq_min_price_list.append(min_price_row_data)

            self.close_price_first = self.close_price_row_data
            self.max_price_first = max(self.nq_max_price_list)
            self.min_price_first = min(self.nq_min_price_list)

            self.nq_max_price_list = []
            self.nq_min_price_list = []

            self.stc_fast_k_first = (self.close_price_first - self.min_price_first) / (self.max_price_first - self.min_price_first) * 100
            self.stc_fast_k_first = round(self.stc_fast_k_first, 2)  # 소수 셋째자리에서 반올림

            self.stc_fast_d_list.append(self.stc_fast_k_first)

        self.stc_fast_d_list_final = self.stc_fast_d_list[::-1]

        self.stc_fast_d_list = []

        for i in range(len(self.stc_fast_d_list_final)):
            if i == 0:
                self.stc_oscillator_d_first = self.stc_fast_d_list_final[i]
            elif i >= 1:
                self.stc_oscillator_d_second = (self.stc_fast_d_list_final[i] * self.stc_oscillator_d_weighting) + (self.stc_oscillator_d_first * (1 - self.stc_oscillator_d_weighting))
                self.stc_oscillator_d_first = self.stc_oscillator_d_second

        self.stc_fast_d_list_final = []

        self.stc_oscillator_fast_d = round(self.stc_oscillator_d_first, 2)      # 현재시간의 오실레이터_fast_d
        print(self.stc_oscillator_fast_d)

        ### 오실레이터_slow_%D 구하기 ###
        for i in range(0, 100):
            for j in range(i, i+26):  # 현재 분의 STC_슬로우 K, D 구하기 ///       stc_slow_k = ∑(현재가 - 최대값) / ∑(최대값 - 최소값)
                for k in range(j, j + 12):
                    self.close_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "현재가").strip()))
                    max_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", k, "고가").strip()))
                    min_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", k, "저가").strip()))

                    self.nq_max_price_list.append(max_price_row_data)
                    self.nq_min_price_list.append(min_price_row_data)

                self.close_price_first = self.close_price_row_data
                self.max_price_first = max(self.nq_max_price_list)
                self.min_price_first = min(self.nq_min_price_list)

                self.nq_max_price_list = []
                self.nq_min_price_list = []

                self.price_first_close_min = self.close_price_first - self.min_price_first
                self.price_first_max_min = self.max_price_first - self.min_price_first

                self.stc_slow_k_list_close_min.append(self.price_first_close_min)
                self.stc_slow_k_list_max_min.append(self.price_first_max_min)

                self.stc_slow_k_first = sum(self.stc_slow_k_list_close_min)
                self.stc_slow_k_second = sum(self.stc_slow_k_list_max_min)

                self.stc_slow_k_list_close_min = []
                self.stc_slow_k_list_max_min = []

                self.stc_slow_oscillator_k_26_close_min_list.append(self.stc_slow_k_first)
                self.stc_slow_oscillator_k_26_max_min_list.append(self.stc_slow_k_second)

            self.stc_slow_oscillator_k_26_close_min_list = self.stc_slow_oscillator_k_26_close_min_list[::-1]
            self.stc_slow_oscillator_k_26_max_min_list = self.stc_slow_oscillator_k_26_max_min_list[::-1]

            self.stc_slow_oscillator_k_26_close_min = sum(self.stc_slow_oscillator_k_26_close_min_list)
            self.stc_slow_oscillator_k_26_max_min = sum(self.stc_slow_oscillator_k_26_max_min_list)

            self.stc_slow_oscillator_k_26_close_min_list = []
            self.stc_slow_oscillator_k_26_max_min_list = []

            self.stc_slow_oscillator_k_26 = self.stc_slow_oscillator_k_26_close_min / self.stc_slow_oscillator_k_26_max_min * 100
            self.stc_slow_oscillator_k_26 = round(self.stc_slow_oscillator_k_26, 2)

            self.stc_slow_oscillator_final.append(self.stc_slow_oscillator_k_26)

        self.stc_slow_oscillator_final = self.stc_slow_oscillator_final[::-1]

        for i in range(0, 100):
            if i == 0:
                self.stc_oscillator_d_first = self.stc_slow_oscillator_final[i]
            elif i >= 1:
                self.stc_oscillator_d_second = (self.stc_slow_oscillator_final[i] * self.stc_oscillator_d_9_weighting) + (self.stc_oscillator_d_first * (1 - self.stc_oscillator_d_9_weighting))
                self.stc_oscillator_d_first = self.stc_oscillator_d_second

        self.stc_slow_oscillator_k = self.stc_slow_oscillator_final[99]     # 현재시간의 오실레이터_slow_k
        self.stc_slow_oscillator_final = []

        self.stc_oscillator_slow_d = round(self.stc_oscillator_d_first, 2)
        print(self.stc_oscillator_slow_d)

        self.stc_oscillator_current = self.stc_oscillator_fast_d - self.stc_oscillator_slow_d       # 200줄, 262줄
        self.stc_oscillator_current = round(self.stc_oscillator_current, 2)      # 현재시간의 오실레이터_slow_d   ///  # 부동소수점 문제로 소수 셋째자리에서 반올림
        print(self.stc_oscillator_current)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    btl = btl_system()

    btl.rq_data_opc10002("NQZ23", "1")

    app.exec_()

 

2) Stochastic 전체코드

현재가격 관련 Stochastic의 5가지를 모두 구현할 수 있는 코드이다.
   * 5가지 : 현재가격의 Stochastic Fast %F, Fast %D, Slow %F, %D, Stochastic Oscillator
 

더보기
import sys
from PyQt5.QAxContainer import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class btl_system():
    def __init__(self):
        self.kiwoom = QAxWidget("KFOPENAPI.KFOpenAPICtrl.1")
        print("로그인 시작!")

        self.kiwoom.dynamicCall("CommConnect(1)")  # CommConnect() : 괄호 안에 자동(1)을 넣는다.
        self.kiwoom.OnEventConnect.connect(self.login_Connect)
        self.kiwoom.OnReceiveTrData.connect(self.trdata_get)

        self.login_event_loop = QEventLoop()
        self.login_event_loop.exec_()

        self.kiwoom.dynamicCall("GetCommonFunc(QString, QString)", "ShowAccountWindow", "")  # 계좌번호 입력창을 띄우는 내부함수

        self.nq_max_price_list = []
        self.nq_min_price_list = []

        self.stc_fast_d_list = []

        self.stc_slow_k_list_close_min = []
        self.stc_slow_k_list_max_min = []

        self.stc_slow_d_list_row = []
        self.stc_slow_d_list = []

        self.stc_slow_k_forth_list = []

        self.stc_fast_k_weighting = 2 / (3 + 1)
        self.stc_slow_d_weighting = 2 / (5 + 1)
        self.stc_oscillator_d_weighting = 2 / (26+1)
        self.stc_oscillator_d_9_weighting = 2 / (9+1)

        self.stc_slow_oscillator_k_26_close_min_list = []
        self.stc_slow_oscillator_k_26_max_min_list = []

        self.stc_slow_oscillator_final = []

    def login_Connect(self, err_code):
        if err_code == 0:
            print('로그인 성공했습니다!')
        else:
            print('로그인 실패했습니다!')
        self.login_event_loop.exit()

    ########## 키움서버에 TR 요청하는 함수 모음 ##########
    def rq_data_opc10002(self, stock_code_num, time_unit):  # 1분봉 데이터 조회
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "종목코드", stock_code_num)
        self.kiwoom.dynamicCall("SetInputValue(QString,QString)", "시간단위", time_unit)
        self.kiwoom.dynamicCall("CommRqData(QString, QString, QString, QString)", "opc_10002", "opc10002", "", "1002")
        self.tr_event_loop = QEventLoop()
        self.tr_event_loop.exec_()

    ########## OnReceiveTrData을 통해 수신받은 데이터 함수  ##########
    def trdata_get(self, scrno, rqname, trcode, recordname, prenext):
        if rqname == "opc_10002":

            self.tr_event_loop.exit()

            btl.stochastic_searching()

    def stochastic_searching(self):
        ########## 스토캐스틱 패스트 %K, %D ##########
        for i in range(0, 30):  # 현재 분의 STC_패스트 K, D 구하기
            for j in range(i, i + 5):
                self.close_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "현재가").strip()))
                max_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "고가").strip()))
                min_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "저가").strip()))

                self.nq_max_price_list.append(max_price_row_data)
                self.nq_min_price_list.append(min_price_row_data)

            self.close_price_first = self.close_price_row_data
            self.max_price_first = max(self.nq_max_price_list)
            self.min_price_first = min(self.nq_min_price_list)

            self.nq_max_price_list = []
            self.nq_min_price_list = []

            self.stc_fast_k_first = (self.close_price_first - self.min_price_first) / (self.max_price_first - self.min_price_first) * 100
            self.stc_fast_k_first = round(self.stc_fast_k_first, 2)  # 소수 셋째자리에서 반올림

            self.stc_fast_d_list.append(self.stc_fast_k_first)

        self.stc_fast_d_list_final = self.stc_fast_d_list[::-1]
        self.stc_fast_k = self.stc_fast_d_list_final[29]

        self.stc_fast_d_list = []

        for i in range(len(self.stc_fast_d_list_final)):
            if i == 0:
                self.stc_fast_d_first = self.stc_fast_d_list_final[i]
            elif i >= 1:
                self.stc_fast_d_second = (self.stc_fast_d_list_final[i] * self.stc_fast_k_weighting) + (self.stc_fast_d_first * (1 - self.stc_fast_k_weighting))
                self.stc_fast_d_first = self.stc_fast_d_second

        self.stc_fast_d_list_final = []

        self.stc_fast_k = round(self.stc_fast_k, 2)
        self.stc_fast_d = round(self.stc_fast_d_second, 2)

        # print(self.stc_fast_k)
        # print(self.stc_fast_d)

        ########## 스토캐스틱 슬로우 %K, %D ##########
        for i in range(0, 30):
            for j in range(i, i+5):  # 현재 분의 STC_슬로우 K, D 구하기 ///       stc_slow_k = ∑(현재가 - 최대값) / ∑(최대값 - 최소값)
                for k in range(j, j + 12):
                    self.close_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "현재가").strip()))
                    max_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", k, "고가").strip()))
                    min_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", k, "저가").strip()))

                    self.nq_max_price_list.append(max_price_row_data)
                    self.nq_min_price_list.append(min_price_row_data)

                self.close_price_first = self.close_price_row_data
                self.max_price_first = max(self.nq_max_price_list)
                self.min_price_first = min(self.nq_min_price_list)

                self.nq_max_price_list = []
                self.nq_min_price_list = []

                self.price_first_close_min = self.close_price_first - self.min_price_first
                self.price_first_max_min = self.max_price_first - self.min_price_first

                self.stc_slow_k_list_close_min.append(self.price_first_close_min)
                self.stc_slow_k_list_max_min.append(self.price_first_max_min)

            self.stc_slow_k_first = sum(self.stc_slow_k_list_close_min)
            self.stc_slow_k_second = sum(self.stc_slow_k_list_max_min)

            self.stc_slow_k_list_close_min = []
            self.stc_slow_k_list_max_min = []

            self.stc_slow_k_third = self.stc_slow_k_first / self.stc_slow_k_second * 100
            self.stc_slow_k_third = round(self.stc_slow_k_third, 2)

            self.stc_slow_k_forth_list.append(self.stc_slow_k_third)
            if i == 0:
                self.stc_slow_k = self.stc_slow_k_forth_list[0]
            elif i == 1:
                self.stc_slow_k_1_ago = self.stc_slow_k_forth_list[1]

        self.stc_slow_d_list = self.stc_slow_k_forth_list[::-1]
        self.stc_slow_k_forth_list = []

        for i in range(0, 30):
            if i == 0:
                self.stc_slow_d_first = self.stc_slow_d_list[i]
            elif i >= 1:
                self.stc_slow_d_second = (self.stc_slow_d_list[i] * self.stc_slow_d_weighting) + (self.stc_slow_d_first * (1 - self.stc_slow_d_weighting))
                self.stc_slow_d_first = self.stc_slow_d_second

        self.stc_slow_d = round(self.stc_slow_d_first, 2)
        self.stc_slow_d_list = []

        # print(self.stc_slow_k)
        # print(self.stc_slow_d)

        ########## 스토캐스틱 오실레이터 ##########
        ### 오실레이터_fast_%D 구하기 ###
        for i in range(0, 150):  # 현재 분의 STC_오실레이터 구하기
            for j in range(i, i + 12):
                self.close_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", i, "현재가").strip()))
                max_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "고가").strip()))
                min_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "저가").strip()))

                self.nq_max_price_list.append(max_price_row_data)
                self.nq_min_price_list.append(min_price_row_data)

            self.close_price_first = self.close_price_row_data
            self.max_price_first = max(self.nq_max_price_list)
            self.min_price_first = min(self.nq_min_price_list)

            self.nq_max_price_list = []
            self.nq_min_price_list = []

            self.stc_fast_k_first = (self.close_price_first - self.min_price_first) / (self.max_price_first - self.min_price_first) * 100
            self.stc_fast_k_first = round(self.stc_fast_k_first, 2)  # 소수 셋째자리에서 반올림

            self.stc_fast_d_list.append(self.stc_fast_k_first)

        self.stc_fast_d_list_final = self.stc_fast_d_list[::-1]

        self.stc_fast_d_list = []

        for i in range(len(self.stc_fast_d_list_final)):
            if i == 0:
                self.stc_oscillator_d_first = self.stc_fast_d_list_final[i]
            elif i >= 1:
                self.stc_oscillator_d_second = (self.stc_fast_d_list_final[i] * self.stc_oscillator_d_weighting) + (self.stc_oscillator_d_first * (1 - self.stc_oscillator_d_weighting))
                self.stc_oscillator_d_first = self.stc_oscillator_d_second

        self.stc_fast_d_list_final = []

        self.stc_oscillator_fast_d = round(self.stc_oscillator_d_first, 2)      # 현재시간의 오실레이터_fast_d
        print(self.stc_oscillator_fast_d)

        ### 오실레이터_slow_%D 구하기 ###
        for i in range(0, 100):
            for j in range(i, i+26):  # 현재 분의 STC_슬로우 K, D 구하기 ///       stc_slow_k = ∑(현재가 - 최대값) / ∑(최대값 - 최소값)
                for k in range(j, j + 12):
                    self.close_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", j, "현재가").strip()))
                    max_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", k, "고가").strip()))
                    min_price_row_data = abs(float(self.kiwoom.dynamicCall("GetCommData(QString,QString,int,QString)", "opc_10002", "opc10002", k, "저가").strip()))

                    self.nq_max_price_list.append(max_price_row_data)
                    self.nq_min_price_list.append(min_price_row_data)

                self.close_price_first = self.close_price_row_data
                self.max_price_first = max(self.nq_max_price_list)
                self.min_price_first = min(self.nq_min_price_list)

                self.nq_max_price_list = []
                self.nq_min_price_list = []

                self.price_first_close_min = self.close_price_first - self.min_price_first
                self.price_first_max_min = self.max_price_first - self.min_price_first

                self.stc_slow_k_list_close_min.append(self.price_first_close_min)
                self.stc_slow_k_list_max_min.append(self.price_first_max_min)

                self.stc_slow_k_first = sum(self.stc_slow_k_list_close_min)
                self.stc_slow_k_second = sum(self.stc_slow_k_list_max_min)

                self.stc_slow_k_list_close_min = []
                self.stc_slow_k_list_max_min = []

                self.stc_slow_oscillator_k_26_close_min_list.append(self.stc_slow_k_first)
                self.stc_slow_oscillator_k_26_max_min_list.append(self.stc_slow_k_second)

            self.stc_slow_oscillator_k_26_close_min_list = self.stc_slow_oscillator_k_26_close_min_list[::-1]
            self.stc_slow_oscillator_k_26_max_min_list = self.stc_slow_oscillator_k_26_max_min_list[::-1]

            self.stc_slow_oscillator_k_26_close_min = sum(self.stc_slow_oscillator_k_26_close_min_list)
            self.stc_slow_oscillator_k_26_max_min = sum(self.stc_slow_oscillator_k_26_max_min_list)

            self.stc_slow_oscillator_k_26_close_min_list = []
            self.stc_slow_oscillator_k_26_max_min_list = []

            self.stc_slow_oscillator_k_26 = self.stc_slow_oscillator_k_26_close_min / self.stc_slow_oscillator_k_26_max_min * 100
            self.stc_slow_oscillator_k_26 = round(self.stc_slow_oscillator_k_26, 2)

            self.stc_slow_oscillator_final.append(self.stc_slow_oscillator_k_26)

        self.stc_slow_oscillator_final = self.stc_slow_oscillator_final[::-1]

        for i in range(0, 100):
            if i == 0:
                self.stc_oscillator_d_first = self.stc_slow_oscillator_final[i]
            elif i >= 1:
                self.stc_oscillator_d_second = (self.stc_slow_oscillator_final[i] * self.stc_oscillator_d_9_weighting) + (self.stc_oscillator_d_first * (1 - self.stc_oscillator_d_9_weighting))
                self.stc_oscillator_d_first = self.stc_oscillator_d_second

        self.stc_slow_oscillator_k = self.stc_slow_oscillator_final[99]     # 현재시간의 오실레이터_slow_k
        self.stc_slow_oscillator_final = []

        self.stc_oscillator_slow_d = round(self.stc_oscillator_d_first, 2)
        print(self.stc_oscillator_slow_d)

        self.stc_oscillator_current = self.stc_oscillator_fast_d - self.stc_oscillator_slow_d       # 200줄, 262줄
        self.stc_oscillator_current = round(self.stc_oscillator_current, 2)      # 현재시간의 오실레이터_slow_d   ///  # 부동소수점 문제로 소수 셋째자리에서 반올림
        print(self.stc_oscillator_current)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    btl = btl_system()

    btl.rq_data_opc10002("NQZ23", "1")

    app.exec_()

 


5. 마치며

Stochastic 지표에 대해 알아보았다. 상당히 많이 활용되는 지표로 알고 있다. 그런데 산출식이 이렇게 복잡할 줄은 몰랐다. 필자가 아직은 미숙해서인지 코드도 길어지고, 설명이 부족하지 않았나 걱정이 되기도 하고... 약간 아쉬움이 많이 남는 스톡캐스틱 설명이었다.
 
오후2시~새벽2시까지 계속 스토캐스틱 코드작성에 신경을 썼다. 코딩을 하면서 이렇게 고민하고 글을 쓴적이 있었나 싶다. 그래도 기쁜 건, 그동안 "벽"으로만 느꼈던 지수이동 평균을 5줄 이내로 간략하게 작성할 수 있는게 마음에 든다.
 
그 동안 RSI만 활용하였는데, 스토캐스틱 지표도 열심히 공부해서 향후 코딩에 적용해보고 싶다.
 

반응형