본문 바로가기
실전머신러닝

인공지능을 활용한 최적화된 MACD지표로 수익성을 강화하고 스마트하게 투자하기!

by 다빈치스탁 2024. 4. 18.
728x90
반응형

https://youtu.be/CLnfIgnfj5c

안녕하세요, 다빈치스탁입니다. 오늘은 인공지능을 통해 주식투자에서 자주 사용되는 기술적 지표들의 최적 파라메터를 찾는 방법에 대해 알아보겠습니다.

 

기술적 지표는 시장의 패턴을 식별하고 예측하는 유용하지만, 모든 종목에 동일한 설정을 적용하는 것은 때때로 오류를 초래할 있습니다. 예를 들어, MACD 지표의 골든크로스와 데드크로스는 매수와 매도의 신호로 해석되지만, 이러한 신호들은 항상 정확하지 않으며 거짓 신호를 있습니다. 이는 다양한 성장 순환주기를 가진 종목들에 단일 파라메터를 적용하려는 경향 때문입니다.

 

이러한 문제를 해결하기 위해, 강화 학습을 사용하여 각 종목별로 최적의 파라메터 설정을 찾아 보겠습니다. 강화 학습은 에이전트가 환경과 상호 작용하며 보상을 최대화하는 방법을 배우는 머신러닝의 분야로, 최적의 행동 전략을 학습합니다.

 

그럼 지금부터, 삼성전자의 과거 5년간 주가 데이터를 이용하여 강화 학습 모델을 훈련시키고, 다양한 MACD 파라메터를 시험해 최적의 조합을 찾아보겠습니다. 학습을 위해 세금과 수수료는 제외하고, 거래량을 포함시켜 휩소에 대처하며, 골든 데드 크로스를 교차 검증하여 학습이 발산되지 않도록 설계했습니다.

 

이번 영상에서는 기본적인 모델인 A2C를 사용하였지만 PPO, DQN, DDPG, TRPO 등 여러 가지 신경망이 있습니다. 이러한 모델들의 특징과 적합한 사용 용도는 소스코드와 함께 상세히 설명되어 있으니 참고하시길 바랍니다.

import numpy as np
import gym
from gym.spaces import Box, Discrete
from datetime import *

def calculate_macd(df, short_window=12, long_window=26, signal_window=9):
    df.loc[:, "ShortEMA"] = df["Close"].ewm(span=short_window, adjust=False).mean()
    df.loc[:, "LongEMA"] = df["Close"].ewm(span=long_window, adjust=False).mean()
    df.loc[:, "MACD"] = df["ShortEMA"] - df["LongEMA"]
    df.loc[:, "SignalLine"] = df["MACD"].ewm(span=signal_window, adjust=False).mean()
    return df

