Skip to main content
Glama

Qanat is an agent-first backtesting engine that turns your raw data into portfolio weights through a pipeline of steps you define.

The idea

In Qanat, you build a strategy as a pipeline of tables.

It starts with your raw data: prices, news, or anything else you track. From there, you define each step of the pipeline. A step reads one or more tables and writes a new one, so you can clean the data, calculate metrics, and score your symbols. The final step outputs your portfolio weights, detailing exactly what to hold and how much.

Qanat replays this pipeline across historical data, one date at a time. It prices the portfolio's holdings, accounts for fees, and outputs a complete PnL table.

Nothing is hidden. Every table is visible on your screen, with its row count and the logic that created it. If a number looks off, you open that table and inspect the data.

As your project grows, you can plug in new data sources and build new steps on top of them.

You don't have to write the code yourself. Describe what you want in plain English, and the agent will build the step, run it, and show you the resulting table.

Qanat is built for retail traders who rebalance daily or weekly. You can't out-race an institutional hedge fund on speed, and with a longer horizon, you don't need to.

Related MCP server: mcp-dagster

Quick start

You need Python 3.10 or newer.

uv tool install qanat-fdtl                 # or: pip install qanat-fdtl
qanat init my-alpha --demo && cd my-alpha
qanat serve

The console opens on http://127.0.0.1:8420.

--demo builds four working strategies, runs the pipeline, and prices each one, so the console opens with real numbers in it. It takes about fifteen seconds. The data is synthetic, so this works with no API key and no network. Leave --demo off for an empty project.

Qanat makes no network calls on its own. The only outbound requests are the ones your data sources make.

Needs nothing but Docker, and brings its own Postgres:

git clone https://github.com/fidetolabs/qanat.git && cd qanat
docker compose up --build

Postgres is on localhost:5433, not 5432, because 5432 is often taken already. User, password and database are all qanat. Bind a directory to /project to use your own project instead of the demo.

If a port is already in use, set the host ports yourself:

POSTGRES_HOST_PORT=5434 QANAT_HOST_PORT=8421 docker compose up --build

To start over, docker compose down -v && docker compose up --build.

Use it with an agent

This is the main way to work with Qanat. You describe what you want, and the agent writes the step, runs it, and shows you the table it produced.

Add it to any MCP client:

{ "mcpServers": { "qanat": { "command": "qanat", "args": ["mcp"], "cwd": "/path/to/my-alpha" } } }

For Claude Code, claude mcp add qanat -- qanat mcp. Add --read-only and the agent can look at everything but change nothing.

Things you can ask for:

  • "What is in this project, and what feeds the momentum strategy?" The agent traces the table back to its sources and answers with data, not a guess.

  • "Add a momentum strategy and backtest it." Before running anything, it comes back with what your data can actually cover and asks you to choose the window, the rebalance, and the costs.

  • "Which of my strategies actually works?" It lists every one with what it earned, so it compares instead of speculating.

  • "Why did it lose money in March?" It opens that period and shows what was held, what each name returned, and what was traded to get there.

The console and the agent are two views of the same project, so they can never disagree about its state. The full tool list is in docs/agents.md.

How it works

Everything lives in one qanat.yaml. Nothing hides in application code.

project: equity
store: ./data/qanat.duckdb

universes:                         # which symbols a portfolio may hold
  - id: sp500
    symbols: ./universes/sp500.csv

stages:                            # order here is order in the pipeline
  - { id: raw,        kind: raw }
  - { id: normalized, kind: features }
  - { id: features,   kind: features }
  - { id: weights,    kind: weights }
  - { id: pnl,        kind: pnl }

sources:                           # where data comes from
  - id: prices
    to: [raw.daily_prices]
    connector: rest
    options:
      url: https://api.example.com/v1/bars
      headers: { Authorization: "Bearer ${PRICE_API_KEY}" }

steps:                             # each one reads tables and writes tables
  - id: momentum
    from: [normalized.prices]
    to:   [features.momentum]
    script: steps/momentum.py
    options: { lookback: 20 }

  - id: alpha_momentum             # the step that writes weights is the strategy
    from: [features.momentum, features.risk]
    to:   [weights.momentum]
    script: steps/alpha_momentum.py
    universe: sp500
    rebalance: 20d

backtest:                          # what prices the portfolio, and what it costs
  prices: normalized.prices
  fee_bps: 5
  slippage_bps: 10

A step is a .sql file, or a .py file with a run(ctx) function:

def run(ctx):
    bars = ctx.read("normalized.prices")      # only tables the step declared in `from`
    held = ctx.universe()                     # the symbols it may hold
    return df

ctx.read() refuses any table the step did not list, so a missing dependency is an error instead of a wrong number.

The five stages

Each stage holds tables, and data only moves forward through them.

