单只股票交易¶
从零开始的深度强化学习股票交易:单只股票交易
提示
在 Google Colab 上逐步运行代码。
步骤 1:准备¶
步骤 1.1:概览
由于深度强化学习 (DRL) 在量化金融中已被公认为是一种有效的方法,因此动手实践对初学者很有吸引力。然而,训练一个实用的 DRL 交易代理,使其决定在哪里交易、以什么价格交易以及交易量多少,涉及容易出错且艰巨的开发和调试工作。
我们引入了 DRL 库 FinRL,它有助于初学者接触量化金融并开发自己的股票交易策略。通过易于重现的教程,FinRL 库使用户能够简化自己的开发并轻松与现有方案进行比较。
FinRL 是一个对初学者友好的库,包含经过优化的标准 DRL 算法。它是在三个主要原则下开发的
完整性:我们的库应完整涵盖 DRL 框架的组件,这是一项基本要求;
动手实践教程:我们的目标是创建一个对初学者友好的库。包含详细步骤的教程将帮助用户探索我们库的功能;
可重现性:我们的库应保证可重现性,以确保透明度,并让用户对其所做的工作充满信心
本文重点介绍我们论文中的一个用例:单只股票交易。我们使用一个 Jupyter Notebook 包含所有必要的步骤。
我们在本文中以 Apple Inc.(苹果公司)股票:AAPL 为例,因为它是一种最受欢迎的股票。

步骤 1.2:问题定义
这个问题是为单只股票交易设计一个自动化交易解决方案。我们将股票交易过程建模为马尔可夫决策过程 (MDP)。然后我们将交易目标表述为一个最大化问题。
强化学习环境的组成部分包括
动作:动作空间描述了代理与环境交互时允许的动作。通常,a ∈ A 包括三个动作:a ∈ {−1, 0, 1},其中 −1、0、1 分别代表卖出、持有和买入一股。此外,一个动作可以针对多股进行。我们使用动作空间 {−k, …, −1, 0, 1, …, k},其中 k 表示买入的股数。例如,“买入 10 股 AAPL”或“卖出 10 股 AAPL”分别表示 10 或 −10。
奖励函数:r(s, a, s′) 是代理学习更好动作的激励机制。当在状态 s 执行动作 a 并到达新状态 s' 时,投资组合价值的变化,即 r(s, a, s′) = v′ − v,其中 v′ 和 v 分别表示在状态 s′ 和 s 时的投资组合价值。
状态:状态空间描述了代理从环境中接收到的观测值。就像人类交易员在执行交易前需要分析各种信息一样,我们的交易代理也会观察许多不同的特征,以便在交互式环境中更好地学习。
环境:针对 AAPL 的单只股票交易
本案例研究将使用的单只股票数据通过 Yahoo Finance API 获取。数据包含开盘价、最高价、最低价、收盘价和成交量。
步骤 1.3:Python 包安装
第一步,我们检查所需的额外包是否已存在,如果不存在则进行安装。
Yahoo Finance API
pandas
matplotlib
stockstats
OpenAI gym
stable-baselines
tensorflow
1import pkg_resources
2import pip
3installedPackages = {pkg.key for pkg in pkg_resources.working_set}
4required = {'yfinance', 'pandas', 'matplotlib', 'stockstats','stable-baselines','gym','tensorflow'}
5missing = required - installedPackages
6if missing:
7 !pip install yfinance
8 !pip install pandas
9 !pip install matplotlib
10 !pip install stockstats
11 !pip install gym
12 !pip install stable-baselines[mpi]
13 !pip install tensorflow==1.15.4
步骤 1.4:导入包
1import yfinance as yf
2from stockstats import StockDataFrame as Sdf
3
4import pandas as pd
5import matplotlib.pyplot as plt
6
7import gym
8from stable_baselines import PPO2, DDPG, A2C, ACKTR, TD3
9from stable_baselines import DDPG
10from stable_baselines import A2C
11from stable_baselines import SAC
12from stable_baselines.common.vec_env import DummyVecEnv
13from stable_baselines.common.policies import MlpPolicy
步骤 2:下载数据¶
Yahoo Finance 是一个提供股票数据、财经新闻、财务报告等的网站。Yahoo Finance 提供的所有数据都是免费的。
这篇 Medium 博客 解释了如何在 Python 中直接使用 Yahoo Finance API 提取数据。
FinRL 使用 YahooDownloader 类从 Yahoo Finance API 获取数据
调用限制:使用公共 API(无需身份验证),您每小时每个 IP 地址的请求次数限制为 2,000 次(或每天总计最多 48,000 次请求)。
我们可以手动输入股票代码,如 AAPL,到网站搜索栏下载股票数据,如开盘价-最高价-最低价-收盘价,或者我们也可以直接使用 Yahoo Finance API 自动提取数据。
FinRL 使用 YahooDownloader 类来提取数据。
class YahooDownloader:
"""
Provides methods for retrieving daily stock data from Yahoo Finance API
Attributes
----------
start_date : str
start date of the data (modified from config.py)
end_date : str
end date of the data (modified from config.py)
ticker_list : list
a list of stock tickers (modified from config.py)
Methods
-------
fetch_data()
Fetches data from yahoo API
"""
下载数据并将其保存在 pandas DataFrame 中
1 # Download and save the data in a pandas DataFrame:
2 df = YahooDownloader(start_date = '2009-01-01',
3 end_date = '2020-09-30',
4 ticker_list = config_tickers.DOW_30_TICKER).fetch_data()
5
6 print(df.sort_values(['date','tic'],ignore_index=True).head(30))

