Lesson 0.2.1 · 35 min

Installing Python with uv: versions and support cycle

Python is not just one thing: there is a new version every October and each one lives for five years. This lesson installs the course version with uv, explains what it means for a version to be "supported" and shows how to check, from the code itself, which interpreter it is running on.

Fig. — Support cycle of Python 3.10 to 3.15: two years of bug fixes, then security only, and end of life after five years
01/06
Module
0.2 · Modern working environment
Duration
35 min
Updated
Level 0 lessons
  1. 0.1 · Welcome and expectations

    1. 0.1.1 Systematic, algorithmic and quantitative trading versus discretionary trading
    2. 0.1.2 Realistic expectations: costs, time and risk of loss
    3. 0.1.3 Course structure, entry tests, personal repository and learning with AI
  2. 0.2 · Modern working environment

    1. 0.2.1 Installing Python with uv: versions and support cycle
    2. 0.2.2 Virtual environments and dependencies: uv init, uv add, pyproject.toml and lockfiles
    3. 0.2.3 Editors (VS Code, Cursor, PyCharm) and notebooks (Jupyter, JupyterLab, marimo)
    4. 0.2.4 Basic terminal, paths, environment variables and .env files
    5. 0.2.5 Git and GitHub: commits, branches, pull requests and .gitignore
    6. 0.2.6 Introduction to AI coding assistants: prompts, review and tests
  3. 0.3 · Language fundamentals

    1. 0.3.1 Variables, types and operators; floating point and decimal for prices and ticks
    2. 0.3.2 Strings, f-strings and formatting numbers and dates
    3. 0.3.3 Lists, tuples, dictionaries, sets and comprehensions
    4. 0.3.4 Control flow: if, elif, else, for, while and match-case
    5. 0.3.5 Functions: arguments, *args and **kwargs, lambdas and scope
    6. 0.3.6 Exceptions; modules and packages, with Python 3.15 lazy imports
  4. 0.4 · Intermediate Python

    1. 0.4.1 Object-oriented programming: classes, inheritance, composition and dataclasses
    2. 0.4.2 Typing: type hints, mypy or ty, and Pydantic for configurations
    3. 0.4.3 Iterators, generators, decorators and context managers
    4. 0.4.4 Files (CSV, JSON, YAML, TOML) and dates with time zones
    5. 0.4.5 Professional logging and testing with pytest
    6. 0.4.6 Concurrency: threading, multiprocessing, asyncio and free-threading
  5. 0.5 · Numerical computing and data

    1. 0.5.1 NumPy 2: arrays, broadcasting, vectorization, linear algebra and random generators
    2. 0.5.2 pandas 3.0: Series, DataFrame, time indexes, Copy-on-Write, the str type and pd.col()
    3. 0.5.3 Selection, filtering, groupby, merge, pivot, resample and moving windows
    4. 0.5.4 Polars: lazy versus eager API, expressions, performance and when to pick it over pandas
    5. 0.5.5 DuckDB and essential SQL: SELECT, JOIN, GROUP BY and window functions on Parquet
  6. 0.6 · Visualisation

    1. 0.6.1 Object-oriented Matplotlib, Seaborn and Plotly: price, drawdown and candlestick charts
    2. 0.6.2 Quick dashboards with Streamlit: a monitoring dashboard in a single script
  7. 0.7 · Minimum mathematics and statistics

    1. 0.7.1 Simple and log returns, compound interest and descriptive statistics
    2. 0.7.2 Probability and distributions, correlation versus causation and simple regression
    3. 0.7.3 Intuitive linear algebra: vectors and matrices as groundwork for portfolios

Why the version matters

A Python program does not run on its own: it is run by an interpreter, the python program that reads your code and turns it into operations. Each version of the interpreter adds features to the language, changes details of the standard library and, over time, stops receiving fixes. The course libraries (NumPy, pandas, Polars…) publish packages for specific versions and drop the old ones.

For systematic trading, this has three practical consequences:

  • Reproducibility. A backtest must give the same result today and a year from now. If the interpreter or a library changes, a calculation can change with them: a different default value, a rounding, a warning that did not appear before.
  • Security. A live system connects to a broker with API keys. If its interpreter no longer receives security patches, any flaw that is discovered will stay open forever.
  • Compatibility. What is new in libraries reaches recent versions first. Staying on an old version ends up leaving you out of the improvements in pandas 3.0 or NumPy 2 (module 0.5).

The course uses Python 3.14 (specifically 3.14.8, the version all the code you will see has been run with). 3.13 also works. This lesson explains why and how to install it.