# 주식 투자 환경 정의하기 : AI 신경망이 학습을 위한 데이터를 받아들이기 위한 환경(가격변동, 기술적지표, 투자금액, 보상,,,)
class StockEnv(gym.Env):   
    def __init__(self, df):
        self.data = df
        self.current_step = 0
        # 다음과 같은 환경 파라미터를 정의할 수 있습니다.
        # 예시: 포트폴리오, 보유 주식 수, 현금, 세금, 수수료 등
        # 순수한 MACD 성능비교를 위해 세금과 수수료는 제외
        #self.fee = 0.015 
        #self.tax = 0.028
        self.portfolio_value = 100000  # 초기 투자 금액
        self.holdings = 0  # 보유 주식 수
        self.cash = self.portfolio_value  # 현금 잔고
        self.action_space = Discrete(3)
        self.observation_space = Box(low=-np.inf, high=np.inf, shape=(6,))
        self.reset()

    def _get_observation(self):
        """
        현재 관측값을 계산하여 반환합니다.
        Returns:
            observation (object): 현재 관측값
        """
        current_price = self.data["Close"].iloc[self.current_step-1]
        # MACD에서는 이용되지 않지만 신경망이 학습할 관측값에는 거래량을 포함
        current_volume = self.data["Volume"].iloc[self.current_step-1]
        ShortEMA = self.data["ShortEMA"].iloc[self.current_step-1]
        LongEMA = self.data["LongEMA"].iloc[self.current_step-1]
        macd = self.data["MACD"].iloc[self.current_step-1]
        signal_line = self.data["SignalLine"].iloc[self.current_step-1]

        # 주식 가격과 MACD 교차 여부를 관측값으로 사용
        observation = [current_price, current_volume, ShortEMA, LongEMA, macd, signal_line]
        return observation
    
    def reset(self):
        """
        환경을 초기 상태로 리셋합니다.
        Returns:
            observation (object): 초기 관측값 (예: 주식 가격, MACD 교차 여부)
        """

        # 초기 상태 설정 (예: 포트폴리오 초기화, 보유 주식 수 초기화, 현금 잔고 초기화 등)
        self.current_step = 0
        self.holdings = 0
        self.portfolio_value = 100000  # 초기 투자 금액
        self.cash = self.portfolio_value
        # 초기 관측값 반환
        observation = self._get_observation()  # MACD 지표값, 주식가격, 거래량 등을 반환
        return observation
     
    def step(self, action):
        """
        주어진 행동을 실행하고 새로운 상태, 보상, 종료 여부를 반환합니다.
        Args:
            action (int): 행동 (예: 매수, 매도, 홀딩 등)

        Returns:
            new_state (object): 새로운 상태 (예: 주식 가격, MACD 교차 여부)
            reward (float): 보상
            done (bool): 종료 여부 (예: 시뮬레이션 종료, 최대 스텝 도달 등)
        """
        # 행동 실행 및 상태 업데이트
        current_price = self.data["Close"].iloc[self.current_step ]
        macd = self.data["MACD"].iloc[self.current_step]
        signal_line = self.data["SignalLine"].iloc[self.current_step]

        if action == 0:  # 매수
            # MACD의 골든 크로스일 때 매수 : 관측값을 임의 해석하지 못하도록 매수 조건을 정의하여 빠른 학습 수렴 유도
            if macd > signal_line and self.data["MACD"].iloc[self.current_step - 1] <= self.data["SignalLine"].iloc[self.current_step - 1]:
                self.holdings += self.cash // current_price
                self.cash -= self.holdings * current_price
        elif action == 1:  # 매도
            # MACD의 데드 크로스일 때 매도 : 관측값을 임의 해석하지 못하도록 매도 조건을 정의하여 빠른 학습 수렴 유도
            if macd < signal_line and self.data["MACD"].iloc[self.current_step - 1] >= self.data["SignalLine"].iloc[self.current_step - 1]:
                self.cash += self.holdings * current_price
                self.holdings = 0

        # 주식 가격 변화에 따른 보상 계산
        prev_price = self.data["Close"].iloc[self.current_step - 1]
        price_change = current_price - prev_price
        reward = price_change * self.holdings

        # 종료 여부 확인 (예: 최대 스텝 도달)
        done = self.current_step >= len(self.data) - 1
        if not done:
            self.current_step += 1
            new_state = self._get_observation()
        else:
            new_state = [0, 0]  # 종료 시 적절한 기본값 설정

        info = {'balance': self.cash,'portfolio':self.holdings,'reward':reward} # 추가 정보

        return new_state, reward, done, info
    
# 차트데이터를 불러오는 라이브러리
import yfinance as yf
import numpy as np
from stable_baselines3 import PPO, A2C
from stable_baselines3.common.vec_env import DummyVecEnv

# 주식 데이터 로드 (예시: 삼성전자 주가 데이터)
df = yf.download("005930.KS", start="2019-04-16", end="2023-04-16")
df = df.dropna()

# Calculate MACD, 볼린저, rsi values
df = calculate_macd(df)

# 환경 초기화 (render_mode 설정)
env = StockEnv(df)
env = DummyVecEnv([lambda: env])

