将强化学习应用到量化投资中实战篇(代理模块开发)
文章目录
1.主要属性和功能
代理模块 (agent.py) 有一个代理类 (Agent),用于执行投资操作和管理投资和持股。Agent该类的主要属性和功能如下。
1.1属性
- initial_balance:初始投资
- balance:现金余额
- num_stocks:持有的股票数量
- portfolio_value:投资组合价值(投资余额+当前股票价格*持有的股票数量)
- num_buy:购买次数
- num_sell:销售数量
- num_hold:管网数量
- immediate_reward:立即奖励
- profitloss:当期损益
- base_profitloss:上一次延迟补偿后的盈亏
- explore_base:确定探索行为的标准
1.2 功能
- reset():重置代理的状态
- set_balance():设置初始资金
- get_states():获取代理状态
- decide_action():通过探索性或策略神经网络决定一个动作
- validate_action():验证动作
- decide_trading_unit():决定购买或出售多少股
- act():执行一个动作
2.代理类的常量声明
下面是代理类的常量声明部分的源码。
代理类:常量声明部分
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) # 神经网络中要考虑的输出数量
Agent 类使用几个常量。首先,STATE_DIM 是代理状态的维度。 RLTrader 中的代理状态是二维的,因为它有两个值:持股比率和投资组合价值比率。代理的状态将在后面进一步讨论。
由于代理是执行买卖的实体,因此它具有恒定的交易费用和税收。 TRADING_CHARGE 表示买卖费用,TRADING_TAX 表示交易税。
事实上,在真实的股票投资中,交易费用和交易税是决定盈利能力的重要因素。本文中的股票投资模拟每天都在日线图上交易股票,所以交易次数非常多。在实践中,做这么多交易并不是一个好的做法。
本书不专注于股票投资本身,而是专注于提供个人将深度学习应用于股票投资(实验)所需的知识。因此,本文不考虑交易费用和交易税。如果您想将 RLTrader 应用于实际投资,您需要将交易次数减少到每周一次、每月一次或每年一次,并使用各种指标以及日线图。一般来说,您可以将交易费视为 0.015%,将交易税视为 0.25%。如果不想考虑交易费用和交易税,可以将 TRADING_CHARGE 和 TRADING_TAX 都设置为 0。
代理可以采取的行动是买入、卖出和持有。 代理为这些动作分配一个特定的值并将它们保持为常量。 ACTION_BUY 表示买入动作,值为 0,ACTION_SELL 是卖出动作,赋值为 1,ACTION_HOLD 表示买入或卖出动作,赋值为 2。 在这些动作中,策略神经网络将计算概率的动作存储在 ACTIONS 列表中。 RLTrader 只计算买入和卖出的概率。
3.代理类的构造函数
现在让我们看一下代理类的构造函数。
代理类:构造函数
def __init__(
self, environment, min_trading_unit=1, max_trading_unit=2,
delayed_reward_threshold=.05):
# 环境对象
# 获取当前股价的参考环境
self.environment = 环境
# 最小交易单位、最大交易单位、延迟补偿阈值
self.min_trading_unit = min_trading_unit # 最小单个交易单位
self.max_trading_unit = max_trading_unit # 最大单个交易单位
# 延迟补偿阈值
self.delayed_reward_threshold = delay_reward_threshold
# 代理类的属性
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 # 确定探索行为的标准
# 代理类的状态
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,以便您在对自己的决定充满信心时可以买入或卖出更多。 delay_reward_threshold 为延迟补偿阈值,当盈亏超过此值时,将导致延迟补偿。
initial_balance 是初始资本,即投资开始时持有的现金金额。 balance 是您当前的现金余额。 num_stocks 是当前持有的股票数量。投资组合价值是投资组合价值,等于您持有的现金数量和您持有的股票数量乘以当前股价。 base_portfolio_value 是比较当前投资组合价值与达到目标回报率或基本损失率之前投资组合的历史价值是否增加或减少的基础。 explore_base 是探索的基础概率,根据探索决定买还是卖。稍后我们将更详细地了解如何使用此变量。
num_buy、num_sell 和 num_hold 分别是代理进行的买入、卖出和等待的次数。 immediate_reward 是代理最近采取的行动的即时奖励值。作为输入进入神经网络的样本还包含代理的状态。这里,代理状态具有包含持股比率的 ratio_hold 和包含投资组合价值比率的 ratio_portfolio_value。
3.代理类中的函数
下面来看看agent类的Get函数和Set函数。
3.1 初始化函数
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()该函数初始化代理类的属性。在训练阶段,我们需要在每个 epoch 初始化代理的状态。函数是新确定reset_exploration()探索基础的函数。exploration_base为了有利于探险,训练初始值设置50%的桥接探险概率。set_balance() 函数设置代理的初始资本。get_states()该函数返回代理的状态。一个状态由两个值组成:
持股比率=所持股份数量/(投资组合价值/当前价格)
持股比率是您当前持有的股份与您在当前状态下最多可以拥有的股份数量的比率。值 0 表示您不拥有任何股份,值 0.5 表示您拥有最大股份数量的一半,值 1 表示您拥有最多股份。如果股票数量太少,你从买入角度投资,如果股票数量太多,你从卖出角度投资。换句话说,我们将持有的股票数量作为我们政策神经网络的输入,以影响我们的投资行为决策。
投资组合价值比率 = 投资组合价值/基础投资组合价值
投资组合价值比率是当前投资组合价值与基准投资组合价值的比率。基准投资组合价值是投资组合在刚刚达到目标回报或损益比率时的价值。该值是可用于确定是否已获得利润或已发生亏损的值。投资组合价值比率接近 0 表示损失较大,而投资组合价值比率大于 1 表示回报。如果回报率接近目标回报率,你可以投资一个卖点。我们将此值设置为代理的状态,并将其作为我们的策略神经网络的输入,因为回报率会影响投资行为决策。
Python 提示:int()是一个 Python 内置函数,可将数字转换(类型转换)为整数。例如,int(0.5)返回 0 并int(1.3)返回 1。
Python 提示:在 Python 中,(a, b, …)它表示元组。[a, b, …]元组类似于列表。不同之处在于元组不能处理添加、更改或删除元素,而列表可以。由于元组使用较少的内存,如果不需要修改元素,使用元组是有效的。
3.2 行为决策函数
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()以 ε ε ε 的概率作为输入随机确定行为,否则通过神经网络确定行为。生成一个介于 0 和 1 之间的随机值,如果该值小于 epsilon,则随机确定行为。,作为探索的基础,exploration_base对于每个时期都是新确定的。exploration_base如果接近1,探索时会选择多买。反之exploration_base,如果接近 0,探索时选择多出售。这样设置的原因是为了避免在一个epoch内进行[buy,sell,buy,sell]这样的无效探索。
如果您不进行探索,则可以使用神经网络来确定您的行为。如果有pred_policy(预测策略值),行为则由pred_policy决定。没有,则行为由pred_value(预测价值值)决定。对于 DQNLearner,pred_policy 为 None,因此 pred_value 决定行为。
Python 提示:NumPy 的模块提供了random生成随机值的函数。rand()此函数生成并返回一个介于 0 和 1 之间的值。randint(low, high=None)该函数随机生成从 0 到 when 以及fromhigh和to when的整数,不包括在内。lowhighlowhigh
Python 提示:NumPyargmax(array)函数array返回最大值的位置 例如,如果array是,[3, 5, 7, 0, -3]最大值是 7,所以它返回 2,它的位置。在 Python 中,索引从 0 开始
3.3 行为验证函数
行为验证功能实现
def validate_action(self, action):
if action == Agent.ACTION_BUY:
# 账户余额是否能够买入
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
如果您决定购买而余额不足以购买一股,或者如果您决定出售而您没有股份,则您无法执行您已决定的行动。因此,为了检查决定的动作是否可以执行,使用了 validate_action() 函数。
确保您有足够的余额在您的购买决定中至少购买一股。此时还考虑到交易费用。确保您的卖出决定有库存余额。 num_stocks 变量存储当前持有的股票数量。
3.4 行为执行函数
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是通过探索或策略神经网络确定的行为,值为0或1,表示买入或卖出。 如果通过策略神经网络确定,置信度是确定动作的 softmax 概率值。
首先,我们检查我们是否可以做到这一点,如果我们不能,我们坚持什么都不做。 并从环境对象中获取当前股票价格。 此价格用于计算买入金额、卖出金额和投资组合价值。 即时奖励会重置,因为它是在每次代理采取行动时确定的。
接下来,我们来看看 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 # 增加购买次数
首先我们确定要执行的行为是否是买入,然后我们确定要购买的单位数量(要购买的股票数量)。 并在购买后检查余额此购买后的余额不能小于零。
如果确定的买入单位超过最大单笔交易单位,我们将其限制在最大单笔交易单位,并在最小交易单位以上买入至少一股。
总投资是通过对要购买的单位收取费用来计算的。 然后从当前余额中减去该金额,从而增加投资单位持有的股份数量。 而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。
如果决定的买入或卖出行动无法执行,则应用暂停。由于持有股份不需要做什么,不会影响所持有的股票数量或余额。相反,投资组合的价值会随着价格的波动而波动。最后将统计持有行为数量 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,则延迟补偿设置为立即补偿值,否则延迟补偿设置为零。如果延迟补偿不为零,RLTrader 会进行训练。换句话说,如果一个利润超过了延迟奖励阈值,则之前的行为被认为是好的并且是积极学习的。