Inventory Management Software

Kardex Tauro

Kardex Tauro® Inventory Software is designed to efficiently manage your warehouse or storage facility, and it is quick and easy to learn.

Kardex Tauro is free for non-commercial use.
It does not require an internet connection; it runs on Windows.

Python code: Bollinger bands with mean and standard deviation (free download)

Python code: Bollinger bands with mean and standard deviation (free download)

Open almost any price chart and you will meet three curved lines travelling together: one down the middle and two that hug it from above and below. Those are Bollinger bands, and the name makes them sound far harder than they are. Behind the lines there are only two everyday ideas: the average of the last twenty days, and how far the price usually drifts away from that average. This article hands you the whole Python program, commented in English, so you can run it on your own machine and watch the three lines appear over a sample series. It is written for the small business owner, the accountant and the warehouse clerk who want to understand what those lines say before they trust them with money.

⬇ Download the code (ZIP)

Notice: this is educational material, and it is not a recommendation to buy or sell. The sample prices are made up and the historical data is quoted only for practice. A past result guarantees nothing about the future, so talk to a licensed professional before you invest.

Before we touch the code, here is what travels inside the compressed file:

File inside the ZIPWhat it is for
bandas_bollinger.pyThe program, commented line by line in English
datos/prices.csvThe sample series with 180 daily closes, ready to practise on
salida_ejemplo.txtThe output you should get when you run it, so you can compare
README.mdThe guide that ships with the package, with the steps and two warnings worth money

What the program does

The program is short and always does the same thing, in this order:

  1. It reads the file datos/prices.csv, which carries one date and one close per line.
  2. It walks the series with a window of twenty closes: the twenty days that end on the row it is looking at.
  3. For that window it works out the mean, which is the plain average, and the standard deviation, which is one number for how much the closes move around that average.
  4. It draws two bands: the upper one, which is the mean plus twice the deviation, and the lower one, which is the mean minus twice the deviation.
  5. It measures the width of the band as a percentage of the mean, so you can tell whether the price is calm or restless.
  6. It counts how many closes finished above the upper band and how many fell below the lower one, and it checks that the lower band stays under the mean and the mean stays under the upper band on every row.
  7. It prints the result and saves it to salida/bollinger.csv, a file you can open in Excel or any spreadsheet.

How to run it

You need one thing only: Python 3.11 or newer, installed. Nothing else, because the program uses the standard library alone. The steps are simple: download the ZIP with the button above, unpack it into a folder, open your system terminal (on Windows, Command Prompt or PowerShell) and move into that folder. Then type the program name and press Enter:

python bandas_bollinger.py

If the python command does not answer on your machine, try py on Windows or python3 on Linux and Mac. There is nothing to install, no sign-up, no internet needed: the program reads the file that comes inside the ZIP and writes its result next to it, in the salida folder.

The code, explained

These are the five pieces that matter. The rest of the file is presentation detail, so the on-screen table lines up and reads without twisting your neck.

First, the two numbers that define the bands: the window of closes and the factor. The window is twenty days, which is the long-standing habit with these bands, and the factor is two deviations:


VENTANA = 20                                       # 20-close window: the long-standing one for these bands
FACTOR = Decimal("2")                              # Factor 2: the bands sit 2 deviations from the mean

A plain-language note is worth it here. The mean is the average of the last twenty days. The standard deviation is a single number that sums up how much the price usually moves around that average: if the closes jump around, the deviation is large; if they stay glued to the average, it is small. Doubling it is a way of saying "the range the price has almost always stayed inside". Raise the factor and the bands widen; lower it and they narrow. The program always prints which factor it used, so there is no doubt.

Second, the deviation itself. There is a detail here that trips up a lot of people, and the code states it in a comment: it divides by twenty, not by nineteen. That is the population deviation; it gives a slightly narrower band and it is the one these bands traditionally use. If you prefer the sample version, you change one line, but pick one and always say which, because the number of band touches changes with the other one:


def desviacion(valores: list[Decimal], promedio: Decimal) -> Decimal:
    """Population standard deviation of the closes in the window."""
    # POPULATION standard deviation: divide by n (the 20 closes), not by n - 1
    cuadrados = sum(((valor - promedio) ** 2 for valor in valores), Decimal("0"))
    return (cuadrados / Decimal(len(valores))).sqrt()

Third, the sliding window. The program does not recompute the whole series each time: it slides the window one day at a time. That is why the table starts on the twentieth row and the first nineteen rows never show up: they have no twenty closes behind them, so no band can be computed. That is the reason your table may not start where you expected, and not a bug.


def calcular(precios: list[dict]) -> list[dict]:
    """Walks the series with a sliding window and computes the mean, the deviation and the bands."""
    filas = []
    for indice in range(VENTANA - 1, len(precios)):
        # The window is the 20 closes that end on today's row
        ventana = [precio["cierre"] for precio in precios[indice - VENTANA + 1: indice + 1]]

Fourth, the width. It is simply the distance between the two bands, divided by the mean: when the price gets restless the band widens, and when it calms down the band narrows. It lets you compare how jumpy one stretch was against another, as long as you keep the same window and the same factor:


    for fila in filas:
        # Width as a percentage of the mean: (upper - lower) / mean
        fila["anchura"] = redondear((fila["superior"] - fila["inferior"]) / fila["media"] * CIEN)