stage

what it holds

raw

data exactly as it arrived. Never edited

normalized

typed, deduplicated, one key set

features

anything you measure or calculate

weights

one table per strategy. What to hold, and how much

pnl

what each strategy earned. Written by qanat backtest

qanat check enforces the rules that keep this honest, and refuses to run a project that breaks one. They are written out in docs/contract.md.

The commands you need

command

qanat init

create a project

qanat run

run the pipeline once

qanat serve

scheduler and console

qanat backtest

replay over history and price what it held

qanat report

one backtest, period by period

qanat --help lists the rest. qanat tui gives you the console in the terminal if you prefer to stay there.

A source or a step can also run on a clock, or run whenever its input changes. Set schedule: or when: on it, or fill it in from the console.

Backtest

A backtest replays the pipeline over a period that already happened and prices what it held.

qanat backtest --from 2026-01-05 --to 2026-06-01 --rebalance 10d
    gross            +25.150%
    fees              -0.636%
    slippage          -1.272%
    ------------------------------
    net              +23.242%

    per period        +1.660%  over 14 periods
    hit rate           64.3%

Before each pass, every table is filtered down to the rows that existed at that moment. A step reads the past without knowing it is being replayed, so a step that forgot to filter still cannot see the future.

Net is the headline, not gross. Fees and slippage are charged on turnover, which is how much had to be traded to reach the new portfolio. Trading more often can turn a winning strategy into a losing one. Same strategy, same window, only the rebalance changed:

  --rebalance 10d     turnover   10.8     net  +23.2%
  --rebalance 1d      turnover  128.5     net  -27.1%

--decay N works from the other side. It holds a blend of the last N portfolios, so the strategy stops paying fees for noise:

                    turnover      net
  --decay off          45.00    -5.05%
  --decay 4            25.32    -2.88%

In sample and out of sample are reported separately. --split <date> cuts the run in two. The lookback, the rebalance and the decay were all chosen by someone who could see the first half, so that number partly measures the choosing:

    in sample        -22.055%  19 periods,  -1.161% each
    out of sample     -2.843%  14 periods,  -0.203% each
    split at 2026-03-01 — out of sample is the line to believe

Several strategies can be priced together as one book with --alpha alpha_momentum,alpha_low_vol. Each keeps its own weights table, and the result lands in one PnL table.

More in docs/backtest.md.

Strategies on the shelf

Four plain strategies ship with Qanat, so you have something real to replay on day one.

qanat alphas                                        # what is on the shelf
qanat alphas momentum --reads normalized.prices     # wire one up
qanat run && qanat backtest --from … --to …

momentum

rank by trailing return, hold the top names. Long only

reversal

the same over days rather than months, buying what just fell

low_vol

hold the quietest names, sized inversely to their own volatility

neutral_momentum

momentum with the average taken out. Long and short, equal sides

Each one writes an ordinary step file into steps/. Edit it, or throw it away and write your own. The tool is what is given away here. The strategy never is.

Data sources

connector

what it is

rest

an HTTP endpoint returning JSON. ${ENV_VAR} is expanded, so keys never enter the file

sql

any database SQLAlchemy can reach. pip install "qanat-fdtl[sql]"

csv

a local path or a URL

synthetic

a deterministic fake market, so a new project runs before any API key exists

The store is one local database: a DuckDB file by default, or Postgres on localhost (qanat init --postgres, or docker compose).

Four examples ship with it:

examples/equity

synthetic prices. Runs with no network and no keys

examples/fx-bundled

real data, in the repo. 27 years of ECB rates, 126 KB, no key needed

examples/fx-real

the same project fetching the same rates over HTTP

examples/scheduled-ingest

a source on a clock, fetching while you watch the console

Status

Beta, and the first packaged release. It does what this page says on my own work, but nobody else has run it yet. If something breaks or looks wrong, open an issue or say so on Discord.

Working end to end: the pipeline and its rules, the DuckDB and Postgres store, the console, cron scheduling, Docker, the point-in-time replay engine with its net-edge report, and the MCP server.

Not implemented: backfills, incremental windows, and live trading. Qanat produces a portfolio. It does not place an order.

One limit worth knowing before you trust a number. There is no benchmark. Nothing separates your edge from the market's own move, so a long-only strategy in a rising market looks good and the report cannot tell you why.

Qanat runs one kind of pipeline, the kind that ends in a portfolio. Airflow, Dagster and Prefect handle arbitrary DAGs and distributed execution. Reach for those when you need them.

Issues and pull requests are welcome. See CONTRIBUTING.md.

License

MIT License. See LICENSE.

Copyright (c) 2026 fidetolabs

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables users to create and revise strategy graphs, modules, and custom nodes; run exact-revision backtests; and maintain project knowledge and artifact documentation.
    MIT