Technique explained as code
Momentum by rate of change
Rate of change is one line of arithmetic: how far has price moved over the last so many days, in percent. Code you can run, and a result that shows why one good looking test proves nothing.
Risk warning: CFDs and spread bets are complex instruments and come with a high risk of losing money rapidly due to leverage. You can lose money on these products, and each provider publishes its own loss figure under FCA rules. You should consider whether you understand how they work and whether you can afford to take the high risk of losing your money. Automated and API trading can amplify losses through bugs, connectivity failures and runaway orders.
Teaching material only. This page explains how a technique works as code and how to test an idea honestly. It is not investment advice, not a recommendation and not an invitation to trade. Every price on it is invented, so no result here says anything about any real market, and nothing here suggests that a technique makes money.
What it is
Rate of change compares today's price with the price a fixed number of days ago and gives the difference as a percentage. Above zero means price is higher than it was. Below zero means lower.
The rule on this page is in the market while the ten day rate of change is above zero and out while it is below.
The idea behind it
It is the plainest way to put a number on momentum, the idea that what has been rising tends to keep rising for a while. It needs no averages and no smoothing, only two prices.
Because it uses only two prices, it is jumpy. The reading changes whenever the price leaving the window differs from the price entering it, so the rule trades often. That makes it a good example for learning what costs do.
The code
The script prints the total before costs, the costs, and the total after, so the three can be compared directly.
Data the script reads: invented-prices.csv. Invented values, never a real market. Save it in one folder and run the script from that folder.
The file that was run: momentum_roc.py
# Momentum by rate of change: how far has price moved over the last N days, in percent?
# Prices come from invented-prices.csv: a random walk made by a script, never a real market.
# The result therefore says nothing about any real market. Read it for the method.
import csv
closes = []
with open("invented-prices.csv", newline="") as f:
for row in csv.DictReader(f):
closes.append(float(row["close"]))
def rate_of_change(prices, end, days):
"""Percentage change over `days` days, ending at position `end`."""
return (prices[end] / prices[end - days] - 1) * 100
# Example values, picked only because they are easy to follow. They are not a suggestion.
DAYS = 10 # how far back to look
COST = 0.10 # points paid on every completed trade, a stand in for the spread
holding = False
entry = 0.0
results = []
START = DAYS + 1
for day in range(START, len(closes)):
# Measured up to yesterday's close. Today's close is only the price we act at.
roc = rate_of_change(closes, day - 1, DAYS)
price = closes[day]
if not holding and roc > 0:
holding, entry = True, price # price is higher than N days ago: get in
elif holding and roc < 0:
results.append(price - entry - COST) # price is lower than N days ago: get out
holding = False
if holding:
results.append(closes[-1] - entry - COST)
before_costs = sum(results) + COST * len(results)
print("Rate of change on the last day:", round(rate_of_change(closes, len(closes) - 1, DAYS), 2), "percent")
print("Trades:", len(results))
print("Trades that ended above zero:", len([r for r in results if r > 0]))
print("Total points before costs:", round(before_costs, 2))
print("Costs paid:", round(COST * len(results), 2))
print("Total points after costs:", round(sum(results), 2))
print("Simply holding over the same days:", round(closes[-1] - closes[START] - COST, 2))What the run printed
Rate of change on the last day: -0.58 percent Trades: 45 Trades that ended above zero: 20 Total points before costs: 8.56 Costs paid: 4.5 Total points after costs: 4.06 Simply holding over the same days: -10.11
Output of the run on , copied as printed. Every input to this run was invented, so the result says nothing about any real market.
How to read it
This run ended above zero while holding ended below it. Read that with great care, because it is exactly the kind of result that misleads people.
The prices are a random walk. The script that made them gives tomorrow no memory of today, so there is no momentum in the file to capture. The result is luck, and the page on how a backtest lies shows that a rule made of coin flips can score higher on the same file.
Forty five trades, fewer than half of them above zero, and more than half of the total before costs went on costs. The section on costs on that page uses this same rule: at double the cost per trade the total is below zero. A result that depends on the cost being exactly what you guessed is not a result. None of this says anything about any real market.
How it fails or misleads
- It trades often, so it is at the mercy of costs. A small error in the cost you assume changes the sign of the result.
- It reacts to the price that drops out of the window as much as to the one that enters. A sharp move ten days ago can flip the reading today when nothing at all happened today.
- In a market going sideways the reading crosses zero constantly and every crossing is a trade.
- A single run above zero is not evidence. On a file that ends lower than it began, any rule that is out of the market part of the time has a fair chance of ending above holding, simply by missing some of the fall.
- The number of days is a free setting, and the result is sensitive to it.
The page on how a backtest lies shows the mistakes that make any rule look better in a test than it is.
What to try changing
These are programming exercises. Each one changes the code and asks you to explain what you see. None of them is a search for settings to use.
- Run it with DAYS set to each value from 5 to 30 and print the total for each. How many are above zero? What does the spread tell you about the one on this page?
- Require the rate of change to be above 1 percent to get in, not just above zero. Count how many trades that removes.
- Work out the cost per trade at which the total reaches zero, then check your answer against the page on how a backtest lies.
- Split the prices in half and run each half separately.
- Write the rate of change for every day into a new CSV file beside the closing price, so you can look at it in a spreadsheet.
How this page was made
Each script on this page was run with Python 3.12 on Windows, standard library only, from a folder holding the data files listed. No network and no account. Each exited with code 0, and what is shown is everything it printed.
The data files whose names begin with the word invented are written by make_invented_data.py from fixed seeds, so anyone can make the same files again and get the same output. This site is not published if the code or a data file has changed since the run.