[ Splunk Project ] Splunk로 주식 분석 | Phase 10. 주식 매매 결정 하기 2
본문 바로가기

Splunk/Splunk Project

[ Splunk Project ] Splunk로 주식 분석 | Phase 10. 주식 매매 결정 하기 2

728x90
반응형

이번에는 주식 가격 매매를 결정하기 위해서 머신러닝을 이용한 기법을 이용해 보겠다.

이미 우리는 5장에서 이미 MLTK를 이용해서 주식가격을 예측해 보았는데,

이번에는 MLTK를 이용해 모델을 만들고 데이터가 수집 되면
이 모델을 이용해서 평가하는 방법으로 사용할 것이다.

 

 

MLTK 앱의 "Experiment"

이번에는 주식 특정 기간의 종가를 기준으로
다음 틱에서 가격이 올라갈지(1), 내려갈지(-1)를 판단하도록 해보겠다.

가격 변동이 없는 경우에는 -1로 넣기로 한다.

우선 학습할 데이터를 만들어 줘야 하는데,
직전 n 일치 등락폭을 기준으로 해서 오늘의 주식의 방향을 예측하는 것으로 해보도록 하겠다.

 

우선 직전 5일 까지의 변화량을 만들어 주는 SPL 문이다.

index="kospi" code="068270.KS"
| sort _time
| eval change = ((Close - Open) / Open) * 100
| streamstats window=5 current=false list(change) as changes
| eval lag_0 = mvindex(changes, 0)
| eval lag_1 = mvindex(changes, 1)
| eval lag_2 = mvindex(changes, 2)
| eval lag_3 = mvindex(changes, 3)
| eval lag_4 = mvindex(changes, 4)
| eval direction = if(change > 0, 1,  -1)
| table Date, change, direction, lag_*

우선 가격의 변화율을 계산하고,
"streamstats"을 이용해 현재 값을 제외한 직전 5일 치를 리스트로 만들어 준 후
값을 순서 대로 꺼내서 새로운 필드로 만들어 주었다.

그리고 마지막으로 변화율의 부호로 방향을 만들어 준다.

 

결과는 다음과 같다

다 된거 같은데, 만약 우리가 10일치 이전 데이터를 가지고 판단하고 싶다고 하면 어떻게 하지?

eval 문 10개를 써줘야 하나? 그럼 20개면....?흠....

노가다는... 회피하자...

 

 

이 부분을 자동으로 펼쳐줄 수 있도록 매크로로 만들어 보았다.

$arg1$ 에는 lag 할 필드명이, $arg2$ 에는 lag 할 갯수를 넣는다.

streamstats current=false window=$arg2$ list($arg1$) as values
| append [ | makeresults count=$arg2$ | streamstats count | eval field_name = "lag_".(count - 1)  
| chart sum(count)  by _time, field_name limit=0]
| eventstats sum(lag_*) as lag_*
| foreach lag_* [  eval <<FIELD>> = mvindex(values,  (<<FIELD>> - 1)) ]
| where isnotnull(values)

우선 앞에서 처럼 "streamstats" 을 이용해 리스트로 만들어 준다.

그리고 "makeresults" 라는 명령을 이용해 가상의 데이터를 이용해 확장할 필드들을 만들어 준 후

" streamstats" 명령으로 각 줄에 순번을 넣어준다.

 

 

 

 


SPL 설명


1️⃣ 앞에서 처럼 "streamstats" 을 이용해 리스트로 만들어 준다

그리고 "makeresults" 라는 명령을 이용해 가상의 데이터를 이용해 확장할 필드들을 만들어 준 후

" streamstats" 명령으로 각 줄에 순번을 넣어준다.

| makeresults count=10
| streamstats count
| eval field_name = "lag_".(count - 1)

그러면 위와 같은 테이블이 만들어 지는데,

 

 

2️⃣ 이를 chart 명령을 이용해 "field_name" 값들이 필드 명이 되게 해준다.

| chart sum(count)  by _time, field_name limit=0

----------------------------------------------------------------------------------------

| makeresults count=10
| streamstats count
| eval field_name = "lag_".(count - 1)
| chart sum(count)  by _time, field_name limit=0

chart 명령 후에 limit=0을 주지 않으면

필드가 10개 까지만 만들어지기 때문에 10개 이상의 필드를 만들기 위해서는 반드시 필요하다.

 

 

3️⃣ 이렇게 만들어진 열 stremstats 를 "append" 명령을 이용해 합쳐 준다.

"append" 명령은 독립된 검색 문들을 하나의 결과로 합쳐 주는 역할을 한다.

여기 까지 작업을 하게 되면, 아래와 같은 형태의 테이블이 만들어 진다.

