上一篇文章,我粗略的介绍了一下运用强化学习探索基于5分钟K线数据的日内择时策略,其实还根本算不上一个策略,我也只是想说明可以运用强化学习来这样探索。这篇文章我进一步更加详细的介绍了这个策略,欢迎有兴趣的宽客朋友们跟我来交流,我基本也算是一个初学者,希望与更多爱好强化学习的朋友相互学习。
在上一篇文章中关于最后训练数据处理的部分没有讲的很清楚,在这篇文章中,我完全换成了直接从JQData读取数据的方法进行训练的,感兴趣的可以把这篇文章的代码复制出来,稍做调整就可以跑出结果来了。
下面是关于整个模型环境的类:
import numpy as np from ReinfL.envs import terminal # terminal是一个标识符 类似terminal=‘terminal’class MPK:""" 我们首先根据市场的一些统计指标抽象n个状态(s),这些s之间应该有一个 状态转移(s->s')矩阵S: | | s0 | s1 | s2 | s3 | .. | sn | | s0 | | | | | .. | | | s1 | | | | | .. | | | s2 | | | | | .. | | ==》 n x n | s3 | | | | | .. | | | .. | .. | .. | .. | .. | .. | .. | | sn | | | | | .. | | | 矩阵S是全市场所有可能出现的状态转移,但对某一天来说,通常情况下只是 S的一个子集(S’)。例如我们把一天的状态划分成48个时点,即每根5分钟 K线对应一个状态,这样每天就最多对应48个,随着时间的推进,这些状态相 互转换,实际上S‘不一定有48个状态,其中可能有重复的,但肯定会有48个 实例。我们把这48个实例排列成下面这种形式的表格: | -> | -> | -> | -> | -> | | <- | <- | Sij| <- | <- | ==》 8 x 6 | -> | -> | -> | -> | -> | 我们把这个模型抽象比喻成一个机器人捡糖果的游戏: Agent的探索路径为:从格子的第一行左端开始,往右走,然后从下一行走回来. 以此类推。在每个格子里都放有糖果(这个糖果的包装、外形上基本一样,一般 情况下Agent是分辨不开的,就像给你一些K线的历史数据,你根本不能确定接下 是涨是跌。当然,在这里你可以人为的传授Agent一些规则,根据糖果的外行接 下来应该采取的行动。这个糖果里面包有糖,或者是*),Agent可以采取的 行动包括:1.捡起拆开,2.忽略,直接走到下一个格子,3.对糖果施加魔法后 拆开(这意味着,如果糖果里面本来包的糖会变成*,如果是*就会变成糖)。 Agent如果捡起糖果并拆开,里面若是糖,Agent就会尝到甜头,若是*则会 尝到苦头。Agent一路走来将会持续积累甜头和苦头,甜头会跟苦头相抵消。当 Agent积累到足够的甜头或苦头就会主动退出这个探索,或者是探索完全部格子。 Agent的目的就是尽快的找到更多的甜头。 接下来,我们把这个过程中的一些概念或说法换成模型语言: 游戏中所谓的“甜”就是只股票(也可以是其他的证券)涨了;“苦”就是跌了, 甜苦相抵也就说涨跌是可是冲销的-> 3 (-2) = 1。在程序中我们是以接下来 的涨跌幅来划分标记这个苦甜。 Agent可采取的3个动作>B(买入)、W(观望)、S(卖出),当接下来s'是跌的, Agent选择s,也就是说明它避免了亏损,这也算是一种盈利,这个过程就是前面 说的“魔法”。 Agent尝到足够的甜头>止盈;尝到足够的苦头>止损;走完全部的格子表示 既没止损也没止盈。 =》挑战: 每一天格子里面放的糖果都在变,没有比较稳定的状态,好比找宝藏的游戏中, 宝藏和*的位置随时都在变,这对于机器人来说就有点儿难为人了。“上次这个 格子明明可以走的,这次又不能走了,并且周围的环境都没变”,不管让谁来做 这样的决策都会面临这样的尴尬,这也正是分析预测证券市场所面临的困难。 =》强化学习的未来在哪儿? 拼概率、靠能比人记住更多的经验教训的能力取胜。不管多好的算法都不能玩转 证券市场,但要战胜人是很有可能的。强化学习还有一个特点:人有时候做出一个 决策的时候可能自己都不知道为什么要这样做,感觉就应该这样做,这种感觉我们 却又描述不出来,强化学习也会有这种感觉,我们在算法中不会也描述不了这种感觉, 只是会简单粗暴的告诉他,你这样做有“好处”,长此以往,自然而然他就有了这种 感觉。在其他很多机器学习算法当中,你可能就需要去描述这种感觉了,以便下 次遇到这种情况的时候才知道怎么办。 =>思考or怀疑: 证券市场中有没有一种永恒的东西 """def __init__(self, data, stop_get=None, stop_loss=None):self.action_space = ['B', 'S', 'W'] # 买进、卖出、观望self.n_actions = len(self.action_space)# 935 940 ... 1500 每天48根盘口状态数据# data中的数据应该从935排到1500# # | time | state| R |# # | 935 | ... | ... |# # | ... | ... | ... |# # | 1500 | ... | ... |# # 虽然上文一直在说每天48个状态,其实还可以有其他选择,8个、4个等等# 都可以,这完全取决于data的长度self.data = dataself.length = len(data)self.loc = 0 # 0<=loc<=len(data)-1self.state = data.state.iloc[0]self.rewards = 0# 以data的R(一种关于收益、涨跌幅的描述)作为reward,最终的reward# 就是整个探索过程的累计回报,当这个回报大于或小于某个设定的值的时候# 也应该是一种终止状态。默认情况下,系统不止盈,完全亏损即为止损self.stop_get = stop_get if stop_get is not None else np.inf # 止盈self.stop_loss = stop_loss if stop_loss is not None else -1 # 止损# 如果把这个问题抽象出来与我们常见的强化学习例子->“找宝藏”的格子游戏# 相比,止盈就代表着找到了宝藏;止损就代表着踩到了*;与这个游戏相# 比不同点在于,我们还给机器人设定了必须在多少步(48)内找到宝藏。self.terminal = terminal # 终止状态标识符passdef step(self, action):# 根据openAI的gym规范,step函数应该返回4个参数s' reward done info# 要知道当前在那个状态即时间点,用下一时间点的R(收益)作为# 当前采取action的rewardself.loc = 1info = dict()if self.rewards < self.stop_loss:# 止损了,对于出现止损这种情况我们可以额外增加惩罚reward = 0 # 即时奖励,不增加额外惩罚done = True s_ = self.terminal info['step'] = 'sl'elif self.rewards > self.stop_get:# 止盈了,对于出现止盈这种情况我们可以额外增加奖励reward = 0 # 即时奖励,不增加额外奖励done = True s_ = self.terminal info['step'] = 'sg'elif self.loc == self.length - 1: reward = 0done = True s_ = self.terminal info['step'] = 'tml'else:reward = self.data.R.iloc[self.loc] done = False s_ = self.data.state.iloc[self.loc] info['step'] = 'ctu'if action == 'B': pass elif action == 'S':# 当R为-的时候,选择S,应该是正奖励,反过来亦然# 施加魔法的时刻reward = reward * -1else:# 选择观望,既不亏损也不会盈利,但会损失机会成本# 我们当前对观望的决策持客观态度,reward=0,这# 可能需要在不同的大盘行情下适时调整reward = 0passself.state = s_self.rewards = rewardreturn s_, reward, done, info passdef reset(self):# 重置Env,返回stateself.loc = 0self.state = self.data.state.iloc[0]self.rewards = 0return self.state pass
下面是Q-Learning算法:
import numpy as np import pandas as pd from ReinfL.envs import terminalclass QLearning:""" Agent """def __init__(self, actions, q_table=None, learning_rate=0.01, discount_factor=0.9, e_greedy=0.1):self.actions = actions # action 列表self.lr = learning_rate # 学习速率self.gamma = discount_factor # 折扣因子self.epsilon = e_greedy # 贪婪度# 列是action。if q_table is None:# q_table的索引为各状态的哈希值,这样是为了状态转移过程中迅速定位到下个状态self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float32)else:# Q-learning是一种离线算法,即每一次训练得到的# q_table还可以用于下一次训练self.q_table = q_tabledef check_state_exist(self, state):# 检测 q_table 中有没有这个 state# 如果还没有当前 state, 那我们就插入一组全 0 数据,# 作为这个 state 的所有 action 的初始值# state对应每一行,如果不在Q表中,就添加一行if state not in self.q_table.index:# 插入一组全 0 数据,给每个action赋值为0self.q_table = self.q_table.append( pd.Series( [0] * len(self.actions), index=self.q_table.columns, name=state, ) )def choose_action(self, state):# 根据 state 来选择 actionself.check_state_exist(state) # 检查此 state 是否在 q_table 中存在# 选行为,用 Epsilon Greedy 贪婪方法if np.random.uniform() < self.epsilon:# 随机选择 actionaction = np.random.choice(self.actions)else: # 选择 Q 值最高的 actionstate_action = self.q_table.loc[state, :]# 同一个 state, 可能会有多个相同的 Q action 值, 所以我们乱序一下# 比如B=0.4, S=0.4, W=0.1,如果不打乱顺序可能永远都会选择Bstate_action = state_action.reindex(np.random.permutation(state_action.index))# 每一行中取到Q值最大的那个action = state_action.idxmax()return actiondef learn(self, s, a, r, s_):# Q-learning算法中的核心部分->更新 Q 表中的值# s为当前状态, a为当前状态下选择的action,r为即时回报# s_是下一个状态self.check_state_exist(s_) # 检测 q_table 中是否存在 s_# Q(S,A) <- Q(S,A) a*[R v*max(Q(S',a))-Q(S,A)]q_predict = self.q_table.loc[s, a] # 根据 Q 表得到a的估计(predict)值# q_target 是现实值if s_ != terminal: # 下个 state 不是 终止符q_target = r self.gamma * self.q_table.loc[s_, :].max()else:q_target = r # 下个 state 是 终止符# 更新 Q 表中 state-action 的值self.q_table.loc[s, a] = self.lr * (q_target - q_predict)
下面这个是协调上面两个类运作起来的文件:
from ReinfL.envs import MPK from ReinfL.agents import QLearning def update(data, q_table=None):# 止盈止损分别设2个点env = MPK(data, stop_get=2, stop_loss=-2) RL = QLearning(actions=env.action_space, q_table=q_table)# 每天训练100个回合for episode in range(100):# 初始化 state(状态)state = env.reset() step_count = 0 # 记录走过的步数while True:# 更新可视化环境# env.render()# RL大脑根据state挑选actionaction = RL.choose_action(str(state))# Agent在环境中实施这个action, 并得到环境返回的下一个state, reward 和 done (是否到了结束)state_, reward, done, info = env.step(action) step_count = 1 # 增加步数# 机器人大脑从这个过渡(transition) (state, action, reward, state_) 中学习RL.learn(str(state), action, reward, str(state_))# 机器人移动到下一个 statestate = state_# Agent走到了终点if done:# print("回合 {} 结束. 总步数 : {}\n".format(episode 1, step_count))# if info['step'] in ['sl', 'sg']:# print(info)break# print('模拟交易结束了。')# print('\nQ 表:')# print(RL.q_table)return RL.q_tabledef train():import jqdatasdk as jq code = '000001.XSHE'sd = dt.datetime(2018, 10, 1) ed = dt.datetime(2018, 11, 1) jq.auth('', '')data = jq.get_price(security='000001.XSHE', frequency='5m', fields=['close'], start_date=sd, end_date=ed)data['datetime'] = data.indexdata['time'] = data.datetime.map(lambda x: x.hour*100 x.minute)data['date'] = data.datetime.dt.normalize()data = data.sort_values(['datetime'], ascending=False)data['R'] = (data.close.shift(3) / data.close - 1) * 100data.fillna(0, inplace=True)data = data.round({'R': 3})data = data.sort_values(['date', 'time'], ascending=True)# 以时间节点作为状态,择时就像算命的看黄道吉日选时辰一样# 比如你要看个时间搬家,他才不会管你所谓的黄道吉日是不是# 会下雨一样,在他们的世界里每个时辰都有应该做的事情data['state'] = data.timedata = data.loc[:, ['date', 'state', 'R']]# 到这里为止data应该是这样的:# # | date | state| R |# # | ... | 935 | ... |# # | ... | ... | ... |# # | ... | 1500 | ... |# qtb = Nonefor k, g in data.groupby(['date']): print('train to:', k) try: qtb = update(g, qtb)# qtb是这样的:# # | state| B | S | W |# # | 935 | | | |# # | ... | ... | ... | ... |# # | 1500 | | | |# # 每一行的state会对应B S W 三个值,值越大的说# 明越应该选择其对应的动作,例如在935时B对应的# 值最大,说明935这个时候选择买入是最好的except Exception as e: print(e) print('\nQ 表:') print(qtb) qtb['time'] = qtb.index qtb.to_csv(path_or_buf='E:\wv\ReinfL\model_param\qtb({})_{}.csv'. format(code, sd.strftime('%Y_%m_%d')), index=False)# 通过用一段时间的K线训练后,qtb很好的概括了这段时间内# 一天中什么时间该买入,什么时间该卖出。后面的任务就是# 来验证这个历史经验是否对未来做投资决策有益pass train()
最后得出Q表之后,只需要编写简单的代码就能验证这个Q表的效果了。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...