Broker API HubMenu

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.

Independent and unofficialEducational onlyInvented data onlyCode run

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

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.

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.