Apprendre en essayant
En apprentissage supervisé, chaque exemple vient avec la bonne réponse. Le trading n'est pas ainsi : personne ne vous donne la meilleure position, vous agissez et le marché répond par un gain ou une perte. L'apprentissage par renforcement modélise cela comme un agent qui observe un état, prend une action, reçoit une récompense et passe à un nouvel état, avec pour but de maximiser la récompense totale dans le temps. Pour le trading, l'état peut être les rendements récents ou la position actuelle, les actions acheter, garder ou vendre, et la récompense le gain du pas moins les coûts. La difficulté est que les actions ont des conséquences plus tard et que l'agent doit découvrir ce qui paie en essayant.
# 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 : la valeur d'une action dans un état
Le Q-learning garde une table Q[état, action] : la récompense totale attendue si l'on prend action dans état puis qu'on agit bien. Après chaque pas, il pousse l'entrée vers une meilleure estimation, la cible : la récompense qu'on vient de recevoir plus la valeur actualisée de la meilleure action dans l'état suivant, reward + gamma * max(Q[état suivant]). La mise à jour est Q[s, a] += alpha * (cible - Q[s, a]), avec un taux d'apprentissage alpha. Le facteur d'actualisation gamma (0 à 1) dit combien les récompenses futures comptent : gamma = 0 est un agent à courte vue, proche de 1 un agent patient, et le retour d'une séquence est r0 + gamma r1 + gamma^2 r2 + .... À un pas terminal, il n'y a pas d'état suivant et la cible est simplement la récompense.
# 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 furtherExplorer, et une boucle d'entraînement complète
Un agent qui prend toujours la meilleure action qu'il connaît ne découvre jamais si une autre est meilleure. L'epsilon-greedy y remédie : avec une probabilité epsilon il explore une action aléatoire, sinon il exploite l'action de plus grand Q, les égalités allant à la première. Epsilon commence en général haut puis décroît. La boucle est alors : observer l'état, choisir une action, recevoir la récompense et l'état suivant, mettre à jour Q, recommencer. Dans un marché jouet où un jour de hausse est souvent suivi d'un autre, l'agent qui voit « hier était en hausse » apprend que rester long rapporte, et une table de deux états et trois actions suffit à le montrer.
# 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 agent observe un état, agit, reçoit une récompense (gain moins coûts) et apprend quelles actions paient ; le retour est la somme actualisée
r0 + gamma r1 + gamma^2 r2 + .... - Le Q-learning met à jour
Q[s, a] += alpha * (reward + gamma * max Q[s'] - Q[s, a])(justerewardà un pas terminal) et ne modifie pas l'ancienne table sur place ici. - L'epsilon-greedy explore avec la probabilité epsilon et exploite sinon le plus grand Q ; entraînez et testez sur des périodes séparées, facturez des coûts réalistes et comparez à des références simples.
À suivre : le projet de synthèse, une bibliothèque complète de valorisation ou de stratégie qui réunit tout.
Lab : ce qui est noté
- Explorer ou exploiter avec
choose_action25 pts - Apprendre d'un pas avec
q_update25 pts - Faire payer le trading dans
step_reward25 pts - Additionner l'avenir avec
discounted_return25 pts