我的目标是设计一个针对股票交易的自动化交易解决方案。
现有的一些使用到强化学习的量化交易框架,存在各种问题,比如说
-只能针对一只股票进行训练,此前我自己也用paddle写过一个DDPG的
-调用框架训练时间长,代码量很大
-做算法效果对比非常困难,
-数据获取部分缺失
-学习困难,不知道每一部分是做什么的
针对这些问题,我找到了一个框架FinRL,希望借助这个框架进行研究。
FinRL: A Deep Reinforcement Learning Library for Quantitative Finance
AI4Finance-LLC/FinRLg
框架依赖于以下的包
Yahoo Finance API,我个人做了一点调整,把雅虎的数据源改成了tushare
pandas
numpy
matplotlib
stockstats
OpenAI gym
stable-baselines3
pyfolio
先介绍一下,框架目录及作用:
---config:存放配置文件,比如股票代码
---env:使用处理过的数据来定义一个股票交易环境,使用了gym框架
---model:引入了stable-baseline,后续快捷调用model
---marketdata:按格式下载最新的交易数据
---proprecessing:对数据进行预处理
在使用中我发现一些问题,后续会做一些优化
1)baseline训练速度较慢
2)pyfolio与python3.8版本不适配
3)Yahoo Finance API经常端口调用失败
4)与现有的quant平台没有集成,实盘接口没有开发
下边讲一下我进行训练模型和回测的过程
1.import package
import pandas as pd
import numpy as np
import pylab, matplotlib.pyplot
import matplotlib as plt
import datetime
import sys
import torch
#sys.path.append("../FinRL-Library")
matplotlib.use('Agg')
%matplotlib inline
from finrl.config import config
from finrl.marketdata.tusharedownloader import TushareDownloader
from finrl.preprocessing.preprocessors import FeatureEngineer
from finrl.preprocessing.data import data_split
from finrl.env.env_stocktrade import StockTradingEnv
from finrl.model.models import DRLAgent
from finrl.trade.backtest import backtest_stats, backtest_plot, get_daily_return, get_baseline
from pprint import pprint
import argparse
import itertools
主要用到的是config
2.create folder
import os
if not os.path.exists("./" + config.DATA_SAVE_DIR):
os.makedirs("./" + config.DATA_SAVE_DIR)
if not os.path.exists("./" + config.TRAINED_MODEL_DIR):
os.makedirs("./" + config.TRAINED_MODEL_DIR)
if not os.path.exists("./" + config.TENSORBOARD_LOG_DIR):
os.makedirs("./" + config.TENSORBOARD_LOG_DIR)
if not os.path.exists("./" + config.RESULTS_DIR):
os.makedirs("./" + config.RESULTS_DIR)
这里我们引入目录结构
3.downloading data
df = TushareDownloader(start_date = '2010-01-01',
end_date = '2021-07-11',
ticker_list = config.SSE_20_TICKER).fetch_data()
这里我们选择用2010年1月1日到2021年7月11
股票随便选了上证20只股票,可以在config目录下config.py文件中进行配置
df.shape
df.sort_values(['date','tic'],ignore_index=True).head()
在本文中,我们展示了两个趋势跟踪技术指标:MACD和RSI。 添加湍流指数。风险厌恶反映了投资者是否会选择保留资本。当面对不同的市场波动水平时,它也会影响一个人的交易策略。为了在最坏的情况下控制风险,比如2007-2008年的金融危机,FinRL采用了衡量极端资产价格波动的金融动荡指数。
fe = FeatureEngineer(
use_technical_indicator=True,
tech_indicator_list = config.TECHNICAL_INDICATORS_LIST,
use_turbulence=True,
user_defined_feature = False)
processed = fe.preprocess_data(df)
```
Successfully added technical indicators
Successfully added turbulence index
对数据进行处理,后续导入环境
list_ticker = processed["tic"].unique().tolist()
list_date = list(pd.date_range(processed['date'].min(),processed['date'].max()).astype(str))
combination = list(itertools.product(list_date,list_ticker))
processed_full = pd.DataFrame(combination,columns=["date","tic"]).merge(processed,on=["date","tic"],how="left")
processed_full = processed_full[processed_full['date'].isin(processed['date'])]
processed_full = processed_full.sort_values(['date','tic'])
processed_full = processed_full.fillna(0)
4.split data
train = data_split(processed_full, '2009-01-01','2021-04-01')
trade = data_split(processed_full, '2021-04-01','2021-07-11')
print(len(train))
print(len(trade))
讲数据一分为二,后续进行测试
stock_dimension = len(train.tic.unique())
state_space = 1 + 2stock_dimension + len(config.TECHNICAL_INDICATORS_LIST)stock_dimension
print(f"Stock Dimension: {stock_dimension}, State Space: {state_space}")
计算出股票的空间,后续将这些数据灌入模型进行强化学习
Stock Dimension: 21, State Space: 211
5.set env
env_kwargs = {
"hmax": 100,
"initial_amount": 1000000,
"buy_cost_pct": 0.001,
"sell_cost_pct": 0.001,
"state_space": state_space,
"stock_dim": stock_dimension,
"tech_indicator_list": config.TECHNICAL_INDICATORS_LIST,
"action_space": stock_dimension,
"reward_scaling": 1e-4
}
e_train_gym = StockTradingEnv(df = train, **env_kwargs)
env_train, _ = e_train_gym.get_sb_env()
print(type(env_train))
<class 'stable_baselines3.common.vec_env.dummy_vec_env.DummyVecEnv'>
训练过程包括观察股票价格的变化,采取行动和奖励的计算,使代理相应地调整其策略。通过与环境的互动,交易代理将随着时间的推移获得回报最大化的交易策略。
我们的交易环境基于OpenAI Gym框架,根据时间驱动模拟的原理,用真实的市场数据模拟真实的股票市场。
操作空间描述了代理与环境交互的允许操作。
通常,动作a包括三个动作:{-1,0,1},其中-1,0,1代表卖出、持有和买入一股。
此外,一项行动可以在多个股票上进行。我们使用一个动作空间{-k,…,-1,0,1,…,k},其中k表示要购买的股票数量,而-k表示要出售的股票数量。
6.set agent
agent = DRLAgent(env = env_train)
PPO_PARAMS = {
"n_steps": 2048,
"ent_coef": 0.01,
"learning_rate": 0.00025,
"batch_size": 128,
}
model_ppo = agent.get_model("ppo",model_kwargs = PPO_PARAMS)
agent = DRLAgent(env = env_train)
model_td3 = agent.get_model("td3")
{'batch_size': 100, 'buffer_size': 1000000, 'learning_rate': 0.001} Using cpu device
FinRL库包括标准DRL算法,如DQN、DDPG、多智能体DDPG、PPO、SAC、A2C和TD3。
trained_ppo = agent.train_model(model=model_ppo,
tb_log_name='ppo',
total_timesteps=50000)
trained_ppo.save(f"{config.TRAINED_MODEL_DIR}/ppo")
启动训练:这里我们训练100000次,选用TD3模型,
可以看到初步训练的成果,比如起始资产是1000000,收益是1336606.29
Logging to tensorboard_log/td3\td3_1
---------------------------------
| time/ | |
| episodes | 4 |
| fps | 44 |
| time_elapsed | 48 |
| total timesteps | 2160 |
| train/ | |
| actor_loss | 253 |
| critic_loss | 2.28e+04 |
| learning_rate | 0.001 |
| n_updates | 1620 |
---------------------------------
---------------------------------
| time/ | |
| episodes | 8 |
| fps | 32 |
| time_elapsed | 133 |
| total timesteps | 4320 |
| train/ | |
| actor_loss | 96.3 |
| critic_loss | 1.24e+04 |
| learning_rate | 0.001 |
| n_updates | 3780 |
---------------------------------
day: 539, episode: 10
begin_total_asset: 1000000.00
end_total_asset: 2336606.29
total_reward: 1336606.29
total_cost: 1024.01
total_trades: 7568
Sharpe: 1.453
模型保存在指定目录,后续可以直接调用
#trained_ddpg = DDPG.load((f"{config.TRAINED_MODEL_DIR}/ddpg"))
#trained_td3 = TD3.load((f"{config.TRAINED_MODEL_DIR}/td3"))
#trained_ppo = PPO.load((f"{config.TRAINED_MODEL_DIR}/ppo"))
#trained_sac = SAC.load((f"{config.TRAINED_MODEL_DIR}/sac"))
#trained_a2c = A2C.load((f"{config.TRAINED_MODEL_DIR}/a2c"))
这里模型训练好了,下边开始进行测试
将湍流阈值设置为大于样本湍流数据的最大值,如果当前湍流指数大于阈值,则我们假设当前市场是波动的
data_turbulence = processed_full[(processed_full.date<'2021-04-01') & (processed_full.date>='2009-01-01')]
insample_turbulence = data_turbulence.drop_duplicates(subset=['date'])
turbulence_threshold = np.quantile(insample_turbulence.turbulence.values,1)
7.trade
trade = data_split(processed_full, '2020-05-01','2021-07-11')
e_trade_gym = StockTradingEnv(df = trade, turbulence_threshold = 380, **env_kwargs)
DRL模型需要定期更新,以便充分利用数据,理想情况下,我们需要每年、每季度或每月对模型进行重新培训。我们还需要一路调参数,在这个笔记本里我只使用了2009-01到2018-12年的样本内数据调了一次参数,所以这里随着交易日期长度的延长有一些alpha衰减。 许多超参数——例如学习速率、要训练的样本总数——会影响学习过程,通常通过测试一些变量来确定。
df_account_value, df_actions = DRLAgent.DRL_prediction(
model=trained_td3,
environment = e_trade_gym)
8.action
6
date ccount_value
2021-07-05 1.362169e+06
2021-07-06 1.369554e+06
2021-07-07 1.366422e+06
2021-07-08 1.357364e+06
2021-07-09 1.354119e+06
9.account value
回溯测试在评估交易策略的表现中起着关键作用。自动化回溯测试工具是首选,因为它减少了人为错误。我们通常使用Quantopian pyfolio包来回溯测试我们的交易策略。它易于使用,由各种单独的图组成,提供了交易策略表现的综合图像。
print("==============Get Backtest Results===========")
now = datetime.datetime.now().strftime('%Y%m%d-%Hh%M')
perf_stats_all = backtest_stats(account_value=df_account_value)
perf_stats_all = pd.DataFrame(perf_stats_all)
perf_stats_all.to_csv("./"+config.RESULTS_DIR+"/perf_stats_all_"+now+'.csv')
==============Get Backtest Results===========
Annual return 0.302570
Cumulative returns 0.354119
Annual volatility 0.256770
Sharpe ratio 1.161667
Calmar ratio 1.322362
Stability 0.706473
Max drawdown -0.228810
Omega ratio 1.220779
Sortino ratio 1.715430
Skew NaN
Kurtosis NaN
Tail ratio 1.152343
Daily value at risk -0.031166
dtype: float64
Annual return 年回报率
Cumulative returns累计回报
Annual volatility年波动率
Sharpe ratio夏普比率
Calmar ratio卡尔马尔比率
Stability稳定性
Max drawdown 最大压降
10.plot
def plot(tradedata,actionsdata,ticker):
#the first plot is the actual close price with long/short positions
绘制实际的股票收盘数据
df_plot = pd.merge(left=tradedata ,right=actionsdata,on='date',how='inner')
plot_df = df_plot.loc[df_plot['tic']==ticker].loc[:,['date','tic','close',ticker]].reset_index()
fig=plt.figure(figsize=(12, 6))
ax=fig.add_subplot(111)
ax.plot(plot_df.index, plot_df['close'], label=ticker)
只显示时刻点,不显示折线图 => 设置 linewidth=0
ax.plot(plot_df.loc[plot_df[ticker]>0].index, plot_df['close'][plot_df[ticker]>0], label='Buy', linewidth=0, marker='^', c='g')
ax.plot(plot_df.loc[plot_df[ticker]<0].index, plot_df['close'][plot_df[ticker]<0], label='Sell', linewidth=0, marker='v', c='r')
plt.legend(loc='best')
plt.grid(True)
plt.title(ticker +''+plot_df['date'].min()+'_'+plot_df['date'].max())
plt.show()
print(plot_df.loc[df_plot[ticker]>0])
[upl-image-preview url=http://deeprlhub.com/assets/files/2021-07-11/1626018266-109858-5.png]