우리가 우리 생의 모든 부분을 태어날 때부터 죽을 때까지를 다 알고 있다면 우리는 과연 어떤 모습으로 살고 있을 까요?
타임루프 물 애니나 영화에서는 그런 장면이 많이 나옵니다. 자신의 절대 위기나 문제를 이미 경험해 보았기 때문에 모든 문제를 피해 갑니다. 니콜라스 케이지의 2007년 작 넥스트라는 작품이 있습니다. 실제 이 영화는 톰 크루저 주연의 마이너리티 리포트와 같은 소설을 원작으로 하고 있지만 내용은 완전히 다릅니다. 니콜라스 케이지는 한 여자를 운명적으로 좋아하지만 그녀를 만나게 되면 세상은 적어도 미국은 핵폭발로 멸망하게 됩니다. 영화는 니콜라스 케이지의 3분 전을 내다보는 예지 능력의 이야기를 다루지만 2시간 동안의 이야기의 내용은 그래서 아무 일도 안 하고 안 일어났다는 거구 니콜라스 케이지는 몇 번이나 한 여자를 만나서 사랑을 하지만 그녀는 한 번도 니콜라스를 만나지 않았다는 내용의 끝은 좀 허무한 그런 내용입니다. 단 3분 앞을 내다보지만 그 3분만으로 니콜라스 케이지는 아주 쉽게 돈을 벌고 아주 쉽게 FBI를 따 돌리고 세상을 구하지만 결말은 항상 핵폭발이라 그냥 포기하고 혼자 외로운 인생을 산다는 그런 이야기입니다. 우리도 니콜라스 케이지 정도의 예지력은 아니지만 저는 upbit의 이더리움 데이터 1년 6개월치를 가지고 있습니다. Market class의 get_data함수를 보면 아래 부분이 추가되어 있습니다. 주식이던 코인이던 가장 많은 돈을 버는 방법은 가장 고점에서 팔고 가장 저점에서 사고를 계속하면 무조건 돈을 번다는 거죠. python의 rolling함수를 이용하여 앞으로 50 뒤로 50 범위의 수중 가장 높은 값을 미리 구하고 index를 계속 돌리면서 사고팔고를 계속하고 그 중간에는 관망을 하면 어떤 일이 있을 가요? 거의 천문학적인 돈을 벌 수 있습니다. 이런 일이 안 일어나는 이유는 50개의 데이터가 예지의 영역에 있기에 실제로는 가져올 수 없는 값이라는 거죠.
self.df['closemax'] = self.df.close.rolling(window=100, center=True).max()
self.df['closemin'] = self.df.close.rolling(window=100, center=True).min()
위의 내용에서 closemax와 closemin을 미리 계산합니다. 아래 select_action은 제가 말씀 드린 내용으로 매매를 하지만 epsilon값을 구하여 그 값의 하향선에서만 정책적인 매매를 하고 그 이상이 되면 random행동을 한다는 거죠. 예를 들면
섬에 표류한 한 사람이 그 섬에 있는 5가지 열매 중 가장 첫번째 먹어서 죽지 않은 음식을 구출될 때까지 계속 먹게 된다는 이야기와 같은 원리로 머쉰 러닝의 결과도 한 가지 결과 중에서 최고값이 나오면 더 이상은 다른 값을 찾아가지 않습니다. 그래서 프로그래머는 강제로 모험을 하는 확률을 넣어서 모험을 실행하는 계수만큼 모험을 하게 만들어 진짜 최적의 값이 지금 현재 머쉰 러닝이 찾아낸 값인지 계속 질문을 합니다. 아래 함수는 실제 거래에서는 불가능하지만 우리는 이미 거래의 결과를 다 알고 있기 때문에 아래의 방식으로 최고 수익을 내는 방법을 history Series에 저장할 겁니다. 아래는 그 소스입니다. 아래의 행동을 policy action(정책적인 행동)이라고 합니다. 이 부분은 DQN과는 아무 관련이 없으며 인간이 최적의 경로를 찾아가는 MDP(마르코프 실행 프로세스)를 주식이나 코인의 차트에 대입해 본 것입니다. 자 이제는 우리는 최적의 방법으로 미친 수익률을 내는 history를 찾을 수 있습니다. 현실에서는 불가능 하지만 몇천 배의 수익을 발생하게 됩니다. 그야말로 이대로만 되며 누구나 부자가 될 수 있게 죠.
def select_action(df, idx):
try:
global steps_done
sample = random.random()
eps_threshold = EPS_END + (EPS_START - EPS_END) * math.exp(-1. * steps_done / EPS_DECAY)
steps_done += 1
if sample > eps_threshold:
if df.loc[idx, "closemax"] == df.loc[idx, "close"]:
action = 2
return action
elif df.loc[idx, "closemin"] == df.loc[idx, "close"]:
action = 1
return action
else:
return 0
else:
action = random.randrange(action_kind)
return action
except Exception as ex:
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
print("`recall_training -> exception! %s : %s %d" % (str(ex) , fname, exc_tb.tb_lineno))
return 0
def get_chart(market, df, idx, max_data):
img = market.get_chart(idx, max_data=max_data)
if img is None:
return None
if idx > (df.index.max() - 299):
return None
# img = Image.fromarray(np.uint8(cm.gist_earth(plt.io.to_image(fig, format='png')*255)))
# im = Image.fromarray(img, bytes=True)
# im = Image.fromarray(np.uint8(cm.gist_earth(img))/255)
# im = Image.fromarray(np.uint8(img)/255)
# img = img.resize((700, 500), resample=Image.BICUBIC)
# display(img)
# img = Image.fromarray(cm.gist_earth(plt.io.to_image(fig, format='png'), bytes=True))
chart = converter(img).unsqueeze(0).to(device)
return chart
def recall_history(df, account, idx, re_action):
max_price = -np.inf
min_price = np.inf
max_reward = -np.inf
max_rew_step = 0
min_rew_step = 0
save_point_idx = 0
account.reset()
for _, idx in enumerate(df.index, start=idx):
try:
max_step = idx
since = time.time()
if re_action != 0:
action = re_action
re_action = 0
else:
action = select_action(df, idx)
print("idx:%d==>policy_action:%d"%(idx, action))
reward, real_action = account.exec_action(action, idx)
history.loc[idx, "action"] = real_action
max_reward = reward if reward > max_reward else max_reward
history.loc[idx, 'max_rew'] = max_reward
if df.loc[idx, 'close'] > max_price:
max_price = df.loc[idx, 'close']
account.back_up()
max_rew_step = 0
save_point_idx = idx
else:
max_rew_step += 1
if df.loc[idx, 'close'] < min_price:
min_price = df.loc[idx, 'close']
account.back_up()
min_rew_step = 0
save_point_idx = idx
else:
min_rew_step += 1
# if account.unit > 0 and max_rew_step > 50:
# return max_step, True, save_point_idx, 2
# elif account.unit == 0 and min_rew_step > 50:
# return max_step, True, save_point_idx, 1
if account.is_bankrupt():
break
spend = time.time() - since
print("idx:%d used time[%.2f] price[%.2f] unit[%.2f] agent rate:%.05f remind money:%.02f"
% (idx, spend, df.loc[idx, 'close'], account.unit, account.rate, account.balance + account.unit * df.loc[idx, 'close']))
# if epoch % TARGET_UPDATE == 0:
# target_net.load_state_dict(train_net.state_dict())
# torch.save(train_net.state_dict(),"pt/train_net.pt")
# save_file_model(degreeQ)
except Exception as ex:
exc_type, exc_obj, exc_tb = sys.exc_info()
fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
print("`recall_history -> exception! %s : %s %d" % (str(ex) , fname, exc_tb.tb_lineno))
return max_step, False, 0, 0
다음 차수에는 드디어 DQN(CNN) 학습으로 Q(s, a) 함수를 구현해 보도록 하겠습니다. 이전 차수의 상태 값 Sn이 바로 env입니다. 이미 우리는 env를 구했습니다. 그다음은 env에 해당 행동을 적용 했을때 기대값과 실행 후의 상태 Sn을 구해야 하는 문제가 남게 되는데 그것은 Account 클래스를 통해서 구현 해 보도록 하겠습니다. 프로세스는 2개가 동시에 실행되어야 합니다. 지금 우리가 만들고 있는 프로그램은 제 기준으로 1주일에 1 epoch를 도는 굉장히 느린 프로그램입니다. 돈이 좀 더 있다면 DQN 계산 능력을 향상 시키겠지만 현재로는 한 스텝에 평균 8초 정도 걸립니다. 이 시간은 차트 Cn - C(n-1)을 해서 상태 Sn을 구하고 이것을 CNN으로 학습하고 loss를 구하여 optimize까지 실행한 결과 입니다.
방식은 위의 이상적인 history를 먼저 실행하여 실행 행동의 행렬을 구하고 그 다음은 DQN학습을 진행하는 방식입니다.
이렇게 학습된 모델은 어떤 특정 디렉터리(pt/training_net.pt) 파일로 저장이 되고 학습을 전혀 하지 않고 DQN으로 행동만을 받아서 실행하는 또 다른 프로세스에서 모델 파일을 읽어서 셋업을 한 다음 실 데이터로 거래를 하는 형식으로 구성이 될 겁니다. 처음에는 진짜 리얼 데이터가 아니라 학습 데이터로 실행을 할 것이고 이후 학습이 되는대로 실 데이터로 실전을 할 것입니다.
'python > 자동매매 프로그램' 카테고리의 다른 글
강화학습을 이용한 비트코인 매매프로그램(8) - wsl이용하기 (3) | 2022.12.12 |
---|---|
강화학습을 이용한 비트코인 매매프로그램(6)-Buroto Force학습 (17) | 2022.12.04 |
강화학습을 이용한 비트코인 매매프로그램(4)-LSTM의 주가분석 문제점 (1) | 2022.11.30 |
강화학습을 이용한 비트코인 매매프로그램(3) - 데이터로 차트 그리기 (1) | 2022.11.28 |
강화학습을 이용한 비트코인 매매프로그램(2) - 개발 환경 셋팅 (2) | 2022.11.28 |