步骤 3:数据预处理¶
数据预处理是训练高质量机器学习模型的关键步骤。我们需要检查缺失数据并进行特征工程,以便将数据转换为适合模型使用的状态。
FinRL 使用 FeatureEngineer 类来预处理数据
添加技术指标。在实际交易中,需要考虑各种信息,例如历史股票价格、当前持有的股票、技术指标等。
计算技术指标
在实际交易中,需要考虑各种信息,例如历史股票价格、当前持有的股票、技术指标等。
FinRL 使用 stockstats 计算技术指标,例如移动平均聚散指标 (MACD)、相对强弱指数 (RSI)、平均趋向指数 (ADX)、商品通道指数 (CCI) 以及其他各种指标和统计数据。
stockstats:提供了一个基于 pandas.DataFrame 的包装器 StockDataFrame,支持内联股票统计/指标。
我们将 stockstats 技术指标列名存储在 config.py 中
config.INDICATORS = [‘macd’, ‘rsi_30’, ‘cci_30’, ‘dx_30’]
用户可以添加更多技术指标,查看 https://github.com/jealous/stockstats 获取不同名称。
FinRL 使用 FeatureEngineer 类来预处理数据。
class FeatureEngineer:
"""
Provides methods for preprocessing the stock price data
Attributes
----------
df: DataFrame
data downloaded from Yahoo API
feature_number : int
number of features we used
use_technical_indicator : boolean
we technical indicator or not
use_turbulence : boolean
use turbulence index or not
Methods
-------
preprocess_data()
main method to do the feature engineering
"""
执行特征工程
1 # Perform Feature Engineering:
2 df = FeatureEngineer(df.copy(),
3 use_technical_indicator=True,
4 tech_indicator_list = config.INDICATORS,
5 use_turbulence=True,
6 user_defined_feature = False).preprocess_data()
步骤 4:构建环境¶
考虑到自动化股票交易任务的随机性和交互性,金融任务被建模为马尔可夫决策过程 (MDP) 问题。训练过程包括观察股价变化、采取行动和计算奖励,以便代理相应地调整其策略。通过与环境交互,交易代理将随着时间推移推导出最大化奖励的交易策略。
我们的交易环境基于 OpenAI Gym 框架,根据时间驱动模拟的原则,使用真实市场数据模拟实时股票市场。
环境设计是 DRL 中最重要的部分之一,因为它在不同的应用和不同的市场之间差异很大。我们不能使用股票交易环境来交易比特币,反之亦然。
动作空间描述了代理与环境交互时允许的动作。通常,动作 a 包括三个动作:{-1, 0, 1},其中 -1、0、1 分别代表卖出、持有和买入一股。此外,一个动作可以针对多股进行。我们使用动作空间 {-k,…,-1, 0, 1, …, k},其中 k 表示买入的股数,-k 表示卖出的股数。例如,“买入 10 股 AAPL”或“卖出 10 股 AAPL”分别表示 10 或 -10。连续动作空间需要归一化到 [-1, 1],因为策略定义在高斯分布上,需要进行归一化和对称处理。
在本文中,我将 k 设置为 200,AAPL 的整个动作空间是 200*2+1 = 401。
FinRL 使用 EnvSetup 类来设置环境。
class EnvSetup:
"""
Provides methods for retrieving daily stock data from
Yahoo Finance API
Attributes
----------
stock_dim: int
number of unique stocks
hmax : int
maximum number of shares to trade
initial_amount: int
start money
transaction_cost_pct : float
transaction cost percentage per trade
reward_scaling: float
scaling factor for reward, good for training
tech_indicator_list: list
a list of technical indicator names (modified from config.py)
Methods
-------
fetch_data()
Fetches data from yahoo API
"""
初始化环境类
1 # Initialize env:
2 env_setup = EnvSetup(stock_dim = stock_dimension,
3 state_space = state_space,
4 hmax = 100,
5 initial_amount = 1000000,
6 transaction_cost_pct = 0.001,
7 tech_indicator_list = config.INDICATORS)
8
9 env_train = env_setup.create_env_training(data = train,
10 env_class = StockEnvTrain)
用户定义环境:一个模拟环境类。
FinRL 提供了 单只股票交易环境 的蓝图。
class SingleStockEnv(gym.Env):
"""
A single stock trading environment for OpenAI gym
Attributes
----------
df: DataFrame
input data
stock_dim : int
number of unique stocks
hmax : int
maximum number of shares to trade
initial_amount : int
start money
transaction_cost_pct: float
transaction cost percentage per trade
reward_scaling: float
scaling factor for reward, good for training
state_space: int
the dimension of input features
action_space: int
equals stock dimension
tech_indicator_list: list
a list of technical indicator names
turbulence_threshold: int
a threshold to control risk aversion
day: int
an increment number to control date
Methods
-------
_sell_stock()
perform sell action based on the sign of the action
_buy_stock()
perform buy action based on the sign of the action
step()
at each step the agent will return actions, then
we will calculate the reward, and return the next
observation.
reset()
reset the environment
render()
use render to return other functions
save_asset_memory()
return account value at each time step
save_action_memory()
return actions/positions at each time step
"""
关于如何设计定制交易环境的教程将在不久的将来发布。
步骤 5:实现 DRL 算法¶
DRL 算法的实现基于 OpenAI Baselines 和 Stable Baselines。Stable Baselines 是 OpenAI Baselines 的一个分支,进行了主要的结构重构和代码清理。
提示
FinRL 库包含经过优化的标准 DRL 算法,例如 DQN、DDPG、多代理 DDPG、PPO、SAC、A2C 和 TD3。我们也允许用户通过调整这些 DRL 算法来设计自己的 DRL 算法。

