AI를 활용한 재밌는 것들을 개발합니다

2020년 1월 18일 토요일

[퀀트 조건 최적화] R genalg 유전자 알고리즘으로 최적 매매조건을 찾아라.

자 다음과 같이 주식매매전략 조건들이 있다고 하자.

1. 상승모멘텀을 평가하기 위한 기간 : A ex)5일,10일,20일,30일,...
2. 상승모멘텀이라고 판단 할 수 있는 값 : B ex)1,2,3,5,10,...
3. 하강모멘텀을 평가하기 위한 기간 : C ex)5일,10일,20일,30일,...
4. 상승모멘텀이라고 판단 할 수 있는 값 : D ex)1,2,3,5,10,...
5. 상승모멘텀일 때 매수 조건 : E ex)최근 5일,10일,.... 최고가일 때
6. 상승모멘텀일 때 매도 조건 : F ex)최근 5일,10일,.... 최저가일 때
7. 하강모멘텀일 때 매수 조건 : G ex)최근 5일,10일,.... 최고가일 때
8. 하강모멘텀일 때 매도 조건 : H ex)최근 5일,10일,.... 최저가일 때

총 8가지의 변수 값이 있는데 이 값에 따른 매매전략으로 주식매매 시뮬레이션을 돌렸을 때 최종 수익률이 가장 높게 나오는 그 8가지의 값을 찾는 것을 최적화 한다고 부른다.

어떻게 최적 값을 찾을 수 있을까?

각 변수별로 들어 갈 수 있는 값의 범위를 대략적으로 정해보면 아래와 같다.

A : 60개(1일~60일)
B : 20개(1~20)
C : 60개(1일~60일)
D : 20개(1~20)
E : 30개(1일~30일)
F : 30개(1일~30일)
G : 30개(1일~30일)
H : 30개(1일~30일)

위의 각 변수값을 한번씩 다 대입해서 그 중 최종 수익률을 찾는 것이 최적화 하는 것이라고 했다. 위 변수 값을 다 대입한다는 것은 몇 번의 시뮬레이션을 돌려야 하는 것일까?

60 X 20 X 60 X 20 X 30 X 30 X 30 X 30 = 1,166,400,000,000‬

1조 1천 6백 6십 4억 번의 시뮬레이션을 다 돌려봐야 그 중 최고의 수익률을 보이는 최적 값을 찾을 수 있다는 말이다.

그런데 시뮬레이션 한 번 돌리는데 1초가 걸린다고 생각해보자. 모든 시뮬레이션을 돌리는데는 얼마나 걸릴까??

13,500,000 일 

허허허.... 3만6천년 정도 걸린다...

자 여기서 뭔가 비슷한 문제가 있었던게 기억나지 않는가? 2016년, 계산이 불가능하다던 바둑에서 이세돌에게 4번 이기고 한번만 졌던 그 알파고... 사실 알파고 때문에 지금의 직장으로 이직할 수 있었지만... 암튼 그 알파고가 불가능한 경우의 수 계산을 다 하지 않고 정해진 시간에 빠르게 할 수 있게 한게 몬테카를로 머시기 알고리즘이다. 모든 경우의 수를 다 계산 하지 않고도 원하는 값에 근사시키는 방법 중의 하나이다.

다시 본론으로 돌아오면 8개의 변수에 모든 경우의 수를 다 대입하지 않고도 최적 값에 근사시킬 수 있는 방법이 있는데 지금 소개할 게 바로 유전자(genetic) 알고리즘이다. 내가 이 알고리즘을 접한 건 2009년 정도였는데 처음 들었을 때 정말 신기했었는데 실제 현업에 적용해 본 것은 2015년 정도였던 것 같다. 그 때는 C#으로 구현했었다. 유전자 알고리즘이 하나의 목적함수에 대한 최적값을 구하는 것이라면 2개 이상의 다중목적함수의 최적값을 찾는 알고리즘으로는 크게 진화(evolutionary) 알고리즘이 있는데 내가 사용했던 것은 NSGA2라는 알고리즘이었다.

각설하고, 지금 퀀트매매 알고리즘을 R로 만들다 보니 유전자 알고리즘도 R에서 찾다보니 genalg 라는 패키지가 개발되어 있어 바로 사용해 보았다. 유전자 알고리즘의 원리는 따로 찾아서 공부해보시라. 간략히 말하자면 적자생존, 좋은 유전자가 교배하여 더 좋은 유전자를 만든다는 원리다. 램덤으로 경우의 수 10개를 발생시켜 시뮬레이션을 돌려보고 그중 제일 좋은 경우의 수를 선택해 그 경우의 수에 다시 10개의 자손 경우의 수를 만들어 시뮬레이션을 돌리고... 이 걸 계속 반복하는 것이다. 그럼 놀랍게도 몇번 해보지 않아도 최적 값에 근사한 경우의 수를 찾아낼 수 있다. 거짓말 같은가?? 진짜다. 3만 6천년이 아니라 이틀 정도만 돌려도 최적 값을 찾았다고 할 수 있다.

한 알고리즘을 잘 설명하는 것은 정말 어렵다. 그냥 예제를 아래에 보여드릴테니 공부하시라. 모르는 것이 있으면 댓글로 달아 주시면 답은 드리리라.

먼저 셋팅 코드


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
library(genalg)

D=8 #변수 갯수 설정
MaxRange = 60 # 한 변수의 최대값
Dim=ceiling(log(MaxRange,2)) #한 변수를 2진수로 나타낼때 길이
size = D*Dim #한 유전자의 총 길이 (2진수)

