Q-learning: one update rule, repeated

On this page
  1. The table
  2. The update rule
  3. What the learning rate does over many updates
  4. Off-policy: learning greedy while behaving otherwise
  5. When the table is right
  6. Traps
  7. The code
  8. Summary
  9. References

The previous post ended with a promise: if the learner had the action values, the values of doing each action in each state, acting would be one line long, take the largest. It also ended with the hard part, filling those values in from experience, when the environment hands over one reward at a time and never says what the value was. Q-learning is the standard answer. It is a table and one update rule, and this post goes through both on the same ten days of SPY from January 2023, working one update by hand and then repeating it to see what the learning rate does.

The table

The Q-table has one row per state and one column per action. Each cell holds the learner’s current estimate of the action value from the previous post: the return it expects if it takes that action in that state and acts well afterwards. Mitchell writes the estimate with a hat, Q̂, to stress that the table is the learner’s hypothesis about the real values, not the values themselves (section 13.3.2). Sutton and Barto initialise it arbitrarily, except that any terminal state’s row is zero (section 6.5); Ritter’s trading version starts it at zero or at prior information if there is any (section 3.4).

Here is a tiny one, two states and two actions, with starting guesses I made up, in percent of the position:

Statego longgo cash
cash0.500.00
long0.800.10

Read it by row. In state cash the table currently prefers “go long” (0.50 beats 0.00); in state long it prefers “go long” too, which here means stay long (0.80 beats 0.10). A cell is an estimate of a return, so it is in the units of the reward, not a probability and not a next state. The greedy choice compares cells within one row; the number 0.50 is not good or bad on its own, it is only better than 0.00.

The update rule

After one step the learner knows four things: the state it was in, the action it took, the reward it got, and the state it landed in. Q-learning uses them to change exactly one cell, the one for the state and action it just experienced (Sutton and Barto, equation 6.8):

Q, the cell being updated; α, the learning rate; γ, the discount factorR, the reward just received; S′, the state landed in

Q(St,At)←Q(St,At)+α [Rt+1+γ max⁡aQ(St+1,a)−Q(St,At)]\key{Q(S_t, A_t)} \leftarrow \key{Q(S_t, A_t)} + \key{\alpha}\,\Big[\good{R_{t+1}} + \key{\gamma}\,\max_{a} Q(\good{S_{t+1}}, a) - \key{Q(S_t, A_t)}\Big]

The square bracket is the whole idea. The first two terms inside it are a target: the reward you actually got, plus the discounted best value the table currently claims for where you landed. That is the recursion from the previous post, value now equals reward now plus discounted value next, with the actual reward standing in for the expected one and the table’s own entry standing in for the true value next. Subtracting the old estimate gives the error, how far the old guess was from the target; Sutton and Barto call it the temporal difference error (section 6.1). Multiplying by the learning rate α\key{\alpha} and adding it back moves the old guess that fraction of the way towards the target. Nothing else in the table moves.

Worked on the morning of 2023-01-03. The learner is in state cash, takes “go long”, receives day 1’s return as its reward, −0.42%\good{-0.42\%}, and lands in state long. With γ=0.9\key{\gamma} = \key{0.9} the best entry in the long row is 0.80, so the target is −0.42+0.9×0.80=0.30-0.42 + 0.9 \times 0.80 = \good{0.30}. The old guess in the cash, go long cell was 0.50, so the error is 0.30−0.50=−0.200.30 - 0.50 = \risk{-0.20}: the day went worse than the table expected, and the cell has to come down. How far down is the learning rate’s decision:

Learning rateNew estimate = old + α × errorIn words
0.20.50 + 0.2 × (−0.20) = 0.46a fifth of the way to the target
0.90.50 + 0.9 × (−0.20) = 0.32nearly all the way
1.00.50 + 1.0 × (−0.20) = 0.30the target replaces the old guess

