RLTrader의 에이전트 모듈 개발
2020-03-31 • rltrader • stock, 주식투자, reinforcement learning, rl, 강화학습, agent, 에이전트 • 10 min read
에이전트 모듈에 포함된 에이전트 클래스(Agent)의 속성과 함수를 살펴보고 파이썬으로 구현한 소스 코드를 상세히 확인합니다.
에이전트 모듈의 주요 속성과 함수
에이전트 모듈(agent.py)은 투자 행동을 수행하고 투자금과 보유 주식을 관리하기 위한 에이전트 클래스(Agent)를 가집니다. Agent
클래스의 주요 속성과 함수는 다음과 같습니다.
속성
- initial_balance: 초기 투자금
- balance: 현금 잔고
- num_stocks: 보유 주식 수
- portfolio_value: 포트폴리오 가치(투자금 잔고 + 주식 현재가 * 보유 주식 수)
- num_buy: 매수 횟수
- num_sell: 매도 횟수
- num_hold: 관망 횟수
- immediate_reward: 즉시 보상
- profitloss: 현재 손익
- base_profitloss: 직전 지연 보상 이후 손익
- exploration_base: 탐험 행동 결정 기준
함수
- reset(): 에이전트의 상태를 초기화
- set_balance(): 초기 자본금을 설정
- get_states(): 에이전트 상태를 획득
- decide_action(): 탐험 또는 정책 신경망에 의한 행동 결정
- validate_action(): 행동의 유효성 판단
- decide_trading_unit(): 매수 또는 매도할 주식 수 결정
- act(): 행동 수행
에이전트 모듈은 환경 모듈보다는 규모 면에서 더 큽니다. 그래서 소스 코드를 한번에 보여주는 것이 효과적이지 않아서 상수 선언 부분 및 함수 단위로 설명합니다.
코드 조각 1: 에이전트 클래스의 상수 선언
다음은 에이전트 클래스의 상수 선언부의 소스 코드입니다.
import numpy as np
import utils
class Agent:
# 에이전트 상태가 구성하는 값 개수
STATE_DIM = 2 # 주식 보유 비율, 포트폴리오 가치 비율
# 매매 수수료 및 세금
TRADING_CHARGE = 0.00015 # 거래 수수료 (일반적으로 0.015%)
TRADING_TAX = 0.0025 # 거래세 (실제 0.25%)
# TRADING_CHARGE = 0 # 거래 수수료 미적용
# TRADING_TAX = 0 # 거래세 미적용
# 행동
ACTION_BUY = 0 # 매수
ACTION_SELL = 1 # 매도
ACTION_HOLD = 2 # 홀딩
# 인공 신경망에서 확률을 구할 행동들
ACTIONS = [ACTION_BUY, ACTION_SELL]
NUM_ACTIONS = len(ACTIONS) # 인공 신경망에서 고려할 출력값의 개수
에이전트 모듈은 NumPy를 사용하므로 우선 임포트합니다. numpy 모듈을 np로 줄여서 정의했습니다.
파이썬 팁: import는 다른 패키지, 모듈, 클래스, 함수에 접근할 수 있게 연결하는 명령어입니다. as 키워드로 임포트한 패키지, 모듈, 클래스, 함수 등을 다른 이름으로 지정할 수도 있습니다. 예를 들어,
import numpy as np
는 numpy 모듈을 np로 사용하겠다는 의미입니다.
Agent
클래스는 여러 상수를 사용합니다. 우선 STATE_DIM
은 에이전트 상태의 차원입니다. RLTrader에서의 에이전트 상태는 주식 보유 비율과 포트폴리오 가치 비율로 2개의 값을 가지므로 2차원입니다. 에이전트의 상태에 대해서는 뒤에서 더 설명합니다.
에이전트는 매수와 매도를 수행하는 주체이기 때문에 매매 수수료 및 세금을 상수로 가집니다. TRADING_CHARGE
는 매수 및 매도 수수료를, TRADING_TAX
는 거래세를 의미합니다.
사실 실제 주식투자에서는 거래 수수료와 거래세는 수익성을 좌우하는 큰 요소입니다. 이 책에서의 주식투자 시뮬레이션은 일봉 차트로 매일 주식을 매매하므로 거래 횟수가 아주 많습니다. 이렇게 거래를 많이 하는 것은 현실적으로 좋은 방법이 아닙니다.
이 책은 주식투자 자체에 관점을 맞춘 것이 아니라 개인이 딥러닝을 주식투자에 (시험적으로) 적용해 볼 수 있게 필요한 프로그래밍 지식을 전달하는 데 집중하고 있습니다. 따라서 이 책에서는 거래 수수료와 거래세를 고려하지 않습니다. 실전 투자에 RLTrader를 적용하고자 한다면 거래 횟수를 주 1회, 월 1회, 연 1회 등으로 줄이고 일봉 차트뿐만 아니라 다양한 지표를 활용해야 할 것입니다. 일반적으로 거래 수수료는 0.015%, 거래세는 0.25%로 생각하면 됩니다. 거래 수수료와 거래세를 고려하고 싶지 않다면 TRADING_CHARGE
와 TRADING_TAX
를 모두 0으로 정하면 됩니다.
에이전트가 할 수 있는 행동은 매수, 매도, 관망입니다. 에이전트는 이 행동들에 특정 값을 부여해 상수로 가집니다. ACTION_BUY
는 매수 행동을 의미하며 0을 값으로 가지고, ACTION_SELL
은 매도 행동이고 1을 할당하고, ACTION_HOLD
는 매수도 매도도 하지 않는 관망 행동으로 2의 값을 할당합니다. 이 행동들 중에서 정책 신경망이 확률을 계산할 행동들을 ACTIONS
리스트에 저장합니다. RLTrader는 매수와 매도에 대한 확률만 계산하고 매수와 매도 중에서 결정한 행동을 할 수 없을 때만 관망 행동을 합니다.
코드 조각 2: 에이전트 클래스의 생성자
이제 에이전트 클래스의 생성자 함수를 살펴보겠습니다.
def __init__(
self, environment, min_trading_unit=1, max_trading_unit=2,
delayed_reward_threshold=.05):
# Environment 객체
# 현재 주식 가격을 가져오기 위해 환경 참조
self.environment = environment
# 최소 매매 단위, 최대 매매 단위, 지연보상 임계치
self.min_trading_unit = min_trading_unit # 최소 단일 거래 단위
self.max_trading_unit = max_trading_unit # 최대 단일 거래 단위
# 지연보상 임계치
self.delayed_reward_threshold = delayed_reward_threshold
# Agent 클래스의 속성
self.initial_balance = 0 # 초기 자본금
self.balance = 0 # 현재 현금 잔고
self.num_stocks = 0 # 보유 주식 수
# PV = balance + num_stocks * {현재 주식 가격}
self.portfolio_value = 0
self.base_portfolio_value = 0 # 직전 학습 시점의 PV
self.num_buy = 0 # 매수 횟수
self.num_sell = 0 # 매도 횟수
self.num_hold = 0 # 홀딩 횟수
self.immediate_reward = 0 # 즉시 보상
self.profitloss = 0 # 현재 손익
self.base_profitloss = 0 # 직전 지연 보상 이후 손익
self.exploration_base = 0 # 탐험 행동 결정 기준
# Agent 클래스의 상태
self.ratio_hold = 0 # 주식 보유 비율
self.ratio_portfolio_value = 0 # 포트폴리오 가치 비율
생성자의 매개변수로 environment
, min_trading_unit
, max_trading_unit
, delayed_reward_threshold
를 받습니다. environment
는 Environment
클래스의 객체입니다. min_trading_unit
은 최소한의 매매 단위고 max_trading_unit
은 최대의 매매 단위입니다. max_trading_unit
을 크게 잡으면 결정한 행동에 대한 확신이 높을 때 더 많이 매수 또는 매도할 수 있게 설계했습니다. delayed_reward_threshold
는 지연 보상 임계치로, 손익률이 이 값을 넘으면 지연 보상이 발생합니다.
initial_balance
는 초기 자본금으로, 투자 시작 시점의 보유 현금입니다. balance
는 현재의 현금 잔고입니다. num_stocks는 현재의 보유 주식 수입니다. portfolio_value
는 포트폴리오 가치로, 보유 현금과 보유 주식 수에 현재 주가를 곱해 더한 값입니다. base_portfolio_value
는 목표 수익률 또는 기준 손실률을 달성하기 전의 과거 포트폴리오 가치로 현재 포트폴리오 가치가 증가했는지 또는 감소했는지를 비교할 기준이 됩니다. exploration_base
는 탐험의 기준 확률로, 탐험을 하더라도 매수를 기조로 할지, 매도를 기조로 할지를 정하는 것입니다. 뒤에서 이 변수가 어떻게 쓰이는지 더 자세히 알아보겠습니다.
num_buy
, num_sell
, num_hold는
각각 에이전트가 행한 매수, 매도, 관망 횟수입니다. immediate_reward
는 에이전트가 가장 최근 행한 행동에 대한 즉시 보상 값입니다. 신경망에 입력으로 들어가는 샘플에 에이전트의 상태도 포함됩니다. 여기서 에이전트 상태는 주식 보유 비율을 담는 ratio_hold
와 포트폴리오 가치 비율을 담는 ratio_portfolio_value
를 가집니다.
코드 조각 3: 에이전트 클래스의 함수
에이전트 클래스의 속성값 획득(Get) 함수와 설정(Set) 함수를 살펴보겠습니다.
def reset(self):
self.balance = self.initial_balance
self.num_stocks = 0
self.portfolio_value = self.initial_balance
self.base_portfolio_value = self.initial_balance
self.num_buy = 0
self.num_sell = 0
self.num_hold = 0
self.immediate_reward = 0
self.ratio_hold = 0
self.ratio_portfolio_value = 0
def reset_exploration(self):
self.exploration_base = 0.5 + np.random.rand() / 2
def set_balance(self, balance):
self.initial_balance = balance
def get_states(self):
self.ratio_hold = self.num_stocks / int(
self.portfolio_value / self.environment.get_price())
self.ratio_portfolio_value = (
self.portfolio_value / self.base_portfolio_value
)
return (
self.ratio_hold,
self.ratio_portfolio_value
)
reset()
함수는 Agent 클래스의 속성들을 초기화합니다. 학습 단계에서 한 에포크마다 에이전트의 상태를 초기화해야 합니다. reset_exploration()
함수는 탐험의 기준이 되는 exploration_base
를 새로 정하는 함수입니다. 매수 탐험을 선호하기 위해서 50% 매수 탐험 확률을 미리 부여했습니다. set_balance() 함수는 에이전트의 초기 자본금을 설정합니다. get_states()
함수는 에이전트의 상태를 반환합니다. 상태는 다음 두 값으로 구성됩니다.
주식 보유 비율 = 보유 주식 수/(포트폴리오 가치/현재 주가)
주식 보유 비율은 현재 상태에서 가장 많이 가질 수 있는 주식 수 대비 현재 보유한 주식의 비율입니다. 이 값이 0이면 주식을 하나도 보유하지 않은 것이고 0.5이면 최대 가질 수 있는 주식 대비 절반의 주식을 보유하고 있는 것이며 1이면 최대로 주식을 보유하고 있는 것입니다. 아무래도 주식 수가 너무 적으면 매수의 관점에서 투자에 임하고 주식 수가 너무 많으면 매도의 관점에서 투자에 임하게 됩니다. 즉, 보유 주식 수를 투자 행동 결정에 영향을 주기 위해 정책 신경망의 입력에 포함합니다.
포트폴리오 가치 비율 = 포트폴리오 가치/기준 포트폴리오 가치
포트폴리오 가치 비율은 기준 포트폴리오 가치 대비 현재 포트폴리오 가치의 비율입니다. 기준 포트폴리오 가치는 직전에 목표 수익 또는 손익률을 달성했을 때의 포트폴리오 가치입니다. 이 값은 현재 수익이 발생했는지 손실이 발생했는지를 판단할 수 있는 값입니다. 포트폴리오 가치 비율이 0에 가까우면 손실이 큰 것이고 1보다 크면 수익이 발생했다는 뜻입니다. 수익률이 목표 수익률에 가까우면 매도의 관점에서 투자하고는 합니다. 수익률이 투자 행동 결정에 영향을 줄 수 있기 때문에 이 값을 에이전트의 상태로 정하고 정책 신경망의 입력값으로 포함합니다.
파이썬 팁:
int()
는 숫자를 정수로 캐스팅(형식 변환)하는 파이썬 내장 함수입니다. 예를 들어,int(0.5)
는 0을,int(1.3)
은 1을 반환합니다.파이썬 팁: 파이썬에서
(a, b, …)
는 튜플(tuple)을 의미합니다. 튜플은 리스트[a, b, …]
와 비슷합니다. 차이점은 튜플은 요소를 추가, 변경, 삭제하는 처리가 불가능하고 리스트는 가능하다는 것입니다. 튜플이 메모리를 적게 사용하기 때문에 요소를 수정할 필요가 없으면 튜플을 사용하는 것이 효율적입니다.
다음은 에이전트가 행동을 결정하고 결정한 행동의 유효성을 검사하는 함수를 보여줍니다.
def decide_action(self, pred_value, pred_policy, epsilon):
confidence = 0.
pred = pred_policy
if pred is None:
pred = pred_value
if pred is None:
# 예측 값이 없을 경우 탐험
epsilon = 1
else:
# 값이 모두 같은 경우 탐험
maxpred = np.max(pred)
if (pred == maxpred).all():
epsilon = 1
# 탐험 결정
if np.random.rand() < epsilon:
exploration = True
if np.random.rand() < self.exploration_base:
action = self.ACTION_BUY
else:
action = np.random.randint(self.NUM_ACTIONS - 1) + 1
else:
exploration = False
action = np.argmax(pred)
confidence = .5
if pred_policy is not None:
confidence = pred[action]
elif pred_value is not None:
confidence = utils.sigmoid(pred[action])
return action, confidence, exploration
decide_action()
은 입력으로 들어온 엡실론(Epsilon)의 확률로 무작위로 행동을 결정하고 그렇지 않은 경우에 신경망을 통해 행동을 결정합니다.
0에서 1 사이의 랜덤 값을 생성하고 이 값이 엡실론보다 작으면 무작위로 행동을 결정합니다. 탐험의 기조로 작용하는 exploration_base
는 에포크마다 새로 결정됩니다. exploration_base
가 1에 가까우면 탐험할 때 매수를 더 많이 선택할 것입니다. 반대로 exploration_base
가 0에 가까우면 매도 탐험을 더 많이 할 것입니다. 이렇게 기조를 정하는 이유는 하나의 에포크에서 [매수, 매도, 매수, 매도]와 같은 효과적이지 않은 탐험을 하지 않게 하기 위해서입니다.
여기서 NUM_ACTIONS
는 2의 값을 가집니다. 그러므로 랜덤으로 행동을 결정하면 0(매수) 또는 1(매도)의 값을 결정하는 것입니다.
탐험을 하지 않는 경우 신경망을 통해 행동을 결정합니다. 정책 신경망의 출력인 pred_policy
가 있으면 pred_policy
로 행동을 결정하고, 없으면 pred_value
로 행동을 결정합니다. DQNLearner의 경우 pred_policy
가 None
이므로 pred_value
로 행동을 결정합니다. 신경망 클래스의 함수는 5.5절에서 상세하게 다룹니다.
파이썬 팁: NumPy의
random
모듈은 랜덤 값 생성을 위한rand()
함수를 제공합니다. 이 함수는 0에서 1 사이의 값을 생성해 반환합니다.randint(low, high=None)
함수는high
를 넣지 않은 경우 0에서low
사이의 정수를 랜덤으로 생성하고high
를 넣은 경우low
에서high
사이의 정수를 생성합니다.파이썬 팁: NumPy의
argmax(array)
함수는 입력으로 들어온array
에서 가장 큰 값의 위치를 반환합니다. 예를 들어,array
가[3, 5, 7, 0, -3]
이면 가장 큰 값은 7이므로 그 위치인 2를 반환합니다. 파이썬에서 위치(index)는 0부터 시작합니다.
다음은 결정한 행동의 유효성을 검사하는 함수입니다.
def validate_action(self, action):
if action == Agent.ACTION_BUY:
# 적어도 1주를 살 수 있는지 확인
if self.balance < self.environment.get_price() * (
1 + self.TRADING_CHARGE) * self.min_trading_unit:
return False
elif action == Agent.ACTION_SELL:
# 주식 잔고가 있는지 확인
if self.num_stocks <= 0:
return False
return True
RLTrader에서는 신용 매수나 공매도는 고려하지 않습니다. 신용 매수는 잔금이 부족하더라도 돈을 빌려서 매수를 하는 것이고 공매도는 주식을 보유하고 있지 않더라도 미리 매도하고 나중에서 주식을 사서 갚는 방식의 거래입니다.
그러므로 이렇게 결정한 행동은 특정 상황에서는 수행할 수 없을 수도 있습니다. 예를 들어, 매수를 결정했는데 잔금이 1주 매수하기에도 부족한 경우나 매도를 결정했는데 보유하고 있는 주식이 하나도 없는 경우에 결정한 행동을 수행할 수 없습니다. 그래서 결정한 행동을 수행할 수 있는지를 확인하기 위해서 validate_action()
함수를 사용합니다.
매수 결정에 대해서 적어도 1주를 살 수 있는 잔금이 있는지 확인합니다. 이때 거래 수수료까지 고려합니다. 매도 결정에 대해서 주식 잔고가 있는지 확인합니다. num_stocks
변수에 현재 보유한 주식 수가 저장됩니다.
다음 코드조각에서 결정한 행동의 신뢰(confidence)에 따라서 매수 또는 매도의 단위를 조정하는 함수를 살펴봅니다.
def decide_trading_unit(self, confidence):
if np.isnan(confidence):
return self.min_trading_unit
added_traiding = max(min(
int(confidence * (self`.max_trading_uni`t -
self.min_trading_unit)),
self.max_trading_unit-self.min_trading_unit
), 0)
return self.min_trading_unit + added_traiding
decide_trading_unit()
함수는 정책 신경망이 결정한 행동의 신뢰가 높을수록 매수 또는 매도하는 단위를 크게 정해줍니다. 높은 신뢰로 매수를 결정했으면 그에 맞게 더 많은 주식을 매수하고 높은 신뢰로 매도를 결정했으면 더 많은 보유 주식을 매도하는 것입니다.
다음으로 투자 행동을 수행하는 함수의 초반부를 살펴봅니다.
def act(self, action, confidence):
if not self.validate_action(action):
action = Agent.ACTION_HOLD
# 환경에서 현재 가격 얻기
curr_price = self.environment.get_price()
# 즉시 보상 초기화
self.immediate_reward = 0
act()
함수는 에이전트가 결정한 행동을 수행합니다. 인자로는 action과 confidence를 받습니다. action은 탐험 또는 정책 신경망을 통해 결정한 행동으로 매수와 매도를 의미하는 0 또는 1의 값입니다. confidence는 정책 신경망을 통해 결정한 경우 결정한 행동에 대한 소프트맥스 확률 값입니다.
먼저 이 행동을 할 수 있는지 확인하고 할 수 없는 경우 아무 행동도 하지 않게 관망(hold)합니다. 그리고 환경 객체에서 현재의 주가를 받아옵니다. 이 가격은 매수 금액, 매도 금액, 포트폴리오 가치를 계산할 때 사용됩니다. 즉시 보상은 에이전트가 행동할 때마다 결정되기 때문에 초기화합니다.
act()
함수의 매수 행동 수행 부분을 이어서 살펴봅니다.
# 매수
if action == Agent.ACTION_BUY:
# 매수할 단위를 판단
trading_unit = self.decide_trading_unit(confidence)
balance = (
self.balance - curr_price * (1 + self.TRADING_CHARGE) \
* trading_unit
)
# 보유 현금이 모자랄 경우 보유 현금으로 가능한 만큼 최대한 매수
if balance < 0:
trading_unit = max(
min(
int(self.balance / (
curr_price * (1 + self.TRADING_CHARGE))),
self.max_trading_unit
),
self.min_trading_unit
)
# 수수료를 적용해 총 매수 금액 산정
invest_amount = curr_price * (1 + self.TRADING_CHARGE) \
* trading_unit
if invest_amount > 0:
self.balance -= invest_amount # 보유 현금을 갱신
self.num_stocks += trading_unit # 보유 주식 수를 갱신
self.num_buy += 1 # 매수 횟수 증가
먼저 수행할 행동이 매수인지를 확인하고 매수 단위(살 주식 수)를 정합니다. 그리고 매수 후의 잔금을 확인합니다. 신용 매수를 고려하지 않으므로 매수 후 잔금이 0보다 적을 수는 없습니다.
결정한 매수 단위가 최대 단일 거래 단위를 넘어가면 최대 단일 거래 단위로 제한하고 최소 거래 단위보다 최소한 1주를 매수합니다.
파이썬 팁: 파이썬의 내장 함수인
min(a, b)
와max(a, b)
함수에 대해 알아보겠습니다.min(a, b)
는a
와b
중에서 작은 값을 반환합니다. 반대로max(a, b)
는a
와b
중에서 큰 값을 반환합니다. 예를 들어,min(5, 10)
은 5를,max(-5, -10)
은 -5를 반환합니다.
매수할 단위에 수수료를 적용해 총 투자 금액을 계산합니다. 그리고 이 금액을 현재 잔금에서 빼고 주식 보유 수를 투자 단위만큼 늘려 줍니다. 그리고 통계 정보인 num_buy
를 1만큼 증가시킵니다.
이어서 매도와 관망에 대한 부분을 살펴보겠습니다.
# 매도
elif action == Agent.ACTION_SELL:
# 매도할 단위를 판단
trading_unit = self.decide_trading_unit(confidence)
# 보유 주식이 모자랄 경우 가능한 만큼 최대한 매도
trading_unit = min(trading_unit, self.num_stocks)
# 매도
invest_amount = curr_price * (
1 - (self.TRADING_TAX + self.TRADING_CHARGE)) \
* trading_unit
if invest_amount > 0:
self.num_stocks -= trading_unit # 보유 주식 수를 갱신
self.balance += invest_amount # 보유 현금을 갱신
self.num_sell += 1 # 매도 횟수 증가
# 홀딩
elif action == Agent.ACTION_HOLD:
self.num_hold += 1 # 홀딩 횟수 증가
수행한 행동이 매도인지 확인하고 매수 때와 마찬가지로 투자 단위를 판단합니다. 여기서는 투자 단위가 매도할 주식 수가 됩니다. 현재 가지고 있는 주식 수보다 결정한 매도 단위가 많으면 안 되기 때문에 현재 보유 주식 수를 최대 매도 단위로 제한합니다.
다음으로 매도 금액을 계산합니다. 이때 정해준 매도 수수료와 거래세를 모두 고려해 계산합니다. 그리고 보유 주식 수를 매도한 만큼 빼고 매도해 현금화한 금액을 잔고에 더해줍니다. 통계를 위한 변수인 num_sell
을 1만큼 증가시킵니다.
관망(hold)은 결정한 매수(buy)나 매도(sell) 행동을 수행할 수 없는 경우에 적용됩니다. 관망은 아무것도 하지 않는 것이기 때문에 보유 주식 수나 잔고에 영향을 미치지 않습니다. 대신 가격 변동은 있을 수 있으므로 포트폴리오 가치는 변동됩니다. 관망 횟수인 num_hold
를 1만큼 증가시킵니다.
이어서 act()
함수의 후반부를 살펴봅니다.
# 포트폴리오 가치 갱신
self.portfolio_value = self.balance + curr_price \
* self.num_stocks
self.profitloss = (
(self.portfolio_value - self.initial_balance) \
/ self.initial_balance
)
# 즉시 보상 - 수익률
self.immediate_reward = self.profitloss
# 지연 보상 - 익절, 손절 기준
delayed_reward = 0
self.base_profitloss = (
(self.portfolio_value - self.base_portfolio_value) \
/ self.base_portfolio_value
)
if self.base_profitloss > self.delayed_reward_threshold or \
self.base_profitloss < -self.delayed_reward_threshold:
# 목표 수익률 달성하여 기준 포트폴리오 가치 갱신
# 또는 손실 기준치를 초과하여 기준 포트폴리오 가치 갱신
self.base_portfolio_value = self.portfolio_value
delayed_reward = self.immediate_reward
else:
delayed_reward = 0
return self.immediate_reward, delayed_reward
이렇게 매수나 매도, 관망을 하면 포트폴리오 가치에 변동이 생깁니다. 포트폴리오 가치는 잔고, 주식 보유 수, 현재 주식 가격에 의해 결정됩니다. 기준 포트폴리오 가치에서 현재 포트폴리오 가치의 등락률을 계산합니다. 기준 포트폴리오 가치는 과거에 학습을 수행한 시점의 포트폴리오 가치를 의미한다고 했습니다.
즉시 보상은 기준 포트폴리오 가치 대비 현재 포트폴리오 가치의 비율로 정했습니다. 지연 보상은 즉시 보상이 지연 보상 임계치인 delayed_reward_threshold
를 초과하는 경우 즉시 보상 값으로 정하고 그 외의 경우는 0으로 설정됩니다. RLTrader는 지연 보상이 0이 아닌 경우 학습을 수행합니다. 즉, 지연 보상 임계치를 초과하는 수익이 났으면 이전에 했던 행동들을 잘했다고 보고 긍정적으로(positive) 학습하고, 지연 보상 임계치를 초과하는 손실이 났으면 이전 행동들에 문제가 있다고 보고 부정적으로(negative) 학습할 것입니다.