Fifth, the count of touches. The program walks the table it has just built and counts the closes that stepped above the upper band and the ones that dropped below the lower band. Those two figures are everything the program says about how the series behaved, and it says it without decoration:


    arriba = sum(1 for fila in filas if fila["cierre"] > fila["superior"])
    abajo = sum(1 for fila in filas if fila["cierre"] < fila["inferior"])

What you will see on screen

The output is a table of the last ten days with the date, the close, both bands, the mean and the width, followed by a summary with the counts. A real slice of the output shipped in the package looks like this:

LAST 10 DAYS
-----------------------------------------------------------------------
Date            Close   Lower band         Mean   Upper band    Width %
-----------------------------------------------------------------------
2026-06-21     120.85       116.01       120.12       124.23       6.84
2026-06-22     116.96       115.66       119.99       124.32       7.22
2026-06-23     118.49       115.63       119.76       123.89       6.90
2026-06-24     116.43       115.42       119.40       123.37       6.66
2026-06-25     117.39       115.38       119.13       122.87       6.29
2026-06-26     115.28       115.02       118.76       122.50       6.30
2026-06-27     115.53       114.80       118.42       122.04       6.11
2026-06-28     117.18       114.97       118.17       121.37       5.42
2026-06-29     116.97       114.86       118.11       121.35       5.49
2026-06-30     117.24       114.84       118.09       121.35       5.51
-----------------------------------------------------------------------

SUMMARY
  Closes above the upper band: 15
  Closes below the lower band: 5
  Average band width: 9.80 %
  Smallest width: 3.02 %   Largest width: 22.74 %

Those summary figures are what the program prints on the sample series: fifteen closes went above the upper band and five dropped below the lower one, out of 161 rows with a computed band. The average width came out at nine point eight per cent, the narrowest at three point zero two per cent and the widest at twenty-two point seven four per cent. The program also checks that the lower band stays under the mean and the mean under the upper band, and that check ties on every single row.

Now the part that matters most: touching a band is not an order to buy or to sell. A close stepping above the upper band only says that the price moved more than usual over the last twenty days; a close dropping below says the same thing in the other direction. Nothing more. The program does not predict whether tomorrow goes up or down, and neither figure tells you what to do with your money. If you want to use them as a signal you will have to pick the rule yourself and write it down, and even then it stays your rule, not a promise from the program.

The data: what the ZIP carries and how to use your own

The ZIP carries a made-up sample series of 180 closes, and that is on purpose: it lets you practise the calculation without exposing any real market data. The file is a plain CSV, two columns, no mystery:

ColumnWhat it holdsExample
dateThe day of the price, written as year-month-day2026-01-02
closeThe closing price for that day, with a decimal point99.94

To work with your own prices, replace datos/prices.csv with your file, keeping those two column names and the row order, oldest date first. If your spreadsheet writes decimals with a comma, change the format before saving or the program will reject the row. It is also worth saying where the real data used in the other pieces of this workshop comes from: the bitcoin series and the S&P 500 index were downloaded from CoinGecko (api.coingecko.com) and from the Federal Reserve of St. Louis, at fred.stlouisfed.org/series/SP500, on 2026-09-25, and they are used only as historical practice data. If you publish your own charts, cite your source the way we do here.

Common mistakes and tips

  • If running it throws an error that mentions datos/prices.csv, it is nearly always because the terminal is sitting in a different folder. The program looks for the datos folder next to the .py file, so run it from where you unpacked it.
  • If your table starts on an unexpected date, count the rows in your file: the first nineteen never have a band, because twenty closes are needed for the first window.
  • If you change the factor from two to three, the bands widen and the touches drop. That is a valid experiment; just write down which factor produced each table.
  • Never mix the population deviation with the sample one on the same chart. Pick one and keep it fixed across every run.
  • The program computes with exact decimals, not with the decimals of the machine. If you come from Excel, the difference shows up as the absence of that phantom cent that sometimes creeps into totals.
  • Save the output CSV of every run with the date in the file name. When you want to compare two different series, you will be glad the runs are kept apart.
  • Before drawing conclusions from a count, look at how many rows the table has. Fifteen touches over 161 rows reads very differently from fifteen touches over twenty rows.

When this is not enough

This program is here to explain the calculation and to show your own series with three lines drawn over it. What it does not do is keep your prices, your stock or your costs. If the real problem in your business is that you do not know what is sitting in the warehouse, what each batch cost you, or what margin is left in the goods, a band script will not solve it. When spreadsheets and loose scripts stop being enough, the next step is a stock ledger system such as Kardex Tauro: the software itself is free and its job is to keep receipts, issues and the running balance of every product tidy, without a fight over formulas.

Notice: we repeat the disclaimer because it is the serious part of this article. This is educational material and it is not a recommendation to buy or sell; the sample prices are made up and the historical ones are quoted only for practice, and a past result guarantees nothing about the future. The source of the real data is cited above, in the data section, and the ZIP carries this same disclaimer in writing inside its instructions file. Talk to a licensed professional before you invest.

⬇ Download the code (ZIP)

Download the package, run it on the sample series, and only then swap in your own prices. Watching the three lines come out of your own numbers, while knowing exactly what they do and do not mean, is the best way to stop believing them too much.

Share
Link copied
Microsoft Store from Microsoft StoreDownload free
Chatea por WhatsApp