The same arithmetic can be written as a blend, (1−α)×(1 - \alpha) \times old + α×+\ \alpha \times target: 0.8×0.50+0.2×0.30=0.460.8 \times 0.50 + 0.2 \times 0.30 = 0.46. Same number, and the blend form makes plain that α=1\key{\alpha} = 1 keeps nothing of the old guess and α=0\key{\alpha} = 0 keeps all of it. Mitchell’s first version of the rule has no learning rate at all (equation 13.7, the α=1\key{\alpha} = 1 row), because he starts with a world where the same action in the same state always gives the same reward and the same next state, so there is nothing to average. In a market there is, which is why the fraction matters.

One step, one cell, one fraction of the way. The target is reward now plus discounted best next; the learning rate decides how much of the gap one experience is allowed to close.

What the learning rate does over many updates

A single update is a fraction of the way. Repeated updates of the same cell towards the same target close the gap geometrically: after n updates the share of the original gap still open is (1−α)n(1 - \key{\alpha})^n. At α=0.2\key{\alpha} = \key{0.2} ten updates leave 10.7%\risk{10.7\%} of the gap; at 0.5\key{0.5} they leave a tenth of a percent; at 0.9\key{0.9} the second update is already within one percent. If the target never moved, a large learning rate would simply be faster.

Targets move. Each day’s reward is one draw from whatever the market does, so the target the rule builds is different every day even when the state is the same. To see what the learning rate does with that, take the long row’s “go long” cell, starting at 0.80, and feed it one update per day of the episode with the day’s reward as its reward and the rest of the table held still (so the “best next” term stays at 0.9×0.80=0.720.9 \times 0.80 = 0.72 and only the learning rate is at work). The ten targets, reward plus 0.72, run from −0.42\bad{-0.42} on day 3 to 3.01\good{3.01} on day 4, and their average is 1.12:

Learning rateEstimate after each day, days 1 to 10Biggest day-to-day move
0.20.70, 0.86, 0.60, 1.08, 1.00, 1.08, 1.26, 1.23, 1.20, 1.070.48
0.90.35, 1.38, −0.24, 2.69, 0.87, 1.37, 1.92, 1.17, 1.11, 0.592.93

At 0.2\key{0.2} the estimate drifts towards the average of the targets and ends near it, at 1.07\key{1.07}. At 0.9\key{0.9} it is almost a copy of the latest target, swinging from −0.24\bad{-0.24} to 2.69\good{2.69} in a day, and it ends at 0.59\key{0.59} only because the last day was poor. It has not settled, and it never will while the rewards keep varying.

