请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化平台 帖子:3365793 新帖:0

强化学习入门:基于Q-learning算法的日内择时策

耶伦发表于:5 月 9 日 21:45回复(1)

上一篇文章,我粗略的介绍了一下运用强化学习探索基于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表的效果了。

全部回复

0/140

量化课程

    移动端课程