The life cycle of a version

Since 2019, Python has released a new version every year, in October (PEP 602). Each version goes through five phases, which the Python developer's guide describes as follows1Note 1 · Python Developer's Guide, "Status of Python versions" (devguide.python.org/versions), accessed on October 21, 2026. The end-of-support dates from 3.11 on are planned and are adjusted by each version's release managers.:

first betafinal releaseafter two yearsafter five yearsfeatureprereleasebugfixsecurityeol
Mermaid source
stateDiagram-v2
  [*] --> feature
  feature --> prerelease: first beta
  prerelease --> bugfix: final release
  bugfix --> security: after two years
  security --> eol: after five years
  eol --> [*]
  • feature and prerelease: the version is under development. Betas and release candidates are for testing libraries, not for working.
  • bugfix: the version is now stable. It receives bug and security fixes, with new installers roughly every two months (3.14.1, 3.14.2…). It lasts about two years starting with 3.13; in earlier versions, 18 months.
  • security: security patches only, released as source code and without installers.
  • end-of-life (eol): five years after the release, support ends. The interpreter keeps working, but nobody fixes it.

The figure at the top draws that cycle for versions 3.10 to 3.15, with a line at October 2026. The thick bar is the bugfix phase; the thin one, the security-only phase. The dates come from the developer's guide:

VersionReleasedPhase in October 2026End of support
3.10Oct 4, 2021End of lifeOct 1, 2026
3.11Oct 24, 2022Security onlyOct 2027 (planned)
3.12Oct 2, 2023Security onlyOct 2028 (planned)
3.13Oct 7, 2024Security onlyOct 2029 (planned)
3.14Oct 7, 2025BugfixOct 2030 (planned)
3.15Oct 9, 2026BugfixOct 2031 (planned)

Two dates this month change the map. On October 1, 2026, Python 3.10 reached the end of its support: if a server or an old tutorial uses it, it is time to migrate. On October 9, Python 3.15.0 was released2Note 2 · Python 3.15.0 release notes, python.org, October 9, 2026., which among other changes uses UTF-8 as the default encoding and introduces explicit lazy imports (lesson 0.3.6).

Installing uv and Python

uv is a Python project and package manager written in Rust by Astral. It replaces several classic tools (pip, venv, pyenv, pip-tools) with a single, much faster one. In this course it does two things: install Python versions (this lesson) and manage each project's environments and dependencies (lesson 0.2.2).

It is installed with an official script. On Linux and macOS, from the terminal (the terminal is covered in more detail in lesson 0.2.4; for now, copying and pasting is enough):

curl -LsSf https://astral.sh/uv/install.sh | sh

On Windows, from PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

If you prefer a system package manager, brew install uv (macOS) or winget install --id=astral-sh.uv -e (Windows) also work. Close and reopen the terminal and check that it responds:

uv --version

With uv installed, installing Python is a single command:

uv python install 3.14         # the latest 3.14.x
uv python list                 # installed and available versions
python3.14 --version           # uv leaves an executable named after the version
uv python upgrade 3.14         # move to the latest 3.14.x later on

uv downloads a precompiled Python distribution and stores it in its own folder. It does not overwrite anything:

  • Linux: the distribution ships its own Python, used by system tools. Do not upgrade it or install packages into it with sudo pip; a change there can break the system package manager.
  • macOS: there may be a python3 from the Xcode tools, usually old. Leave it alone.
  • Windows: you do not need the python.org installer or the Microsoft Store if you use uv.

Nor do you need to install Python by hand before starting a project: if a project asks for 3.14 and you do not have it, uv downloads it when you run the first command. In practice you will almost never call python directly, but uv run python inside a project, which picks the right version for you (lesson 0.2.2).

To pin the version of a folder, uv python pin 3.14 creates a .python-version file in it with the number. uv reads it every time you work in that folder, and other tools understand it too. The course project will have it from the start (lesson 0.2.2).

Checking the interpreter from Python

Installing is half the job. The other half is knowing, from the code itself, which interpreter it is running on: on your laptop, on a server or on someone else's computer. The standard library's sys module tells you:

IN [01]python
import sys

sys.version_info
OUT [01]Interpreter version
sys.version_info(major=3, minor=14, micro=8, releaselevel='final', serial=0)

sys.version_info is a named tuple: major, minor and patch (micro) version, and the release level (final in a stable release; alpha, beta or candidate in a pre-release). Since it is a tuple, it can be compared with another tuple:

IN [02]python
print(sys.version_info[:2])
print(sys.version_info >= (3, 13))
print(sys.version_info >= (3, 15))
OUT [02]Comparing with a minimum version
(3, 14)
True
False

