(9-4)基于深度强化学习的量化交易策略(OpenAI Baselines +FinRL+DRL+PyPortfolioOpt):构建交易环境

9.7  构建交易环境

考虑到自动股票交易任务的随机性和互动性,在本项目中将金融任务建模为马尔可夫决策过程(Markov Decision Process,MDP)问题。在训练过程观察股价的变化、执行操作以及奖励计算,使代理根据奖励调整其策略。通过与环境互动,交易代理将制定随着时间推移而最大化奖励的交易策略。

本项目的交易环境基于OpenAI Gym框架实现,根据时间驱动模拟的原则模拟实时股票市场,使用真实的市场数据。

1. 训练数据拆分

(1)使用data_split函数将数据集df划分为训练集和交易集,这样的数据集划分是为了在模型训练阶段使用train集,而在模型训练后的回测或实际应用中使用trade集。

train = data_split(df, '2009-01-01','2020-07-01')
#trade = data_split(df, '2020-01-01', config.END_DATE)

对上述代码的具体说明如下所:

  1. train = data_split(df, '2009-01-01','2020-07-01'):将数据集在时间范围从2009年1月1日到2020年7月1日进行划分,生成训练集。
  2. #trade = data_split(df, '2020-01-01', config.END_DATE):被注释掉的这行代码,是指将数据集在时间范围从2020年1月1日到config.END_DATE进行划分,生成交易集。由于被注释掉,该行代码当前并未执行。

(2)通过如下代码,显示训练集train的前几行数据,以便能够查看数据的结构和内容。

train.head()

执行后会输出:

