Aprender probando
En el aprendizaje supervisado cada ejemplo trae la respuesta correcta. El trading no es así: nadie te dice la mejor posición, actúas y el mercado responde con una ganancia o una pérdida. El aprendizaje por refuerzo lo modela como un agente que observa un estado, toma una acción, recibe una recompensa y pasa a un nuevo estado, con el objetivo de maximizar la recompensa total en el tiempo. En trading el estado puede ser los rendimientos recientes o la posición actual, las acciones comprar, mantener o vender, y la recompensa la ganancia del paso menos los costes. La dificultad es que las acciones tienen consecuencias más tarde y el agente debe descubrir qué paga probando.
# 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: el valor de una acción en un estado
Q-learning mantiene una tabla Q[estado, acción]: la recompensa total esperada si tomas acción en estado y luego actúas bien. Tras cada paso empuja la entrada hacia una mejor estimación, el objetivo: la recompensa recién recibida más el valor descontado de la mejor acción en el estado siguiente, reward + gamma * max(Q[estado siguiente]). La actualización es Q[s, a] += alpha * (objetivo - Q[s, a]), con una tasa de aprendizaje alpha. El factor de descuento gamma (0 a 1) dice cuánto importan las recompensas futuras: gamma = 0 es un agente miope, cerca de 1 uno paciente, y el retorno de una secuencia es r0 + gamma r1 + gamma^2 r2 + .... En un paso terminal no hay estado siguiente y el objetivo es solo la 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 y un bucle de entrenamiento completo
Un agente que siempre toma la mejor acción que conoce nunca descubre si otra es mejor. Epsilon-greedy lo arregla: con probabilidad epsilon explora una acción aleatoria, si no explota la acción de mayor Q, y los empates van a la primera. Epsilon suele empezar alto y decaer. El bucle es entonces: observar el estado, elegir una acción, recibir la recompensa y el estado siguiente, actualizar Q, repetir. En un mercado de juguete donde un día al alza suele ir seguido de otro, el agente que ve «ayer fue al alza» aprende que mantenerse largo paga, y una tabla de dos estados y tres acciones basta para mostrarlo.
# 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- Un agente observa un estado, actúa, recibe una recompensa (ganancia menos costes) y aprende qué acciones pagan; el retorno es la suma descontada
r0 + gamma r1 + gamma^2 r2 + .... - Q-learning actualiza
Q[s, a] += alpha * (reward + gamma * max Q[s'] - Q[s, a])(solorewarden un paso terminal) y aquí no edita la tabla antigua en el sitio. - Epsilon-greedy explora con probabilidad epsilon y si no explota el mayor Q; entrena y prueba en periodos separados, cobra costes realistas y compara con referencias simples.
A continuación: el proyecto final, una biblioteca completa de valoración o de estrategia que reúne todo.
Lab: qué se califica
- Explorar o explotar con
choose_action25 pts - Aprender de un paso con
q_update25 pts - Cobrar por operar en
step_reward25 pts - Sumar el futuro con
discounted_return25 pts