...
| append  [...
]
...

 

 

4️⃣ 다음은 "eventstats" 명령을 사용

"eventstats" 은 전체 데이터에 대한 통계를 계산해서
모든 열에 계산 된 같은 값을 채워주는 역할을 한다.

자! 그래서 lag_* 에 있는 모든 열들의 합을 구하면

우리가 새로 만든 열의 값들을 가지고 모든 열이 채워지게 된다.

... | eventstats sum(lag_*) as lag_* ...

 

 

5️⃣ "foreach" 명령

하나의 열에 있는 각 필드들에 대해서 어떤 계산이나 작업을 해 주기 위해서 사용한다.

만약 필드가 20개가 있는데, 이 20개의 필드에 대한 합을 구하고 싶은 경우,

eval 명령으로 20개의 필드명을 다 써서 더해야 하는데,

foreach 명령을 사용하면 "| foreach test* [eval total=total + '<<FIELD>>']" 다음처럼 사용할 수 있다.

이는 test로 시작하는 모든 필드들에 대한 합을 구하는 것이다.

| foreach lag_* 
    [ eval <<FIELD>> = mvindex(values, (<<FIELD>> - 1)) ]

 

 

🍎 완성 🍎

 index="kospi" code="068270.KS"
|sort _time
| eval change = ((Close - Open) / Open) * 100 
| streamstats current=false window=10 list(change) as values
| append
[| makeresults count=10 
| streamstats count
| eval field_name = "lag_".(count - 1) 
| chart sum(count) by _time, field_name limit=0]
| eventstats sum(lag_*) as lag_*
| foreach lag_*
[ eval <<FIELD>> = mvindex(values, (<<FIELD>> -1)) ]
| where isnotnull(values)
| eval direction = if(change > 0, 1, -1)
| table _time, direction, lag_*

 

index="kospi" code="068270.KS"
| sort _time
| eval change = ((Close - Open) / Open) * 100
| `mylag(change, 10)`
| eval direction = if(change > 0, 1,  -1)
| table _time, direction, lag_*
| where isnotnull(lag_9)

그래서 위의 명령은 간단하게 다음처럼 줄여서 쓸 수 있다.

10일전의 데이터를 이용해서 판단을 하고 비어있는 lag 가 있는 데이터는 사용하지 않는다.

이제 다시 MLTK 로 돌아와서 보도록 하자. 각 단계별로 살펴 보도록 하겠다.

 

 

 

1. 데이터 조회

index="kospi" code="068270.KS"
| sort _time
| eval change = ((Close - Open) / Open) * 100
| `mylag(change, 10)`
| eval direction = if(change > 0, 1,  -1)
| table _time, direction, lag_*
| where isnotnull(lag_9)

위 처럼 검색문을 입력하고 수행하면 화면 가장 아래 데이터에서 데이터를 확인할 수 있다.

 

 

2. 데이터 사전 처리

데이터 사전 처리에는 아래처럼 여러가지가 있다.

🍀 "StandardScaler" 🍀

데이터의 크기를 일정하게 해주는 역할을 한다.

가령 주식 데이터에서 종가 데이터와 볼륨데이터는 단위가 각기 틀리고 값이 가지는 범위도 다르다.
이 값들을 일정 범위의 값으로 바꿔 주어서 모델이 더 잘 적합할 수 있도록 하여 준다.

🍀 "FieldSelector""KernalPCA", "PCA" 🍀

모델에 적용할 필드의 개수를 줄여 주는 역할을 한다.

가령 우리가 조사하고 싶은 필드가 200개 정도가 있다고 한다면, 모델의 성능에 악영향을 미칠 수 있고,
실제로 모델을 만드는데 필요가 없는 필드들이 포함되어 있을 수도 있다.

그래서 많은 필드 들 중 특징적인 값들만 선택한다거나(PCA),

아니면 모델에 영향을 줄 수 있을 것 같은 필드만 선택한다거나(FieldSelector) 하게 된다.

🍀 "TFIDF" 🍀

어떤 글로 된 문서에서 형태소별로 분리하고 분리된 형태소의 출현빈도를 계산하여
스코어를 계산한 값으로 사용하는 영역이 위의 둘과 완전히 다르다.

주식 분석에서는 우선 사용할 일이 없으니 이 것은 생각하지 말자.
(만약 트위터나 블로그 글에서 해당 주식에 대한 긍정/부정 단어 분석을 통해 매매에 대한 가부를 결정하고자 한다면 사용해야 할 수 도 있다.)

 

 

그런데 우리는 SPL에서 이미 변화율로 계산하였기 때문에

모든 값들이 특정 범위(-100~100)에 들어가 있다. 그렇기 때문에 사전작업은 하지 않을 것이다.

 

 