intbin=function(x) # 2진수를 정수로 변환
{
  sum(2^(which(rev(x==1))-1))
}

bintbin=function(x) # 2진수 전체유전자를 정수 값으로 반환
{
  s=vector(length=D)
  for(i in 1:D)
  {
    ini=(i-1)*Dim+1;end=ini+Dim-1
    s[i]=intbin(x[ini:end])
  }
  return(s)
}

simulation=function(x) #수익률 시뮬레이션 
{
  allsumprice = (x[1]+x[2]-x[3])*(sin(x[4])+1)+sin(x[5]+1)+sin(x[6]+1)/sin(x[7]+1)-x[8]
  
  return(allsumprice)
  
}

do_simulation=function(x) # 변수값에 제한(Constraint) 줄 수 있음
{
  s=bintbin(x)
  s[1]=ifelse(s[1]<=1,2,s[1])
  s[3]=ifelse(s[3]<=1,2,s[3])
  f=-simulation(s) # 최저값을 향하여(실제는 최고값)
  return(f)
}

monitor <- function(obj) { # 이건 각 유전자 계산 후 결과를 보여줌
  minEval = min(obj$evaluations);
  filter = obj$evaluations == minEval;
  bestObjectCount = sum(rep(1, obj$popSize)[filter]);
  # ok, deal with the situation that more than one object is best
  if (bestObjectCount > 1) {
    bestSolution = obj$population[filter,][1,];
  } else {
    bestSolution = obj$population[filter,];
  }
  outputBest = paste(obj$iter, " #selected=", sum(bestSolution),
                     " Best (Error=", minEval, "): ", sep="");
  for (var in 1:length(bestSolution)) {
    outputBest = paste(outputBest,
                       bestSolution[var], " ",
                       sep="");
  }
  outputBest = paste(outputBest, "\n", sep="");
  cat(outputBest);
}

24행 simulation 함수에는 주식매매 시뮬레이션 코드를 넣어야 하지만 그 코드가 좀 길어서 임의의 수식을 내 마음대로 넣었다. 즉 저 수식에 8개의 변수를 임의로 넣었을때 최고가 되게 하는 변수 값을 찾는 것이다.

이제 유전자 알고리즘을 돌리는 코드이다.


1
2
3
4
5
6
7
8
9
G=rbga.bin(size=size,
           #suggestions=sug,
           popSize=20, # 한 세대의 유전자 수
           iters=10, # 몇 세대까지 반복
           zeroToOneRatio=1,
           evalFunc=do_simulation,
           monitorFunc=monitor,
           elitism=1,
           verbose=TRUE)

시뮬레이션 결과는 아래와 같이 나온다.


10번의 세대를 거쳐 약 111의 최고값을 찾았다는 것이다. 111을 만드는 경우의 수는 그 옆에 나오는 11100... 의 이진수 값인데 이것을 정수로 한번 확인해 보자. 위의 이진수를 복사해서 아래와 같이 해보자.


1
2
3
4
5
6
7
8
9
best_chromosome = c(1, 1, 1, 0, 0, 1,
                    1, 1, 1, 1, 0, 0,
                    0, 0, 0, 1, 1, 0,
                    1, 1, 1, 0, 1, 0, 
                    1, 0, 0, 1, 0, 0, 
                    0, 0, 1, 1, 0, 1, 
                    1, 1, 1, 0, 0, 1,
                    0, 0, 0, 0, 0, 1 )
bintbin(best_chromosome)

결과는 아래와 같다.

A:57, B:60... 이 순서대로 값이 대입되었을 때 111이라는 최고 값을 얻을 수 있었다는 것이다.

지금 시뮬레이션은 아주 간단한 식이라서 순식간에 시뮬레이션이 끝난다. 하지만 한번 시뮬레이션에 1분이 걸린다면... 유전자 알고리즘을 중단 시켰을 때 처음부터 다시 최적값을 찾아가기 너무 힘들어 진다. 이럴 때는 이미 알려진 최적 경우의 수를 유전자 알고리즘 시작할 때 미리 넣어 줄 수 있다. 그럼 그 최적 경우의 수부터 자손을 만들어 시뮬레이션을 계속 해 나갈 수 있다. 최적 경우의 수를 미리 넣어주는 방법은 아래와 같다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sug <- t(replicate(best_chromosome,n = 1))

G=rbga.bin(size=size,
           suggestions=sug,
           popSize=20, # 한 세대의 유전자 수
           iters=10, # 몇 세대까지 반복
           zeroToOneRatio=1,
           evalFunc=do_simulation,
           monitorFunc=monitor,
           elitism=1,
           verbose=TRUE)

자 그럼 유전자 알고리즘 결과를 보는 방법을 알아보자.


summary(G,echo=TRUE) 코드를 입력하면 최적 경우의 수를 볼 수 있다.

세대가 지나면서 최적 값이 변해 가는 것을 볼 수도 있다. 멸령어 코드는

plot(G)



50세대 정도부터는 더 최적값을 찾지 못하고 있는 것으로 봐서 대충 최적값을 찾았다고 할 수 있겠다.

50세대에 최대값을 찾았다고 한다면 50 X 20 = 1000번의 계산만으로 최적 값을 찾아낸 것이다. 다시 1번 시뮬레이션에 1초가 걸린다고 본다면 16.6분만에 최적값에 근사한 값을 찾은 것이다.

실제 주식매매에서 최고 수익률을 보이는 매매 조건을 찾고자 한다면 위에서 simulation 함수 안에 주식매매 시뮬레이션 코드를 넣고 최종 수익금액만 return 시켜주면 된다.

댓글 없음:

댓글 쓰기

가장 많이 본 글