# How far forward should we push? Attack, defence and the price of a rule

Source: https://www.footballdatascience.co.uk/learn/attack-without-losing-the-defence
Published: 2026-10-01

> Push more players forward and you score more, but you concede more too. Calculus finds the best setting, and when the board sets a limit on goals conceded, it puts a price on that limit, in points a season for every goal.

## The football question

A new manager wants his team to be braver: more players in the box, full-backs pushing on. The board is nervous. **"Fine," says the chairman, "but we're not conceding more than 42 goals this season."**

Pushing forward brings goals at one end and costs them at the other. So how far forward should the team push? And what does the chairman's rule cost: nothing, a little, or a lot?

## The concept

[Part 1](/learn/what-are-we-optimising) had yes-or-no decisions, sign a player or don't, so we could check every combination. This decision is a dial. The team can push forward a little, a lot or anywhere in between, so there are endless settings to choose from. Calculus finds the best one without trying them all, the same way [gradient descent](/learn/gradient-descent) finds the bottom of a valley.

Two new ideas come with it:

- A constraint is **binding** if it changes the answer, and not binding if the best answer obeys it anyway.
- A binding constraint has a **shadow price**: how much the objective would improve if the rule were loosened by one unit. Here, how many points a season one more goal conceded would buy.

## A football example

Call the setting *a*: 0 means everyone behind the ball, 1 means all-out attack. Here are made-up curves for the goals scored and conceded in a season, scaled so that a middling setting (0.5) gives an average Scottish top-flight side: 51 goals at each end, about 1.34 a game, the average over 26 seasons of real data.

$$\begin{aligned} \text{scored} &= 30 + 54a - 24a^2 \\ \text{conceded} &= 36 + 12a + 36a^2 \end{aligned}$$

<div class="plain" markdown="1">
In plain football

- **Parking the bus** (*a* = 0) scores 30 and concedes 36.
- **Scored** rises as the team pushes on, but more slowly each time: the third player in the box adds less than the first.
- **Conceded** rises faster and faster: each extra player forward leaves a bigger gap behind.
- The numbers are made up. Real teams don't come with curves like these, but most managers would recognise the shape.
</div>

To turn goals into points we use the values from [how many points is a goal worth?](/research/points-per-goal): each goal scored is worth about 0.65 points over a season and each goal conceded costs about 0.60. An average side takes about 52 points, so

$$\begin{aligned} \text{points} = 52 \;&+ 0.65 \times (\text{scored} - 51) \\ &- 0.60 \times (\text{conceded} - 51) \end{aligned}$$

<div class="plain" markdown="1">
In plain football

- Start from the average side's **52 points**.
- Add 0.65 points for every goal scored above the average 51, and take off 0.60 for every goal conceded above it.
- The middling setting gives 52 points exactly. **All-out attack** (*a* = 0.8) scores 57.8 but concedes 68.6, and takes only 45.9 points.
</div>

## The best setting

Points rise as the team pushes forward, reach a peak, then fall. At the peak, a little more pushing would add exactly as many points through goals scored as it loses through goals conceded. The slope of each curve, its [derivative](/learn/derivative), says how many goals a small push brings, so the peak is where

$$\begin{aligned} 0.65 \times (54 - 48a) &= 0.60 \times (12 + 72a) \\ a &= 0.375 \end{aligned}$$

<div class="plain" markdown="1">
In plain football

- **54 − 48*a*** is how fast goals scored rise as the team pushes on; **12 + 72*a*** is how fast goals conceded rise. Both come from the curves above.
- The left side is the points a small push wins at the front; the right side is the points it costs at the back.
- Where they're equal, pushing on stops paying. That's at *a* = **0.375**, a bit more careful than middling.
- There the team scores **46.9**, concedes **45.6** and takes **52.58** points.
</div>

<figure class="rank-chart">
<div role="img" aria-label="Points a season against how far forward the team pushes, from 0 to 0.8. Points rise from 47.4 to a peak of 52.6 at 0.375, then fall to 45.9 at 0.8. A rule of conceding 42 or fewer rules out everything beyond 0.274, where the team takes 52.2 points.">