3. 알고리즘 선택

알고리즘을 선택한다.

현재 Categorical 예측에는 다음 알고리즘들이 포함되어 있다.

각 알고리즘을 다 설명할 수는 없기 때문에 간단하게 분류를 해보면
뒤에 NB가 붙은 알고리즘은 확률에 기반을 한 알고리즘이다.

그렇기 때문에 다른 알고리즘과 해석하는 방법이 약간 달라질 수 있다.

(MLTK에서는 다 똑같이 해석하니 크게 구별할 필요는 없다.)

그 중 BernoulliNB 는 이진 데이터(결과가 True/False 인 경우)에만 적용이 가능하기 때문에

여러개의 카테고리를 예측하는 경우에는 사용할 수 없다는 것만 알아두자.

그리고 나머지 RandomForestClassifer, LogisticRegression, SVM, DecisionTreeClassifier

내부 알고리즘의 차이는 있지만 결과를 확인하는 방법은 동일하기 때문에,

특정 문제에 대해서 각각을 적용해 보고 가장 정확도가 좋은 알고리즘을 선택하면 된다.

알고리즘을 선택하면 다음은 예측할 필드와 예측을 위해 사용할 필드를 선택하게 된다.

아래에서는 "LogisticRegression" 알고리즘을 선택했고

예측 필드로 "direction"을 그리고 사용할 필드로는 "lag_0 ~ lag_9" 까지 입력했다.

 

그리고 가장 오른쪽에 있는 바는 모델을 만드는데 사용할 데이터와

모델의 정확도를 테스트할 때 사용할 데이터 양을 나누는 비율을 선택한다.

모든 데이터를 훈련하는데 써버리면 나중에 테스트 용 데이터가 없다.

그리고 훈련하는데 사용했던 데이터를 다시 테스트 하는데 사용하게 되면, 정확도가 올라 갔다가,

새로운 데이터가 나타나면 갑자기 정확도가 형편없이 떨어질 수 도 있기 때문에

모델을 만드는 시점에 테스트 데이터로 검증하는 작업이 필요하다.

(데이터 양에 따라서 7:3, 8:2 정도로 설정한다. )

또한 알고리즘 별로 여러가지 파라미터를 넣도록 되어있는데,
이는 각 알고리즘에 대한 공부가 필요하기 때문에 일단은 모두 디폴트로 사용했다.

 

4. 실행 및 모델 평가

자, 이제 모델 "Fit Model"을 눌러서 모델을 만들어 보도록 하자 . 실행한 결과가 다음처럼 표로 나타난다.

초록색으로 보이는 부분이 안타깝게도 잘 못 예측 된 부분이고, 상당히 많이 보인다.

 

아래는 모델 테스트에 대한 요약이다.

모델을 평가하는 데는 여러가지 평가 기준이 있는데,

이 평가 기준에 따라서 왼쪽에서 보는 것처럼 "Precision", "Recall", "Accuracy", "F1" 스코어로 나뉜다.

하지만 이러한 스코어 모두 왼쪽에서 보이는 "Confusion Matrix" 를 이용해서 나온 값이다.

이번에는 가장 기본적인 Precision 만 보자.

이는 "-1"이든 "1"이든 전체 테스트 한 양 대비 정확하게 예측한 확률이다.

51%로 거의 찍은 것과 같음

 

실망하지 말고 알고리즘을 바꿔서 다시 실행해보자. (로지스틱 회귀)

더 낮아짐

 

SVM

별반다를게 없음

 

 

GaussianNB

오.. 나름??ㅋㅋㅋ

 

DecisionTreeClassifier

할 말이 없닼ㅋㅋ 근데 이게 잘 먹혔다면 이미 다들 부자가 되었겠지ㅋㅋㅋ

 

다양한 데이터와 다양한 속성들을 추가해서 나만의 알고리즘을 만들어 보자....

지금까지 테스트 한 내용은 역시 "Experiment History" 에 남아있다.

이 중에서 가장 맘에 든 것을 선택해서 사용하면 되겠다.

그나마 높은 것의 Fit Model 클릭

저장 클릭

저장하였으면 이제 이 모델을 다른데서 사용할 수 있도록 publish 한다.

방금 모델은 셀트리온 주식에 대해서 모델을 만들었기 때문에 셀트리온 모델로 저장하자.

(적용도 셀트리온 주식에만 적용한다.) 대상 앱은 "stock"을 선택하자.

"제출" 하면 다음처럼 모델을 사용할 수 있는 방법이 나온다. 자 이제 모델을 실제 사용해보자.

 

... | apply model_predict_stock_direction_for_celltrion

 