# Initialize the PPO model
model = A2C('MlpPolicy', env, verbose=1)
"""
DQN (Deep Q-Network): 
이산적인 행동 공간을 가진 문제에 적합하며, 비교적 단순한 환경에서 잘 작동합니다. 
Q-Learning에 심층 신경망을 결합한 것으로, 가치 기반 강화 학습 알고리즘입니다. 
안정적인 학습을 위해 경험 리플레이와 고정 Q-타겟을 사용합니다.

DDPG (Deep Deterministic Policy Gradient): 
연속적인 행동 공간을 가진 문제에 적합한 알고리즘으로, Actor-Critic 방식을 사용하며, 
DQN의 아이디어를 확장한 것으로 주로 로봇 제어와 같은 분야에서 사용됩니다.

TRPO (Trust Region Policy Optimization): 
PPO의 전신으로 볼 수 있으며, 정책 업데이트 시 특정 범위 내에서만 업데이트를 허용하여,
학습의 안정성을 추구하지만 계산 비용이 높은 편입니다.

PPO (Proximal Policy Optimization): 
TRPO에 비해 계산 비용이 낮으면서도 안정적인 학습을 제공하므로, 다양한 환경에 널리 사용됩니다.
"""

# 학습
model.learn(total_timesteps=1000, log_interval=10)

# 최적의 MACD 파라미터 찾기
best_macd = None
best_reward = float("-inf")

# 파라메터 최적화
for short_value in np.arange(5, 10, 5):
    for long_value in np.arange(20, 40, 5):
        for signal_value in np.arange(1, 10, 1):
            env.reset()
            # MACD 값을 변경
            env.envs[0].data = calculate_macd(df, short_window=short_value, long_window=long_value, signal_window=signal_value)
            obs = env.reset()
            total_reward = 0

            while True:
                action, _ = model.predict(obs)
                obs, reward, done, _ = env.step(action)
                total_reward += reward
                if done:
                    break

            if total_reward > best_reward:
                best_reward = total_reward
                best_macd = (short_value, long_value, signal_value)
            print(f"MACD value: {(short_value, long_value, signal_value)}, best value: {best_macd}, best reward: {best_reward}")

print(f"Best MACD value: {best_macd}, Best reward: {best_reward}")

# 검증을 위한 주식 데이터 로드 (예시: 삼성전자 주가 데이터)
df_test = yf.download("005930.KS", start="2023-04-16", end="2024-04-16")
df_test = df_test.dropna()

# Calculate MACD, 볼린저, rsi values
df_test = calculate_macd(df_test)

# 환경 초기화 (render_mode 설정)
env = StockEnv(df_test)
env = DummyVecEnv([lambda: env])

env.envs[0].data = calculate_macd(df_test, short_window=12, long_window=26, signal_window=9)
obs = env.reset()
total_reward = 0

while True:
    action, _ = model.predict(obs)
    obs, reward, done, _ = env.step(action)
    total_reward += reward
    if done:
        break

print(f"Performance on recent 1-year data with default value: {total_reward}")

env.envs[0].data = calculate_macd(df_test, short_window=best_macd[0], long_window=best_macd[1], signal_window=best_macd[2])
obs = env.reset()
total_reward = 0

while True:
    action, _ = model.predict(obs)
    obs, reward, done, _ = env.step(action)
    total_reward += reward
    if done:
        break

print(f"Optimized MACD value: {best_macd}, Performance on recent 1-year data: {total_reward}")

학습 결과, 5일 단기, 35일 장기, 8일 시그널이라는 MACD 파라메터 조합이 삼성전자에 가장 효과적임을 발견했습니다. 최적화 이전에는 1.6%의 수익을, 최적화 이후에는 8.4%의 수익을 관측했습니다. 이는 장기적인 성장률을 가진 대형 우량주에는 긴 주기의 골든크로스 전략이 유리할 수 있음을 시사합니다.

 

이외에도 볼린저밴드, 스토캐스틱, 시장강도지표 등 다양한 지표에도 이 방법을 확장할 수 있습니다. 제공된 소스코드를 참고하여 날짜와 주기 등을 실험해보고 최적의 조합을 찾아보시길 바랍니다.

 

이 강의를 통해 새로운 지식을 얻으셨기를 바라며, 다음 강의에서는 더 심도 있는 내용을 다루도록 하겠습니다. 감사합니다!

728x90
반응형

댓글