FinRL 使用 DRLAgent 类来实现算法。
class DRLAgent:
"""
Provides implementations for DRL algorithms
Attributes
----------
env: gym environment class
user-defined class
Methods
-------
train_PPO()
the implementation for PPO algorithm
train_A2C()
the implementation for A2C algorithm
train_DDPG()
the implementation for DDPG algorithm
train_TD3()
the implementation for TD3 algorithm
DRL_prediction()
make a prediction in a test dataset and get results
"""
步骤 6:模型训练¶
本文我们使用了 5 种 DRL 模型,即 PPO、A2C、DDPG、SAC 和 TD3。我在前一篇文章中介绍了这些模型。TD3 是对 DDPG 的改进。
Tensorboard:奖励和损失函数图 我们使用 Tensorboard 集成进行超参数调整和模型选择。Tensorboard 生成美观的图表。
调用 learn 函数后,您可以使用以下 bash 命令在训练期间或之后监控 RL 代理
1 # cd to the tensorboard_log folder, run the following command
2 tensorboard --logdir ./A2C_20201127-19h01/
3 # you can also add past logging folder
4 tensorboard --logdir ./a2c_tensorboard/;./ppo2_tensorboard/
每种算法的总奖励

total_timesteps (int):训练的总样本数。它是最重要的超参数之一,还有其他重要的参数,如学习率、批量大小、缓冲区大小等。
为了比较这些算法,我设置 total_timesteps = 100k。如果我们将 total_timesteps 设置得太大,则会面临过拟合的风险。
通过观察 episode_reward 图表,我们可以看到这些算法最终会随着步数的增加而收敛到最优策略。TD3 收敛非常快。
DDPG 的 actor_loss 和 TD3 的 policy_loss