index="kospi" code="068270.KS" earliest="-30d"
| sort _time 
| eval change = ((Close - Open) / Open) * 100 
| `mylag(change, 10)`
| eval direction = if(change > 0, 1, -1) 
| table Date, direction, lag_*
| where Date="2023-06-1"
| apply model_predict_stock_direction_for_celtrion

자 다음처럼 2023년 6월 1일에 대해서 예측해 보면 용케 맞췄다.

 

자 이렇게 해서 지금까지,

* Kalman Filter 예측,

* 삼중창 매매 타이밍 분석,

* ML 예측

을 테스트 해보았다. 다음에는 이 3가지 모델을 매일 배치로 예측을 하고 각각 모델의 가중치를 둬서, 실제 매매를 결정하고 시뮬레이션 하는 과정을 진행해보도록 하겠다.

 

 


추가 1

다시 모델화면으로 돌아가서 "Show SPL" 을 통해 SPL을 복사해 두자.

index="kospi" code="068270.KS"
| sort _time
| eval change = ((Close - Open) / Open) * 100
| `mylag(change, 10)`
| eval direction = if(change > 0, 1,  -1)
| table _time, direction, lag_*
| where isnotnull(lag_9)
| fit GaussianNB  "direction" from "lag_0" "lag_1" "lag_2" "lag_3" "lag_4" "lag_5" "lag_7" "lag_6" "lag_8" "lag_9" into "_exp_draft_812babb92f2a491c840d676d055a9772"

그리고 검색에서 코드를 변경하면서 모델을 바로 바로 만들 수 있다. 이 때 fit 명령 중 "into" 가 모델의 이름을 저장하는 부분이기 때문에 이 곳을 알 수 있는 이름으로 바꾸자..

 

 

index="kospi" code="005930.KS"  earliest=0
| sort _time
| eval change = ((Close - Open) / Open) * 100
| `mylag(change, 10)`
| eval direction = if(change > 0, 1,  -1)
| table _time, direction, lag_*
| where isnotnull(lag_9)
| fit GaussianNB  "direction" from "lag_0" "lag_1" "lag_2" "lag_3" "lag_4" "lag_5" "lag_7" "lag_6" "lag_8" "lag_9" into "model_predict_stock_direction_for_samsung_elec"

삼성전자에 대한 모델 생성.

이 부분을 "savedsearch"로 저장한다.

이 후 설정에서 검색/리포트/알람에 들어간다.

쿼리를 다음처럼 수정해서 파라미터를 통해 모델을 생성할 수 있도록 한다.

 

index="kospi" code="$code$.KS" earliest=0
| sort _time
| eval change = ((Close - Open) / Open) * 100
| `mylag(change, 10)`
| eval direction = if(change > 0, 1,  -1)
| table _time, direction, lag_*
| where isnotnull(lag_9)
| fit GaussianNB  "direction" from "lag_0" "lag_1" "lag_2" "lag_3" "lag_4" "lag_5" "lag_7" "lag_6" "lag_8" "lag_9" into "model_predict_stock_direction_for_$code$"

이는 주기적으로 관심 목록에 있는 주식 모델을 가지고 모델을 만드는데 사용할 것이다.

 

 


추가 2

 

앞에서 mylag "macro" 는

현재 시점을 빼고 lag를 만들기 때문에

모델을 만들 때 사용하기는 좋았으나,

미래를 예측하기 위해서 쓸 때는

현재까지 들어온 데이터를 가지고

다음 장을 에측해야 하기 때문에

약간 문제가 있을 수 있다.

 

그래서 파라미터 하나를 더 추가해서,
미래 예측할 때도 쓸 수 있는 macro를 추가하였다.

 

streamstats current=$arg3$ window=$arg2$ list($arg1$) as values 
| append [ | makeresults count=$arg2$ 
    | streamstats count 
    | eval field_name = "lag_".(count - 1) 
    | chart sum(count) by _time, field_name limit=0] 
| eventstats sum(lag_*) as lag_*
| foreach lag_* [ eval <<FIELD>> = mvindex(values, (<<FIELD>> - 1)) ] 
| where isnotnull(values)

세 번째 arg3에 "true" , "false" 값을 넣어서 선택할 수 있음.

 

 

다음 장 예측

index="kospi" code="068270.KS" earliest="-30d"
| sort _time 
| eval change = ((Close - Open) / Open) * 100 
| `mylag(change, 10, true)`
| eval direction = if(change > 0, 1, -1) 
| table Date, direction, lag_*
| where Date="2021-01-15"
| apply model_predict_stock_direction_for_celltrion
| table Date, lag_*, predicted*

 

 

15일 변화율까지 포함되어서 다음 장에 대해서 예측함.

728x90
반응형