Lernen durch Ausprobieren
Beim überwachten Lernen kommt jedes Beispiel mit der richtigen Antwort. Beim Trading ist das anders: Niemand nennt Ihnen die beste Position, Sie handeln und der Markt antwortet mit Gewinn oder Verlust. Reinforcement Learning modelliert das als Agenten, der einen Zustand beobachtet, eine Aktion wählt, eine Belohnung erhält und in einen neuen Zustand wechselt, mit dem Ziel, die Gesamtbelohnung über die Zeit zu maximieren. Beim Handel kann der Zustand aus jüngsten Renditen oder der aktuellen Position bestehen, die Aktionen aus Kaufen, Halten oder Verkaufen und die Belohnung aus dem Gewinn des Schritts abzüglich Kosten. Die Schwierigkeit: Aktionen haben spätere Folgen, und der Agent muss durch Ausprobieren entdecken, was sich auszahlt.
# 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: der Wert einer Aktion in einem Zustand
Q-Learning führt eine Tabelle Q[Zustand, Aktion]: die erwartete Gesamtbelohnung, wenn man Aktion im Zustand wählt und danach gut handelt. Nach jedem Schritt schiebt es den Eintrag zu einer besseren Schätzung, dem Ziel: die soeben erhaltene Belohnung plus der abgezinste Wert der besten Aktion im Folgezustand, reward + gamma * max(Q[Folgezustand]). Das Update lautet Q[s, a] += alpha * (Ziel - Q[s, a]) mit einer Lernrate alpha. Der Diskontfaktor gamma (0 bis 1) sagt, wie viel künftige Belohnungen zählen: gamma = 0 ist ein kurzsichtiger Agent, nahe 1 ein geduldiger, und der Return einer Folge ist r0 + gamma r1 + gamma^2 r2 + .... Bei einem Endschritt gibt es keinen Folgezustand, und das Ziel ist nur die Belohnung.
# 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 furtherErkunden und eine ausgearbeitete Trainingsschleife
Ein Agent, der stets die beste ihm bekannte Aktion wählt, erfährt nie, ob eine andere besser ist. Epsilon-greedy behebt das: Mit Wahrscheinlichkeit epsilon erkundet er eine zufällige Aktion, sonst nutzt er die Aktion mit dem größten Q, bei Gleichstand die erste. Epsilon beginnt meist hoch und nimmt ab. Die Schleife lautet dann: Zustand beobachten, Aktion wählen, Belohnung und Folgezustand erhalten, Q aktualisieren, wiederholen. In einem Spielzeugmarkt, in dem auf einen Aufwärtstag meist ein weiterer folgt, lernt der Agent, der „gestern war aufwärts“ sieht, dass Long-Bleiben sich lohnt, und eine Tabelle aus zwei Zuständen und drei Aktionen reicht, um das zu zeigen.
# 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- Ein Agent beobachtet einen Zustand, handelt, erhält eine Belohnung (Gewinn minus Kosten) und lernt, welche Aktionen sich lohnen; der Return ist die abgezinste Summe
r0 + gamma r1 + gamma^2 r2 + .... - Q-Learning aktualisiert
Q[s, a] += alpha * (reward + gamma * max Q[s'] - Q[s, a])(bei einem Endschritt nurreward) und ändert hier die alte Tabelle nicht an Ort und Stelle. - Epsilon-greedy erkundet mit Wahrscheinlichkeit epsilon und nutzt sonst das größte Q; trainieren und testen Sie auf getrennten Perioden, setzen Sie realistische Kosten an und vergleichen Sie mit einfachen Basislinien.
Als Nächstes: das Abschlussprojekt, eine durchgängige Bewertungs- oder Strategiebibliothek, die alles zusammenführt.
Lab: Was bewertet wird
- Mit
choose_actionerkunden oder ausnutzen25 Pkt. - Mit
q_updateaus einem Schritt lernen25 Pkt. - Handeln in
step_rewardbezahlen lassen25 Pkt. - Mit
discounted_returndie Zukunft aufsummieren25 Pkt.