Quant500 Academy

3. From a CSV to a database: when you need one and when it is just for show

Level 5: Storing, updating and scaling

There comes a moment in every project when somebody says this now calls for a proper database. Sometimes they are right. Far more often they are describing a problem they do not have, and the price of building it is paid in maintenance hours that come out of the budget for researching strategies. This module is the criterion for deciding it without embarrassing yourself in either direction.

What a database does that files do not

A database is not a place to store tables; files are already that. What it adds is a set of guarantees that with files you would have to program yourself:

The real signs that files are no longer enough

None of these signs is the size of the data, and that is the most expensive misunderstanding of all:

Now the flip side. If you are a single user, with daily data for a few thousand stocks, a process that runs once a day and a store of tens of megabytes, you have none of those signs. You have well-organized files, and that is a correct architecture, not a provisional phase.

SQL without a database: the middle ground almost nobody uses

The argument you hear most in favor of a database is that SQL is more expressive than chaining pandas operations together. That is true, and it does not imply what people think it implies, because there are embedded analytical engines that run SQL directly over your columnar files, with no server, no users and no process to maintain.

import duckdb

con = duckdb.connect()   # in memory: there is no server and no database file

top = con.execute("""
    SELECT simbolo, avg(volumen) AS vol_medio, count(*) AS sesiones
    FROM read_parquet('almacen/precios/**/*.parquet', hive_partitioning = true)
    WHERE anio BETWEEN 2023 AND 2024 AND volumen > 0
    GROUP BY simbolo
    HAVING count(*) > 400
    ORDER BY vol_medio DESC
    LIMIT 10
""").df()

Look at what that SELECT does: the filter on anio is pushed down to the file layer and only the 2023 and 2024 folders are opened, and of the columns only the three that appear in the query are read. You get the expressiveness of SQL over the store from module one, without having installed a server. For the vast majority of personal projects, the conversation ends here.

Time series or relational: what really changes

If a database really is needed, the next choice is not a matter of fashion either:

import sqlite3

con = sqlite3.connect("almacen/precios.db")
con.execute("""
    CREATE TABLE IF NOT EXISTS precios (
        simbolo TEXT NOT NULL,
        fecha   TEXT NOT NULL,
        cierre  REAL NOT NULL,
        volumen INTEGER NOT NULL,
        descargado_en TEXT NOT NULL,
        PRIMARY KEY (simbolo, fecha)
    )
""")

# The ON CONFLICT clause needs that key or a unique index to exist:
# without the PRIMARY KEY above, this statement does not run, it fails
con.executemany("""
    INSERT INTO precios (simbolo, fecha, cierre, volumen, descargado_en)
    VALUES (?, ?, ?, ?, ?)
    ON CONFLICT(simbolo, fecha) DO UPDATE SET
        cierre = excluded.cierre,
        volumen = excluded.volumen,
        descargado_en = excluded.descargado_en
""", filas)
con.commit()

A case: the project that set up a server for nothing

A typical and perfectly recognizable case. A single user, 500 stocks, daily data, twenty-five years: 3.1 million rows, tens of megabytes in columnar format. The process runs once a day and takes a couple of minutes. Setting up a database server there adds a service that has to be started, updated, backed up and monitored, and a connection layer that can fall over at three in the morning.

What do you get in exchange? With a single writer, no concurrency guarantee you actually need. With known queries launched from your own script, no planner to save you. With a single user, no access control.

The honest criterion is this: most personal projects never need more than well-organized files plus an embedded SQL engine when an odd query comes along. The day two writers, a second person or an integrity requirement appears, you migrate, and by then you will know exactly which table and which constraints you need. Migrating late costs you a while; migrating early costs you the whole project.

The rest of Quant500

What you are learning, applied to the index every day:

The model portfolio, weekly in your inbox

Every Saturday, its week in one minute: how it went, the best and the worst, and which insiders bought with their own money. Plus, the day the model is rebuilt, which stocks come in and which go out. A simulation, not advice. At most one email a week, no ads, no account; one-click unsubscribe. Made by one person: Pol Gracia.

A model portfolio with simulated results, not a personalized recommendation. Your email is used for this only: privacy.

Behind the Academy and the rest of Quant500 there is one person: Pol Gracia Serrahima.