date	open	high	low	close	adjcp	volume	tic	day	macd	boll_ub	boll_lb	rsi_30	cci_30	dx_30	close_30_sma	close_60_sma	cov_list	return_list
0	2009-01-02	3.067143	3.251429	3.041429	3.241071	2.767330	746015200	AAPL	4	-0.082758	3.633600	2.892864	45.440193	-30.508777	2.140064	3.244631	3.376833	[[0.001366150662406761, 0.000433938195725591, ...	tic AAPL AMGN AXP ...
0	2009-01-02	58.590000	59.080002	57.750000	58.990002	44.523743	6547900	AMGN	4	0.320448	59.148360	56.339640	52.756859	94.549630	0.814217	56.759667	56.166000	[[0.001366150662406761, 0.000433938195725591, ...	tic AAPL AMGN AXP ...
0	2009-01-02	18.570000	19.520000	18.400000	19.330000	15.477422	10955700	AXP	4	-1.059847	23.489423	16.086577	43.923322	-42.018825	16.335101	20.028333	22.263333	[[0.001366150662406761, 0.000433938195725591, ...	tic AAPL AMGN AXP ...
0	2009-01-02	42.799999	45.560001	42.779999	45.250000	33.941101	7010200	BA	4	-0.019566	43.926849	37.932151	50.664690	275.696308	20.494464	40.621667	43.237334	[[0.001366150662406761, 0.000433938195725591, ...	tic AAPL AMGN AXP ...
0	2009-01-02	44.910000	46.980000	44.709999	46.910000	31.942251	7117200	CAT	4	1.248426	46.543072	38.372928	53.534743	131.675975	34.637448	40.623333	39.911333	[[0.001366150662406761, 0.000433938195725591, ...	tic AAPL AMGN AXP ...

2. 投资组合配置(Portfolio Allocation)环境

"Portfolio Allocation"环境是针对投资组合分配任务设计的强化学习环境。在这个环境中,模型的目标是通过动态调整投资组合中各个资产的权重,以最大化投资组合的价值或收益。这个环境需要考虑股票市场的波动性、交易成本、风险管理等因素,以制定有效的投资策略。这种环境的设计旨在模拟真实的投资情境,帮助强化学习模型学习并优化投资组合配置。

(1)定义类StockPortfolioEnv,这是一个基于 OpenAI Gym 实现的股票交易环境

class StockPortfolioEnv(gym.Env):
    """用于OpenAI Gym的单一股票交易环境

    属性
    ----------
        df: DataFrame
            输入数据
        stock_dim : int
            唯一股票的数量
        hmax : int
            最大交易股数
        initial_amount : int
            初始资金
        transaction_cost_pct: float
            每笔交易的交易成本百分比
        reward_scaling: float
            奖励的缩放因子,有助于训练
        state_space: int
            输入特征的维度
        action_space: int
            等于股票的维度
        tech_indicator_list: list
            技术指标名称的列表
        turbulence_threshold: int
            控制风险厌恶的阈值
        day: int
            控制日期的增量数

    方法
    -------
    step()
        在每个步骤中,代理将返回动作,然后
        我们将计算奖励,并返回下一个观察。
    reset()
        重置环境
    render()
        使用render返回其他功能
    save_asset_memory()
        返回每个时间步的账户价值
    save_action_memory()
        返回每个时间步的动作/仓位
    """
    metadata = {'render.modes': ['human']}

(2)__init__ 方法:是Python类的构造函数,在创建类实例时自动调用。在StockPortfolioEnv类中,__init__ 方法用于初始化环境的各种属性和参数。

    def __init__(self, 
                df,
                stock_dim,
                hmax,
                initial_amount,
                transaction_cost_pct,
                reward_scaling,
                state_space,
                action_space,
                tech_indicator_list,
                turbulence_threshold=None,
                lookback=252,
                day = 0):
        #super(StockEnv, self).__init__()
        #money = 10 , scope = 1
        self.day = day
        self.lookback=lookback
        self.df = df
        self.stock_dim = stock_dim
        self.hmax = hmax
        self.initial_amount = initial_amount
        self.transaction_cost_pct =transaction_cost_pct
        self.reward_scaling = reward_scaling
        self.state_space = state_space
        self.action_space = action_space
        self.tech_indicator_list = tech_indicator_list
        # 动作空间标准化,形状为 self.stock_dim
        self.action_space = spaces.Box(low=0, high=1, shape=(self.action_space,))
        # 形状为 (34, 30) 的协方差矩阵 + 技术指标
        self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(self.state_space + len(self.tech_indicator_list), self.state_space))

        # 从 pandas 数据框加载数据
        self.data = self.df.loc[self.day, :]
        self.covs = self.data['cov_list'].values[0]
        self.state = np.append(np.array(self.covs), [self.data[tech].values.tolist() for tech in self.tech_indicator_list], axis=0)
        self.terminal = False
        self.turbulence_threshold = turbulence_threshold
        # 初始化状态:初始投资组合回报 + 单个股票回报 + 单个权重
        self.portfolio_value = self.initial_amount

        # 每步记住投资组合价值
        self.asset_memory = [self.initial_amount]
        # 每步记住投资组合回报
        self.portfolio_return_memory = [0]
        self.actions_memory = [[1 / self.stock_dim] * self.stock_dim]
        self.date_memory = [self.data.date.unique()[0]]

(3)step:是强化学习环境中的一个关键方法,定义了在每个时间步执行的操作。在StockPortfolioEnv类中,step 方法用于执行一个时间步的交易操作,并返回相关的环境信息。方法step的具体说明如下所示。

  1. actions:接收代理程序选择的动作,即投资组合中每个股票的权重。这些权重用于确定在该时间步内如何分配资金。
  2. 检查环境是否已经到达时间序列的末尾(self.terminal),如果到达末尾,将生成并保存有关投资组合表现的一些图表,并输出总资产、夏普比率等信息。如果时间序列尚未结束,方法将执行代理程序选择的动作,计算并返回奖励、新的观察状态以及是否已经到达时间序列末尾的信息。
  3. 方法step还用于更新环境的内部状态,包括日期、股票价格等,并将投资组合的相关信息保存在内存中供后续分析使用。
    def step(self, actions):
        # print(self.day)
        self.terminal = self.day >= len(self.df.index.unique()) - 1
        # print(actions)

        if self.terminal:
            df = pd.DataFrame(self.portfolio_return_memory)
            df.columns = ['daily_return']
            plt.plot(df.daily_return.cumsum(), 'r')
            plt.savefig('results/cumulative_reward.png')
            plt.close()

            plt.plot(self.portfolio_return_memory, 'r')
            plt.savefig('results/rewards.png')
            plt.close()

            print("=================================")
            print("begin_total_asset:{}".format(self.asset_memory[0]))
            print("end_total_asset:{}".format(self.portfolio_value))

            df_daily_return = pd.DataFrame(self.portfolio_return_memory)
            df_daily_return.columns = ['daily_return']
            if df_daily_return['daily_return'].std() != 0:
                sharpe = (252 ** 0.5) * df_daily_return['daily_return'].mean() / df_daily_return[
                    'daily_return'].std()
                print("Sharpe: ", sharpe)
            print("=================================")

            return self.state, self.reward, self.terminal, {}

        else:
            # print("Model actions: ",actions)
            # 动作是投资组合权重
            # 标准化为和为1
            # if (np.array(actions) - np.array(actions).min()).sum() != 0:
            #   norm_actions = (np.array(actions) - np.array(actions).min()) / (np.array(actions) - np.array(actions).min()).sum()
            # else:
            #   norm_actions = actions
            weights = self.softmax_normalization(actions)
            # print("Normalized actions: ", weights)
            self.actions_memory.append(weights)
            last_day_memory = self.data

            # 加载下一个状态
            self.day += 1
            self.data = self.df.loc[self.day, :]
            self.covs = self.data['cov_list'].values[0]
            self.state = np.append(np.array(self.covs),
                                   [self.data[tech].values.tolist() for tech in self.tech_indicator_list], axis=0)
            # print(self.state)
            # 计算投资组合回报
            # 单个股票的回报 * 权重
            portfolio_return = sum(((self.data.close.values / last_day_memory.close.values) - 1) * weights)
            # 更新投资组合价值
            new_portfolio_value = self.portfolio_value * (1 + portfolio_return)
            self.portfolio_value = new_portfolio_value

            # 保存到记忆中
            self.portfolio_return_memory.append(portfolio_return)
            self.date_memory.append(self.data.date.unique()[0])
            self.asset_memory.append(new_portfolio_value)

            # 奖励是新的投资组合价值或结束投资组合价值
            self.reward = new_portfolio_value
            # print("Step reward: ", self.reward)
            # self.reward = self.reward*self.reward_scaling

        return self.state, self.reward, self.terminal, {}

(4)reset():用于重置交易环境的状态。在训练或评估过程中,当需要重新开始时,可以调用此方法。

    def reset(self):
        self.asset_memory = [self.initial_amount]
        self.day = 0
        self.data = self.df.loc[self.day, :]
        # 加载状态
        self.covs = self.data['cov_list'].values[0]
        self.state = np.append(np.array(self.covs),
                               [self.data[tech].values.tolist() for tech in self.tech_indicator_list], axis=0)
        self.portfolio_value = self.initial_amount
        # self.cost = 0
        # self.trades = 0
        self.terminal = False
        self.portfolio_return_memory = [0]
        self.actions_memory = [[1 / self.stock_dim] * self.stock_dim]
        self.date_memory = [self.data.date.unique()[0]]
        return self.state

(5)render():用于返回当前环境状态的表示,用于可视化或记录。在人类可读的模式下返回当前状态。

    def render(self, mode='human'):
        return self.state

(6)softmax_normalization():对动作进行 softmax 标准化操作,以确保动作概率的有效性。

    def softmax_normalization(self, actions):
        numerator = np.exp(actions)
        denominator = np.sum(np.exp(actions))
        softmax_output = numerator / denominator
        return softmax_output

(7)save_asset_memory():用于返回每个时间步的账户价值,用于记录和分析模型的性能。

    def save_asset_memory(self):
        date_list = self.date_memory
        portfolio_return = self.portfolio_return_memory
        # print(len(date_list))
        # print(len(asset_list))
        df_account_value = pd.DataFrame({'date': date_list, 'daily_return': portfolio_return})
        return df_account_value

(8)save_action_memory():用于返回每个时间步的动作/持仓,记录和分析模型的交易决策。

    def save_action_memory(self):
        # 日期和收盘价的长度必须与动作的长度匹配
        date_list = self.date_memory
        df_date = pd.DataFrame(date_list)
        df_date.columns = ['date']

        action_list = self.actions_memory
        df_actions = pd.DataFrame(action_list)
        df_actions.columns = self.data.tic.values
        df_actions.index = df_date.date
        # df_actions = pd.DataFrame({'date':date_list,'actions':action_list})
        return df_actions

(9)_seed():这是一个种子生成器函数,用于确保环境在不同运行中具有相同的初始状态。

    def _seed(self, seed=None):
        self.np_random, seed = seeding.np_random(seed)
        return [seed]

(10)get_sb_env():用于获取 Stable-Baselines3 环境,将当前环境包装为适用于 Stable-Baselines3 库的向量化环境。

    def get_sb_env(self):
        e = DummyVecEnv([lambda: self])
        obs = e.reset()
        return e, obs

在强化学习中,通常会使用向量化环境(Vectorized Environment)来提高训练效率。Stable-Baselines3(SB3)库中的向量化环境是指一次执行多个并发环境的能力。与传统的环境相比,向量化环境允许同时在多个环境实例上执行模型的前进和训练步骤。

在SB3中使用的向量化环境,主要是通过类DummyVecEnv实现。类DummyVecEnv将多个环境实例包装在一起,使其表现得像单个环境。这种环境的向量化可以带来训练速度的显著提升,特别是在使用深度学习模型进行训练时,因为模型的计算可以在多个环境之间并行进行。在使用SB3进行强化学习时,通过使用向量化环境,可以更有效地利用硬件资源,加速训练过程。

(11)下面代码首先计算了训练数据集中不同股票(tic是股票代码)的数量,然后将该数量分配给变量stock_dimension。接着,将stock_dimension的值赋给state_space变量。最后,打印输出了股票维度(即不同股票的数量)和状态空间的维度。

stock_dimension = len(train.tic.unique())
state_space = stock_dimension
print(f"Stock Dimension: {stock_dimension}, State Space: {state_space}")

上述代码的目的可能是在股票交易环境中设置环境参数,其中stock_dimension表示股票的数量,而state_space表示状态空间的维度。执行后会输出:

Stock Dimension: 28, State Space: 28

(12)下面这段代码创建了一个股票交易环境e_train_gym,并使用之前定义的参数字典env_kwargs来设置环境的各种参数。具体来说,包括如下所示的参数。

  1. "hmax":最大交易数量,限制每次交易的最大股票数量。
  2. "initial_amount":初始资金,代表交易的起始资金。
  3. "transaction_cost_pct":交易成本百分比,表示每次交易的手续费百分比。
  4. "state_space":状态空间的维度,这里使用之前计算的state_space。
  5. "stock_dim":股票的数量,这里使用之前计算的stock_dimension。
  6. "tech_indicator_list":技术指标列表,包含在训练环境中使用的技术指标。
  7. "action_space":动作空间的维度,这里设置为股票的数量。
  8. "reward_scaling":奖励缩放因子,用于调整奖励的规模。

这样,通过传递这些参数,我们创建的股票交易环境就具备了相应的特性和限制。

env_kwargs = {
    "hmax": 100, 
    "initial_amount": 1000000, 
    "transaction_cost_pct": 0.001, 
    "state_space": state_space, 
    "stock_dim": stock_dimension, 
    "tech_indicator_list": config.INDICATORS, 
    "action_space": stock_dimension, 
    "reward_scaling": 1e-4
    
}

e_train_gym = StockPortfolioEnv(df = train, **env_kwargs)

(13)通过get_sb_env()方法获取库Stable-Baselines3的向量化环境env_train,然后打印其类型。通过使用get_sb_env()方法,原始的股票交易环境被包装成了Stable-Baselines3库中的向量化环境,以便与该库中的强化学习算法进行集成。

env_train, _ = e_train_gym.get_sb_env()
print(type(env_train))

在上述代码中,print(type(env_train))语句用于打印输出env_train的类型,以确认其为Stable-Baselines3中的环境类型。这通常是DummyVecEnv类型,这是Stable-Baselines3库中用于包装环境的向量化环境。执行后会输出:

未完待续

你可能感兴趣的:(金融大模型,人工智能,深度学习,机器学习,python,算法)