{"panels":[{"x0":58.04,"x1":565.2,"y0":29.16,"y1":223.28,"title":"1. One cell moved towards one fixed target, again and again","xlabel":"Number of updates of the same cell (same target each time)","ylabel":"Estimate in the cell (% of the position)","xlog":false},{"x0":58.04,"x1":565.2,"y0":311.96,"y1":506.08,"title":"2. The same cell fed the ten daily targets of January 2023","xlabel":"Day of the episode (0 = before the first update)","ylabel":"Estimate in the cell (% of the position)","xlog":false}],"series":[{"panel":0,"label":"learning rate 0.2","color":"var(--chart-blue, #3b6ea5)","x":[0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[0.5,0.459804,0.427647,0.401922,0.381341,0.364877,0.351705,0.341168,0.332739,0.325995,0.3206],"sx":[81.09,127.2,173.31,219.41,265.52,311.62,357.73,403.83,449.94,496.04,542.15],"sy":[37.98,73.28,101.51,124.1,142.17,156.63,168.19,177.44,184.85,190.77,195.51]},{"panel":0,"label":"learning rate 0.5","color":"var(--chart-grey, #a3a19c)","x":[0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[0.5,0.39951,0.349265,0.324142,0.311581,0.3053,0.30216,0.30059,0.299805,0.299412,0.299216],"sx":[81.09,127.2,173.31,219.41,265.52,311.62,357.73,403.83,449.94,496.04,542.15],"sy":[37.98,126.22,170.34,192.39,203.42,208.94,211.7,213.08,213.76,214.11,214.28]},{"panel":0,"label":"learning rate 0.9","color":"var(--chart-maroon, #b6262c)","x":[0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[0.5,0.319118,0.301029,0.299221,0.29904,0.299022,0.29902,0.29902,0.29902,0.29902,0.29902],"sx":[81.09,127.2,173.31,219.41,265.52,311.62,357.73,403.83,449.94,496.04,542.15],"sy":[37.98,196.81,212.69,214.28,214.44,214.45,214.45,214.45,214.45,214.45,214.45]},{"panel":1,"label":"target that day: reward + 0.9 \u00d7 best next","color":"var(--chart-grey, #a3a19c)","x":[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[0.29902,1.49201,-0.421335,3.01319,0.663321,1.42129,1.9848,1.08408,1.10795,0.536783],"sx":[127.2,173.31,219.41,265.52,311.62,357.73,403.83,449.94,496.04,542.15],"sy":[460.24,398.94,497.25,320.78,441.52,402.58,373.62,419.9,418.68,448.02]},{"panel":1,"label":"estimate, learning rate 0.2","color":"var(--chart-blue, #3b6ea5)","x":[0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[0.8,0.699804,0.858246,0.60233,1.0845,1.00027,1.08447,1.26454,1.22844,1.20435,1.07083],"sx":[81.09,127.2,173.31,219.41,265.52,311.62,357.73,403.83,449.94,496.04,542.15],"sy":[434.5,439.65,431.51,444.66,419.88,424.21,419.88,410.63,412.48,413.72,420.58]},{"panel":1,"label":"estimate, learning rate 0.9","color":"var(--chart-maroon, #b6262c)","x":[0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0],"y":[0.8,0.349118,1.37772,-0.241429,2.68773,0.865762,1.36574,1.92289,1.16796,1.11396,0.5945],"sx":[81.09,127.2,173.31,219.41,265.52,311.62,357.73,403.83,449.94,496.04,542.15],"sy":[434.5,457.67,404.81,488.01,337.5,431.12,405.43,376.8,415.59,418.37,445.06]}]} 2026-10-23T07:37:26.804499 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/ 0 2 4 6 8 10 Number of updates of the same cell (same target each time) 0.300 0.325 0.350 0.375 0.400 0.425 0.450 0.475 0.500 Estimate in the cell (% of the position) 1. One cell moved towards one fixed target, again and again learning rate 0.2 learning rate 0.5 learning rate 0.9 0 1 2 3 4 5 6 7 8 9 10 Day of the episode (0 = before the first update) −0.5 0.0 0.5 1.0 1.5 2.0 2.5 3.0 Estimate in the cell (% of the position) 2. The same cell fed the ten daily targets of January 2023 target that day: reward + 0.9 × best next estimate, learning rate 0.2 estimate, learning rate 0.9

Sutton and Barto give the mechanism (section 2.5). With a constant learning rate the estimate is a weighted average of every target it has ever seen, with the weight on a target seen k updates ago equal to α(1−α)k\key{\alpha}(1 - \key{\alpha})^k: the most recent target gets α\key{\alpha}, the one before it a factor (1−α)(1 - \key{\alpha}) less, and so on, an exponentially fading memory. At α=0.2\key{\alpha} = \key{0.2} the target from five updates ago still carries a weight of 0.066; at 0.9\key{0.9} it carries nothing to three decimal places. A constant learning rate therefore never converges in the strict sense; the estimate keeps responding to the latest rewards, which the book notes is what you want when the true values drift, as a market’s do. The alternative is a learning rate that shrinks with each visit to the cell; if it shrinks slowly enough to overcome any starting value but fast enough to settle (their conditions 2.7), the estimate converges. The sample average, one over the number of visits, is the classic choice.

Off-policy: learning greedy while behaving otherwise

Look at the target once more. It uses the best entry in the next state’s row, whatever the learner goes on to do there. The learner’s own next action may be exploratory, a random choice made to see what happens; the target ignores it and asks what the greedy action would be worth. That is what makes Q-learning off-policy: the behaviour that generates experience and the policy whose values are being learned are different policies. Sutton and Barto put it as the learned table “directly approximates” the optimal action values “independent of the policy being followed” (section 6.5). The behaviour policy still matters, but only in one way: it decides which cells get visited, and therefore which cells get updated.

The on-policy cousin is Sarsa, whose target uses the next action the learner actually takes (equation 6.7). The two differ only in that one term, and the difference shows up where exploring is dangerous. In the book’s cliff-walking example (6.6), Q-learning learns the values of the shortest path along the cliff edge, because that is the greedy path, and then falls off it now and then while exploring; Sarsa, which counts its own exploratory stumbles in its targets, learns the longer, safer path and scores better while learning. Which is right depends on whether the agent will keep exploring once deployed. For a trading agent that explores in a simulator and acts greedily on real capital, the Q-learning target is the one that matches.

When the table is right

Mitchell’s convergence theorem (13.1) says what it takes for the table to end up at the true values, in his deterministic world: rewards are bounded, the discount factor is below 1, and every state–action pair is visited infinitely often. Sutton and Barto’s statement for the random case adds the shrinking-learning-rate condition above (section 6.5). The visiting condition is the one to carry. The update touches one cell per step, so a cell the behaviour policy never reaches keeps its starting value forever, however long the learner runs. That is absence of evidence, not convergence, and a table can look stable because nothing is being updated rather than because the estimates are right. It is also why a learner that only ever takes the greedy action is in trouble: the cells it believes are worse are the ones it will never check, and Mitchell’s remark (section 13.3.5) is that convergence requires the agent to keep trying every action with some probability. Exploration: when to try something worse on purpose is about that choice.

Mitchell also gives the intuition behind convergence (section 13.3.4). Each update’s new value depends only partly on other error-prone estimates, the max over the next row, and that part is multiplied by γ\key{\gamma}; the rest is the observed reward, which carries no estimation error. So the largest error in the table shrinks by at least a factor of γ\key{\gamma} each time every cell has been updated once. Values propagate backwards from wherever reward appears, one step per visit, which is why in his grid world a table of zeros changes nothing until the agent first reaches the goal, and then one more cell per episode (section 13.3.3).

Notation in other books

Sutton and Barto write the update on the time-indexed pair (St,At)(S_t, A_t) with reward Rt+1R_{t+1} and next state St+1S_{t+1}, and call α\alpha the step-size parameter. Mitchell writes the same pair as (s,a)(s, a) with rr and s′s', the estimate as Q^\hat{Q}, and, in his first treatment, assigns the target directly with no step size (his Table 13.1). Ritter follows Sutton and Barto’s symbols and calls α\alpha the step-size parameter too; in that paper it is restricted to the open interval between 0 and 1. The quantity in the bracket is the TD error, δt\delta_t, in Sutton and Barto’s equation 6.5; some texts write the target as yy and the update as the blend (1−α)Q+αy(1 - \alpha)Q + \alpha y.

Traps

People thinkActually
A cell holds a probabilityit holds an estimated return, in the reward’s units; rows are compared, not read as odds
The reward is discounted in the targetonly the next state’s value is; the reward just received counts in full
A learning rate of 0 wipes the estimateit freezes it; 1 replaces it with the target
Q-learning learns the value of what the agent doesit learns the value of acting greedily, whatever the agent does; that is the off-policy point
The update refreshes the whole tableone cell per step; unvisited cells keep their starting values
A table that has stopped changing has convergedit may just have stopped being visited, or be fed a learning rate too small to move
Taking the max is harmlessthe max over noisy estimates is biased upwards (Sutton and Barto, section 6.7); double Q-learning is one fix

The code

The snippet reproduces the one update of the worked example at three learning rates. Variables:

VariableHolds
Qthe starting table: rows cash and long, columns go long and go cash, in percent
rewardSPY’s return on 2023-01-03, the reward for going long that morning
targetreward plus 0.9 times the best entry in the long row
import pandas as pd

url = "https://raw.githubusercontent.com/ankurtrapasiya/examples/main/data/prices.csv"
px = pd.read_csv(url, index_col="date", parse_dates=True)["SPY"]
r = (px / px.shift(1) - 1).dropna()
reward = 100 * r[pd.Timestamp("2023-01-03")]        # day 1's return, in percent

Q = pd.DataFrame({"go long": [0.50, 0.80], "go cash": [0.00, 0.10]}, index=["cash", "long"])
old = Q.loc["cash", "go long"]                       # the cell for what the learner just did
target = reward + 0.9 * Q.loc["long"].max()          # reward now + gamma * best entry in the next row
for alpha in (0.2, 0.9, 1.0):
    print(f"alpha {alpha}: {old:.2f} + {alpha} x ({target - old:+.2f}) = {old + alpha * (target - old):.2f}")

It prints three lines: 0.50 + 0.2 × (−0.20) = 0.46, then 0.32 at 0.9, then 0.30 at 1.0. The .loc["long"] picks a row of the table by its label and .max() takes the largest entry in it, which is the max over actions in the formula.

The script behind the real numbers

The tables and the chart come from examples/q-learning-one-update-rule/q_update.py, which runs on the blog’s five tickers. To run it yourself, clone the examples repo: its README has a three-command Docker setup, or run uv run q-learning-one-update-rule/q_update.py.

Summary

IdeaIn one lineOn the January 2023 episode
Q-tableone cell per state and action, holding an estimated returncash, go long starts at 0.50
Targetreward now plus γ times the best entry in the next row−0.42 + 0.9 × 0.80 = 0.30
Errortarget minus the old estimate0.30 − 0.50 = −0.20
Learning ratethe fraction of the error applied0.46 at 0.2, 0.32 at 0.9, 0.30 at 1
Constant αan exponentially fading average of past targets0.2 ends near the mean target, 0.9 copies the last one
Off-policythe target uses the greedy next action, not the one takenSarsa would use the action actually taken
Convergenceevery pair visited again and again, with a shrinking αan unvisited cell stays at its starting value

References

  • Sutton and Barto, Reinforcement Learning: An Introduction, 2nd edition (MIT Press, 2018). Section 6.1 for the TD(0) update, bootstrapping and the TD error (equations 6.2 and 6.5); section 6.4 for Sarsa and its on-policy target (equation 6.7); section 6.5 for the Q-learning update (equation 6.8), the off-policy property, the convergence conditions and the procedural algorithm; Example 6.6 for cliff walking; section 6.7 for maximisation bias and double learning; section 2.5 for the constant step size as an exponential recency-weighted average (equation 2.6) and the stochastic approximation conditions (2.7); Example 6.2 for estimates that keep fluctuating under a constant step size.
  • Tom M. Mitchell, Machine Learning (McGraw-Hill, 1997). Section 13.3.1 for the Q function; section 13.3.2 for the table of estimates and the deterministic training rule (equation 13.7, Table 13.1); section 13.3.3 for the worked single update and the backward propagation of values; section 13.3.4 for the convergence theorem (13.1) and the error shrinking by γ; section 13.3.5 for why every action must keep being tried.
  • Gordon Ritter, Machine Learning for Trading (2017, SSRN 3015609). Section 3.3 for the greedy and ε-greedy policies; section 3.4 for the Q-matrix initialised to zero or to prior information and the update rule with its step-size parameter.
Tagged machine-learning, reinforcement-learning, trading← Home