</div>
<figcaption>Points a season for each setting, on the made-up curves. The white dot is the best setting with no rule, the top of the hill: 52.6 points. The chairman's rule forbids everything to the right of the gold line, so the best the team can do is push right up to it: the gold dot, 52.2.</figcaption>
</figure>

## Add the chairman's rule

The best setting concedes 45.6 goals, more than the chairman's 42. So the rule is **binding**: it changes the answer.

When a rule binds, the best answer sits right on it. Pushing forward still adds points, so the team should push until it concedes exactly 42: *a* = **0.274**, scoring 43.0 and taking **52.20** points. The rule costs **0.38 points a season**.

A rule of 50 goals wouldn't bind at all: the best setting already concedes fewer, so the rule costs nothing. A rule only has a cost when it stops you doing what you'd otherwise do.

## The price of a rule

Here's what different limits cost, on the same curves:

| Most conceded | Points lost | Price of one more goal |
|---|---|---|
| 50 (doesn't bind) | 0.00 | 0.00 |
| 45 | 0.01 | 0.03 |
| 42 | 0.38 | 0.24 |
| 40 | 1.06 | 0.47 |
| 38 | 2.38 | 0.91 |

Two lessons are in that table.

**The top of the hill is flat.** Tightening the limit from 45.6 to 45 costs almost nothing, 0.01 points. Each further goal costs more than the one before, and a 38-goal limit costs 2.38 points, more than two draws. A gentle rule near the best setting is nearly free; a strict one gets expensive fast.

**The last column is the shadow price.** It says what loosening the rule by one goal would be worth:

$$\text{price} = \frac{\text{points a small push wins}}{\text{goals that push concedes}}$$

<div class="plain" markdown="1">
In plain football

- At the 42-goal limit, a small push forward wins points at the rate of **0.24 points for every extra goal conceded**. So letting the team concede 43 would buy roughly a quarter of a point.
- Where a rule doesn't bind, its price is **0**: loosening it changes nothing.
- At a 38-goal limit the price is 0.91 points a goal, more than the 0.60 a goal conceded costs, because each goal let in comes with more than two extra scored at the other end.
- Mathematicians call this number the **Lagrange multiplier**, after Joseph-Louis Lagrange. It's the same idea in any optimisation with a rule, however many dials there are.
</div>

Part 1 priced the "must sign a centre-back" rule at 0.75 points by solving the problem with and without it. That works for a yes-or-no rule. A rule like the chairman's can be loosened one goal at a time, and the shadow price says what each goal of slack is worth before anyone has to solve the problem again.

## Why it matters

Every club runs on rules: a wage ceiling, a limit on a player's minutes, a minimum number of home-grown players, "no more than 42 conceded". Each one that binds has a price, whether or not anybody works it out. Shadow prices say which rule is costing the most, and so which one is worth arguing about. A rule with a price of zero isn't hurting anyone, and a rule with a high price deserves a good reason. The next part in this series asks what to do when there's more than one objective, and no single "best".

## Limitations

- **The curves are made up.** They're scaled to an average Scottish side, but nobody can measure how far forward a team "pushes" as one number, and our data has no tactical settings.
- **One dial is a simplification.** Real tactics have many settings at once. The method extends to them, with one slope per dial, as in machine learning.
- **Goals aren't everything.** The 0.65 and 0.60 points per goal are averages over many seasons, and points also depend on when the goals come.
- **The price is for small changes.** At the 42-goal limit, loosening it to 43 gains a little less than 0.24 points, because the price falls as the rule loosens. It's a guide, not an exact quote.

## Try it yourself

Before reading the code, guess: if the chairman's limit were 40 goals, how far forward should the team push? Then change the curves in the code to suit a stronger team, one that scores more for each step forward, and see whether its best setting moves forward or back.

## Reproduce the analysis

This finds the best setting two ways, by trying every setting and by calculus, checks they agree, and prints every number in the article. Nothing to download.

```python
# Made-up curves for a season, scaled so a middling setting (0.5) gives an average Scottish top-flight side: 51 goals
# scored, 51 conceded and 52 points. a runs from 0 (everyone behind the ball) to 1 (all-out attack).
def scored(a):
    return 30 + 54 * a - 24 * a ** 2


def conceded(a):
    return 36 + 12 * a + 36 * a ** 2


def points(a):   # 0.65 points per goal scored, 0.60 per goal conceded (Research: how many points is a goal worth?)
    return 52 + 0.65 * (scored(a) - 51) - 0.60 * (conceded(a) - 51)


def slope(fn, a, h=1e-6):
    return (fn(a + h) - fn(a - h)) / (2 * h)


# Way 1: try every setting from 0 to 1 in steps of 0.0001 and keep the best one the rule allows.
grid = [i / 10000 for i in range(10001)]


def search(limit=None):
    return max((a for a in grid if limit is None or conceded(a) <= limit), key=points)


# Way 2: calculus. With no rule, the best setting is where points stop rising: 0.65 × (54 − 48a) = 0.60 × (12 + 72a).
# If that concedes too many, the best setting is the one that concedes exactly the limit: 36a² + 12a + 36 = limit.
free = (0.65 * 54 - 0.60 * 12) / (0.65 * 48 + 0.60 * 72)


def solve(limit=None):
    if limit is None or conceded(free) <= limit:
        return free
    return (-12 + (144 + 144 * (limit - 36)) ** 0.5) / 72


print(f"Parking the bus (0): scored {scored(0):.0f}, conceded {conceded(0):.0f}, points {points(0):.1f}")
print(f"Middling (0.5):      scored {scored(0.5):.0f}, conceded {conceded(0.5):.0f}, points {points(0.5):.1f}")
print(f"All-out (0.8):       scored {scored(0.8):.1f}, conceded {conceded(0.8):.1f}, points {points(0.8):.1f}")
print()
print("Rule             setting  scored  conceded  points  cost  price")
for limit in [None, 50, 45, 42, 40, 38]:
    a = solve(limit)
    assert abs(a - search(limit)) < 0.0001   # the two ways agree
    price = slope(points, a) / slope(conceded, a)   # points per extra goal the rule allows; 0 if it doesn't bind
    rule = "none" if limit is None else f"concede <= {limit}"
    print(f"{rule:<16} {a:7.3f}  {scored(a):6.1f}  {conceded(a):8.1f}  {points(a):6.2f}  {points(free) - points(a):4.2f}"
          f"  {price:5.2f}")
```

It prints:

```text
Parking the bus (0): scored 30, conceded 36, points 47.4
Middling (0.5):      scored 51, conceded 51, points 52.0
All-out (0.8):       scored 57.8, conceded 68.6, points 45.9

Rule             setting  scored  conceded  points  cost  price
none               0.375    46.9      45.6   52.58  0.00   0.00
concede <= 50      0.375    46.9      45.6   52.58  0.00   0.00
concede <= 45      0.360    46.3      45.0   52.57  0.01   0.03
concede <= 42      0.274    43.0      42.0   52.20  0.38   0.24
concede <= 40      0.206    40.1      40.0   51.52  1.06   0.47
concede <= 38      0.122    36.2      38.0   50.20  2.38   0.91
```

The price column measures the slopes at the best setting rather than solving the problem again, which is how shadow prices are usually found in practice.

## Further reading

- [Lagrange multiplier](https://en.wikipedia.org/wiki/Lagrange_multiplier), Wikipedia. The general method for optimising with a rule, and why the multiplier is a price.
- [Shadow price](https://en.wikipedia.org/wiki/Shadow_price), Wikipedia. The same number as economists use it.
- [How many points is a goal worth?](/research/points-per-goal) The Research piece behind the 0.65 and 0.60 points per goal.
