Aprender tentando
No aprendizado supervisionado cada exemplo vem com a resposta certa. O trading não é assim: ninguém diz a melhor posição, você age e o mercado responde com lucro ou prejuízo. O aprendizado por reforço modela isso como um agente que observa um estado, toma uma ação, recebe uma recompensa e passa a um novo estado, com o objetivo de maximizar a recompensa total ao longo do tempo. No trading o estado pode ser os retornos recentes ou a posição atual, as ações comprar, manter ou vender, e a recompensa o lucro do passo menos os custos. A dificuldade é que as ações têm consequências depois e o agente precisa descobrir o que paga tentando.
# Reward of one step: profit from the position held, minus the cost of changing position
def step_reward(position, price_change, previous_position, cost):
return position * price_change - cost * abs(position - previous_position)
print(round(step_reward(1, 0.5, 0, 0.1), 4)) # 0.4 went long, the price rose 0.5, paid 0.1 to trade
print(round(step_reward(-1, 0.5, 1, 0.1), 4)) # -0.7 flipped from long to short just before a rise: lost 0.5 and paid 0.2
print(step_reward(1, 0.5, 1, 0.1)) # 0.5 holding the same position costs nothingQ-learning: o valor de uma ação em um estado
O Q-learning mantém uma tabela Q[estado, ação]: a recompensa total esperada se você toma ação em estado e depois age bem. Após cada passo empurra a entrada para uma estimativa melhor, o alvo: a recompensa recém-recebida mais o valor descontado da melhor ação no estado seguinte, reward + gamma * max(Q[estado seguinte]). A atualização é Q[s, a] += alpha * (alvo - Q[s, a]), com uma taxa de aprendizado alpha. O fator de desconto gamma (0 a 1) diz o quanto as recompensas futuras importam: gamma = 0 é um agente míope, perto de 1 um paciente, e o retorno de uma sequência é r0 + gamma r1 + gamma^2 r2 + .... Num passo terminal não há estado seguinte e o alvo é só a recompensa.
# One Q-learning update on a table of 2 states x 3 actions (short, flat, long)
import numpy as np
def q_update(q, state, action, reward, next_state, alpha, gamma):
updated = q.copy()
target = reward if next_state is None else reward + gamma * updated[next_state].max()
updated[state, action] += alpha * (target - updated[state, action])
return updated
q = np.zeros((2, 3))
q = q_update(q, 0, 2, 1.0, 1, 0.5, 0.9) # in state 0 we went long and earned 1.0
print(q[0]) # [0. 0. 0.5] moved halfway (alpha 0.5) toward the target of 1.0
q = q_update(q, 0, 2, 1.0, 1, 0.5, 0.9)
print(q[0]) # [0. 0. 0.75] a second visit moves it furtherExplorar e um laço de treino completo
Um agente que sempre toma a melhor ação que conhece nunca descobre se outra é melhor. O epsilon-greedy resolve: com probabilidade epsilon explora uma ação aleatória, senão explora a ação de maior Q, e os empates vão para a primeira. O epsilon costuma começar alto e decair. O laço é então: observar o estado, escolher uma ação, receber a recompensa e o estado seguinte, atualizar Q, repetir. Num mercado de brinquedo em que um dia de alta costuma ser seguido de outro, o agente que vê «ontem foi de alta» aprende que ficar comprado paga, e uma tabela de dois estados e três ações basta para mostrar isso.
# A toy market where an up day tends to be followed by another up day; the agent learns to follow the trend
import random
import numpy as np
rng = random.Random(1)
positions = [-1, 0, 1] # action 0 = short, 1 = flat, 2 = long
q = np.zeros((2, 3)) # state 0 = yesterday down, state 1 = yesterday up
alpha, gamma, cost = 0.1, 0.9, 0.0005
state, previous = 1, 0
for step in range(20000):
epsilon = max(0.05, 1 - step / 10000)
u, explore = rng.random(), rng.randrange(3)
action = explore if u < epsilon else int(np.argmax(q[state]))
up = rng.random() < (0.65 if state == 1 else 0.35) # the trend continues 65% of the time
change = 0.01 if up else -0.01
reward = positions[action] * change - cost * abs(positions[action] - previous)
next_state = 1 if up else 0
target = reward + gamma * q[next_state].max()
q[state, action] += alpha * (target - q[state, action])
state, previous = next_state, positions[action]
print(int(np.argmax(q[0])), int(np.argmax(q[1]))) # 0 2 after a down day go short, after an up day go long# The discounted return: with gamma below 1 a reward now is worth more than the same reward later
def discounted_return(rewards, gamma):
total = 0.0
for reward in reversed(rewards):
total = reward + gamma * total
return total
rewards = [1.0, 1.0, 1.0]
print(discounted_return(rewards, 0.5)) # 1.75 1 + 0.5 + 0.25
print(discounted_return(rewards, 1.0)) # 3.0 a patient agent: a plain sum
print(discounted_return(rewards, 0.0)) # 1.0 a short-sighted agent: only the first reward counts- Um agente observa um estado, age, recebe uma recompensa (lucro menos custos) e aprende quais ações pagam; o retorno é a soma descontada
r0 + gamma r1 + gamma^2 r2 + .... - O Q-learning atualiza
Q[s, a] += alpha * (reward + gamma * max Q[s'] - Q[s, a])(sórewardnum passo terminal) e aqui não edita a tabela antiga no lugar. - O epsilon-greedy explora com probabilidade epsilon e senão explora o maior Q; treine e teste em períodos separados, cobre custos realistas e compare com referências simples.
A seguir: o projeto final, uma biblioteca completa de precificação ou de estratégia que reúne tudo.
Lab: o que é avaliado
- Explorar ou explorar o conhecido com
choose_action25 pts - Aprender de um passo com
q_update25 pts - Cobrar pelo trading em
step_reward25 pts - Somar o futuro com
discounted_return25 pts