The platform module adds the rest: the version as text, the implementation (CPython is the reference one, the one uv installs) and the machine's operating system and architecture. On Linux, platform.system() returns Linux; on macOS, Darwin; on Windows, Windows. The output below is from the machine where the course was run:

IN [03]python
import platform

print("Python:", platform.python_version())
print("Implementation:", platform.python_implementation())
print("System:", platform.system(), platform.machine())
OUT [03]Implementation and platform
Python: 3.14.8
Implementation: CPython
System: Linux x86_64

Versions as tuples, not as text

A classic mistake when working with versions is comparing them as text strings. Python compares strings character by character, and the character "1" comes before "9":

IN [04]python
versiones = ["3.9", "3.10", "3.14", "3.13"]
print(sorted(versiones))
print("3.9" > "3.10")
OUT [04]The mistake of comparing text
['3.10', '3.13', '3.14', '3.9']
True

Going by the text, 3.9 is newer than 3.10. It is the same problem as sorting dates written as "9/3/2025" or amounts stored as text: the order looks reasonable until it stops being so. The solution is to convert each version into a tuple of integers before comparing it:

IN [05]python
def como_tupla(version: str) -> tuple[int, ...]:
    """'3.10' -> (3, 10)"""
    return tuple(int(parte) for parte in version.split("."))

print(sorted(versiones, key=como_tupla))
print(como_tupla("3.9") > como_tupla("3.10"))
print(max(versiones, key=como_tupla))
OUT [05]Sorting versions as tuples
['3.9', '3.10', '3.13', '3.14']
False
3.14

Tuples are compared element by element: first the 3 with the 3 and, since they tie, the 9 with the 10, this time as numbers. That is why sys.version_info is a tuple and not text. The packaging library, which pip and uv use under the hood, does the same with more complex versions ("2.5.4", "3.0.0rc1"), but to compare Python versions the tuple is enough.

Which versions are supported today

With the table above you can compute how many months of support each version has left on a given date. A fixed date (October 21, 2026) is used so that the result does not change depending on the day you run the cell; the developer's guide gives the end-of-support dates as year and month:

IN [06]python
fin_soporte = {
    "3.10": (2026, 10), "3.11": (2027, 10), "3.12": (2028, 10),
    "3.13": (2029, 10), "3.14": (2030, 10), "3.15": (2031, 10),
}
hoy = (2026, 10)

for version, (anio, mes) in fin_soporte.items():
    meses = (anio - hoy[0]) * 12 + (mes - hoy[1])
    estado = "out of support" if meses <= 0 else f"{meses} months"
    print(f"Python {version:>5}: {estado}")
OUT [06]Remaining months of support
Python  3.10: out of support
Python  3.11: 12 months
Python  3.12: 24 months
Python  3.13: 36 months
Python  3.14: 48 months
Python  3.15: 60 months

For a new project, the rule of thumb: the most recent version with at least a few months of track record and every library available. For a live system, also a version with years of support left, because migrating a system that trades costs more than migrating a notebook.

The version, alongside the results

A backtest produces numbers: return, volatility, drawdown. Those numbers depend on the data, the code and the environment. Git will store the code (lesson 0.2.5) and your datos/ folder the data; the environment has to be recorded. A dictionary with the versions, stored alongside each result, is enough to start with:

IN [07]python
import json
import numpy as np
import pandas as pd

entorno = {
    "python": platform.python_version(),
    "implementacion": platform.python_implementation(),
    "sistema": platform.system(),
    "numpy": np.__version__,
    "pandas": pd.__version__,
}
print(json.dumps(entorno, indent=2))
OUT [07]Environment metadata for a backtest
{
  "python": "3.14.8",
  "implementacion": "CPython",
  "sistema": "Linux",
  "numpy": "2.5.4",
  "pandas": "3.0.6"
}

If six months from now the same backtest gives a different Sharpe, the first step is to compare this block with the current one. Lesson 0.2.2 goes one step further: it pins the exact versions of every library in a file (uv.lock) so that the environment is identical on any machine.

Exercises

  1. Install uv and Python 3.14 on your computer. Run uv python list and note which Python versions you already had installed (the system ones) and where they are.
Solution

The output depends on your machine. On Linux the system Python usually appears at /usr/bin/python3 (whatever version your distribution ships) and uv's 3.14 in a folder under your user. On macOS the python3 from the Xcode tools may appear and, if you use Homebrew, its own. On Windows, only the ones you installed yourself. What matters: the system Python is still there and you have not touched it.

  1. Write a function exigir_python(minima) that takes a tuple such as (3, 13) and raises a RuntimeError with a clear message if the interpreter is older. Where would you call it in a system that trades live?