选择模型
我们选择 TD3 模型,因为它收敛得相当快,并且是优于 DDPG 的最新模型。通过观察 episode_reward 图表,TD3 不需要达到完整的 100k total_timesteps 即可收敛。
四种模型:PPO A2C, DDPG, TD3
模型 1:PPO
1#tensorboard --logdir ./single_stock_tensorboard/
2env_train = DummyVecEnv([lambda: SingleStockEnv(train)])
3model_ppo = PPO2('MlpPolicy', env_train, tensorboard_log="./single_stock_trading_2_tensorboard/")
4model_ppo.learn(total_timesteps=100000,tb_log_name="run_aapl_ppo")
5#model.save('AAPL_ppo_100k')
模型 2:DDPG
1#tensorboard --logdir ./single_stock_tensorboard/
2env_train = DummyVecEnv([lambda: SingleStockEnv(train)])
3model_ddpg = DDPG('MlpPolicy', env_train, tensorboard_log="./single_stock_trading_2_tensorboard/")
4model_ddpg.learn(total_timesteps=100000, tb_log_name="run_aapl_ddpg")
5#model.save('AAPL_ddpg_50k')
模型 3:A2C
1#tensorboard --logdir ./single_stock_tensorboard/
2env_train = DummyVecEnv([lambda: SingleStockEnv(train)])
3model_a2c = A2C('MlpPolicy', env_train, tensorboard_log="./single_stock_trading_2_tensorboard/")
4model_a2c.learn(total_timesteps=100000,tb_log_name="run_aapl_a2c")
5#model.save('AAPL_a2c_50k')
模型 4:TD3
1#tensorboard --logdir ./single_stock_tensorboard/
2#DQN<DDPG<TD3
3env_train = DummyVecEnv([lambda: SingleStockEnv(train)])
4model_td3 = TD3('MlpPolicy', env_train, tensorboard_log="./single_stock_trading_2_tensorboard/")
5model_td3.learn(total_timesteps=100000,tb_log_name="run_aapl_td3")
6#model.save('AAPL_td3_50k')
测试数据
1test = data_clean[(data_clean.datadate>='2019-01-01') ]
2# the index needs to start from 0
3test=test.reset_index(drop=True)
交易
假设我们在 2019-01-01 拥有 $100,000 的初始资金。我们使用 TD3 模型交易 AAPL。
1model = model_td3
2env_test = DummyVecEnv([lambda: SingleStockEnv(test)])
3obs_test = env_test.reset()
4print("==============Model Prediction===========")
5for i in range(len(test.index.unique())):
6 action, _states = model.predict(obs_test)
7 obs_test, rewards, dones, info = env_test.step(action)
8 env_test.render()
1 # create trading env
2 env_trade, obs_trade = env_setup.create_env_trading(data = trade,
3 env_class = StockEnvTrade,
4 turbulence_threshold=250)
5 ## make a prediction and get the account value change
6 df_account_value = DRLAgent.DRL_prediction(model=model_sac,
7 test_data = trade,
8 test_env = env_trade,
9 test_obs = obs_trade)

步骤 7:回测我们的策略¶
回测在评估交易策略表现方面起着关键作用。自动化回测工具是首选,因为它可以减少人为错误。我们通常使用 `Quantopian pyfolio`_ 包来回测我们的交易策略。它易于使用,包含各种独立的图表,提供了交易策略表现的全面视图。
为简单起见,在本文中,我们只手动计算夏普比率和年化收益。
1def get_DRL_sharpe():
2 df_total_value=pd.read_csv('account_value.csv',index_col=0)
3 df_total_value.columns = ['account_value']
4 df_total_value['daily_return']=df_total_value.pct_change(1)
5 sharpe = (252**0.5)*df_total_value['daily_return'].mean()/ \
6 df_total_value['daily_return'].std()
7
8 annual_return = ((df_total_value['daily_return'].mean()+1)**252-1)*100
9 print("annual return: ", annual_return)
10 print("sharpe ratio: ", sharpe)
11 return df_total_value
12
13
14def get_buy_and_hold_sharpe(test):
15 test['daily_return']=test['adjcp'].pct_change(1)
16 sharpe = (252**0.5)*test['daily_return'].mean()/ \
17 test['daily_return'].std()
18 annual_return = ((test['daily_return'].mean()+1)**252-1)*100
19 print("annual return: ", annual_return)
20
21 print("sharpe ratio: ", sharpe)
22 #return sharpe