Solution
IN [08]python
def exigir_python(minima: tuple[int, int]) -> None:
    actual = sys.version_info[:2]
    if actual < minima:
        raise RuntimeError(
            f"Python {minima[0]}.{minima[1]} or later is required; "
            f"this one is {actual[0]}.{actual[1]}"
        )

exigir_python((3, 13))
print("Version OK")
OUT [08]Requiring a minimum version
Version OK

At the start of the program, before connecting to the broker or loading data: it is better for the system not to start than to start on an interpreter nobody has tested it with. Exceptions are covered in lesson 0.3.6.

  1. Extend the environment metadata cell with the execution date and time in UTC and save it to an entorno.json file. Why UTC and not local time?
Solution
IN [09]python
from datetime import datetime, timezone
from pathlib import Path

entorno["ejecutado_utc"] = datetime(2026, 10, 21, 16, 30,
                                    tzinfo=timezone.utc).isoformat()
Path("entorno.json").write_text(json.dumps(entorno, indent=2))
print(json.loads(Path("entorno.json").read_text())["ejecutado_utc"])
OUT [09]Metadata with a UTC timestamp
2026-10-21T16:30:00+00:00

In your own code you would use datetime.now(timezone.utc); here the date is fixed so that the output is always the same. UTC because it is the same on every machine and has no daylight saving changes: a server in Frankfurt, another in Virginia and your laptop write the same time for the same instant. Market time zones are covered in lesson 0.4.4.

Footnotes

  1. Python Developer's Guide, "Status of Python versions" (devguide.python.org/versions), accessed on October 21, 2026. The end-of-support dates from 3.11 on are planned and are adjusted by each version's release managers. ↩

  2. Python 3.15.0 release notes, python.org, October 9, 2026. ↩

Each answer is folded under its question

Self-assessment

  1. A server running your live system has Python 3.10. What has changed since October 1, 2026?

    • ANothing: the code keeps working and will keep receiving patches
    • BPython 3.10 has stopped receiving fixes, including security fixes
    • CPython 3.10 stops working on Linux
    • Dpip can no longer install packages on 3.10
    Show answer

    Answer B. Python 3.10 has stopped receiving fixes, including security fixes

    End of support does not switch anything off: the interpreter keeps working. What ends are the patches, security ones included, and little by little libraries stop publishing releases for it. A live system should not depend on an unsupported version.

  2. What does sorted(["3.9", "3.10", "3.14"]) return?

    • A['3.9', '3.10', '3.14']
    • B['3.14', '3.10', '3.9']
    • CAn error, because they are not numbers
    • D['3.10', '3.14', '3.9']
    Show answer

    Answer D. ['3.10', '3.14', '3.9']

    Strings are compared character by character: "1" comes before "9", so "3.10" and "3.14" come before "3.9". Versions are compared as tuples of integers, like sys.version_info.

  3. What phase is Python 3.14 in, in October 2026?

    • ABugfix: it receives bug and security fixes, with installers roughly every two months
    • BDevelopment (feature)
    • CSecurity only
    • DEnd of life
    Show answer

    Answer A. Bugfix: it receives bug and security fixes, with installers roughly every two months

    It was released on October 7, 2025, and versions since 3.13 get about two years of bug fixes. It will move to security-only around October 2027 and its support ends in October 2030.

  4. Why is it worth storing the versions of Python, NumPy and pandas alongside a backtest's results?

    • ABecause regulations require it
    • BTo make the backtest run faster
    • CTo be able to reproduce the result: a version change can change a calculation, a default value or performance
    • DIt is useless if the code is in Git
    Show answer

    Answer C. To be able to reproduce the result: a version change can change a calculation, a default value or performance

    Git stores the code, not the interpreter or the libraries. If a result changes when you repeat it months later, the first question is whether the environment changed. Lesson 0.2.2 solves the problem at the root with a lockfile.

Key points

  • A new Python version comes out every October. It gets about two years of bug fixes, then security patches only, and its support ends after five years.
  • In October 2026: 3.10 out of support since the 1st, 3.15 released on the 9th; the course uses 3.14 and 3.13 also works.
  • uv installs and manages Python versions without touching the system Python: uv python install 3.14.
  • sys.version_info is a tuple: compare it with (3, 13), never compare versions as text.
  • A backtest result without the environment's version is not reproducible: store it alongside the numbers.

Resources