[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-96142":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":14,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":29,"readmeContent":30,"aiSummary":31,"trendingCount":14,"starSnapshotCount":14,"syncStatus":32,"lastSyncTime":33,"discoverSource":34},96142,"qanat","fidetolabs\u002Fqanat","fidetolabs","Agent-native workflow engine for building and backtesting alphas as DAGs.",null,"Python",125,26,3,0,11,45.39,"MIT License",false,"main",true,[22,23,24,25,26,27,28],"alpha-research","backtesting","dag","duckdb","mcp","quantitative-finance","trading-toolkit","2026-09-20 04:01:32","\u003Cp align=\"center\">\n  \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002Ffidetolabs\u002Fqanat\u002Fmain\u002Fassets\u002Fhero.jpg\" alt=\"A qanat cut open: shaft mouths and fields on the surface, the tunnel running beneath them, and a man walking it\" width=\"560\">\n\u003C\u002Fp>\n\n\u003Ch1 align=\"center\">Qanat\u003C\u002Fh1>\n\n\u003Cp align=\"center\">\n  \u003Ci>Declare the alpha as a DAG. Hand the backtest to an agent.\u003C\u002Fi>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002FLICENSE\">\u003Cimg alt=\"License\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-a2e65d?style=flat-square\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002Fpyproject.toml\">\u003Cimg alt=\"Python\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fpython-3.10%2B-a2e65d?style=flat-square\">\u003C\u002Fa>\n  \u003Ca href=\"#status\">\u003Cimg alt=\"Status\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fstatus-beta-e8c069?style=flat-square\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002FJUmwATScS8\">\u003Cimg alt=\"Discord members\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fdynamic\u002Fjson?logo=discord&logoColor=white&label=Discord&query=%24.approximate_member_count&url=https%3A%2F%2Fdiscord.com%2Fapi%2Fv10%2Finvites%2FJUmwATScS8%3Fwith_counts%3Dtrue&color=5865F2&style=flat-square\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Ffidetolabs\u002F\">\u003Cimg alt=\"Instagram followers\" src=\"https:\u002F\u002Fpulse.walls.sh\u002Fbadge?url=https:\u002F\u002Fwww.instagram.com\u002Ffidetolabs\u002F&label=Instagram&color=E4405F\">\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"#quick-start\">Quick start\u003C\u002Fa> ·\n  \u003Ca href=\"#give-it-to-an-agent\">Agents\u003C\u002Fa> ·\n  \u003Ca href=\"#how-it-works\">How it works\u003C\u002Fa> ·\n  \u003Ca href=\"#console\">Console\u003C\u002Fa> ·\n  \u003Ca href=\"#five-stages-named-for-what-they-hold\">The stage contract\u003C\u002Fa> ·\n  \u003Ca href=\"#what-a-project-looks-like\">A project file\u003C\u002Fa> ·\n  \u003Ca href=\"#backward-and-forward\">Backward and forward\u003C\u002Fa> ·\n  \u003Ca href=\"#backtest\">Backtest\u003C\u002Fa> ·\n  \u003Ca href=\"#connectors\">Connectors\u003C\u002Fa> ·\n  \u003Ca href=\"#status\">Status\u003C\u002Fa> ·\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002FCONTRIBUTING.md\">Contributing\u003C\u002Fa> ·\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002Fdocs\u002Fwords.md\">Words\u003C\u002Fa>\n\u003C\u002Fp>\n\nQanat is an **agent-native workflow engine for building and backtesting alphas as DAGs**.\n\n**An alpha is the step that writes a weights table**: a target weight per symbol, no budget.\nBehind it sits a DAG, the features it reads and the tables those came from, back to the source.\nYou point sources at staged tables and write each step as `.sql` or `.py`. Qanat resolves the\ngraph, runs the steps in order, and serves a console where you watch the tables fill.\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002Ffidetolabs\u002Fqanat\u002Fmain\u002Fassets\u002Fconsole.gif\" width=\"720\"\n       alt=\"The Qanat console. Five columns left to right: raw sources, normalized prices, three feature tables, four weights tables (one per alpha), and four PnL tables. Arrows between them are named for the step that does the work. The strategy book on the left lists each alphaset with its net and a sparkline.\">\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Csub>Four alphas in one project. Each writes its own \u003Ccode>weights\u003C\u002Fcode> table, and each has a\n  \u003Ccode>pnl\u003C\u002Fcode> table beside it holding what it earned. \u003Ccode>low_vol + momentum\u003C\u002Fcode> is two\n  of them priced as one book, which is why two arrows run into its result.\n  \u003Ccode>qanat init --demo\u003C\u002Fcode> builds a project like this one.\u003C\u002Fsub>\n\u003C\u002Fp>\n\nThe store is one local database: a **DuckDB file** by default, or **Postgres on localhost**\n(`qanat init --postgres`, or `docker compose`). Qanat makes no network calls on its own; the only\noutbound requests are the ones your sources make. One real dataset ships with it, 27 years of ECB\nreference rates in `examples\u002Ffx-bundled`, and every other source is one you configure.\n\n> **First packaged release, and a public beta.** It does what this page says on my own work, but\n> nobody else has run it yet. If something breaks or looks wrong, open an\n> [issue](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fissues) or say so on [Discord](https:\u002F\u002Fdiscord.gg\u002FJUmwATScS8).\n\n## Quick start\n\nTwo ways in. Both end at the same console on **http:\u002F\u002F127.0.0.1:8420**.\n\n**Install it.** Needs Python 3.10+:\n\n```bash\nuv tool install qanat-fdtl                 # or: pip install qanat-fdtl\nqanat init my-alpha --demo && cd my-alpha\nqanat serve\n```\n\n**Or run it in Docker.** Needs nothing but Docker, and brings its own Postgres:\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat.git && cd qanat\ndocker compose up --build\n```\n\n`--demo` wires the four shelf alphas, runs the pipeline and prices each one, so the console opens\non a book with numbers in it. About ten seconds. Leave it off for an empty starter project, then\n`qanat check` to hold it against the stage contract and `qanat run` for one pass over the graph.\n\nEither way the data is synthetic, so the first run succeeds with **no API key and no network**.\nWhen you are ready, point `sources:` at a real feed. Nothing else has to change — that is\n`examples\u002Ffx-bundled` and `examples\u002Ffx-real`, the same project reading a file and fetching over\nHTTP.\n\n\u003Cdetails>\n\u003Csummary>Other ways to run it, and what to do when Docker will not start\u003C\u002Fsummary>\n\nThe package is `qanat-fdtl` on PyPI; the command it installs is `qanat`. No uv yet?\n`curl -LsSf https:\u002F\u002Fastral.sh\u002Fuv\u002Finstall.sh | sh`. From a clone, write `uv run qanat …` instead of\n`qanat …` — see [CONTRIBUTING.md](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002FCONTRIBUTING.md).\n\n| | |\n| --- | --- |\n| **Postgres, no Docker** | `qanat init my-alpha --postgres` — a real server on `localhost:5432` instead of the file |\n| **Console only** | `qanat serve --no-schedule` — read the tables and run things by hand, nothing on a clock |\n| **One pass, no server** | `qanat run` — runs the graph once and exits. This is what goes in your own cron, or in CI |\n\nIn Docker, Postgres is on **localhost:5433**, not 5432, because 5432 is often taken already. User,\npassword and database are all `qanat`. To use your own project instead of the demo, bind a\ndirectory to `\u002Fproject`.\n\nWipe Postgres and the project, start fresh:\n\n```bash\ndocker compose down -v && docker compose up --build\n```\n\nKeep the Postgres data, reset only the demo project:\n\n```bash\nQANAT_RESET=1 docker compose up --build\n```\n\nPort already in use? Set the host ports yourself:\n\n```bash\nPOSTGRES_HOST_PORT=5434 QANAT_HOST_PORT=8421 docker compose up --build\n```\n\u003C\u002Fdetails>\n\n## Give it to an agent\n\nMost people will drive this through an agent rather than the CLI. Add it to any MCP client:\n\n```json\n{ \"mcpServers\": { \"qanat\": { \"command\": \"qanat\", \"args\": [\"mcp\"], \"cwd\": \"\u002Fpath\u002Fto\u002Fmy-alpha\" } } }\n```\n\nClaude Code: `claude mcp add qanat -- qanat mcp`. Add `--read-only` and it keeps the 20 tools that\nread and drops the 7 that write.\n\nThe console and the MCP server are two thin adapters over one service layer, and no logic lives\nabove them. That is what agent-native means here, and it is testable: an agent can do everything\nthe console can, and the two cannot disagree about the state of a project.\n\n| | 27 tools |\n| --- | --- |\n| **Discover** | `list_tables`, `describe_table`, `sample_table` (honours `as_of`), `lineage`, `list_steps`, `read_step` |\n| **Validate** | `check`, `plan`, `stale_tables` |\n| **Choose** | `list_alphas`, `read_alpha`, `use_alpha`, `alpha_book`, `backtest_conditions` |\n| **Run** | `run`, `backtest`, `list_backtests`, `report`, `period`, `weights`, `compare`, `list_runs` |\n| **Author** | `save_step`, `remove_step`, `save_source` |\n| **Watch** | `open_console`, `console_status` |\n\n### What that looks like\n\n**\"What is in this project, and what feeds the momentum alpha?\"**\nThe agent calls `list_tables`, then `lineage`, and gets the answer as data rather than prose:\n\n```json\n{ \"ref\": \"weights.momentum\",\n  \"upstream\": [ { \"from\": \"normalized.prices\", \"step\": \"alpha_momentum\" } ],\n  \"breaks_portfolio\": false }\n```\n\n**\"Add a momentum alpha and backtest it.\"**\n`list_alphas` → `use_alpha` → `run` → `backtest_conditions` → `backtest`. The fourth call is the\none that matters. Rather than guessing a window, it comes back with what the project can actually\nanswer, and a list of what to ask you first:\n\n```json\n{ \"ask_the_person_for\": [\"alpha\", \"from\", \"to\", \"universe\", \"rebalance\", \"decay\", \"split\"],\n  \"window\": { \"data_available\": { \"earliest\": \"2025-07-13\", \"latest\": \"2026-09-05\" } },\n  \"rebalance\": { \"default\": \"5d\", \"note\": \"shorter means more decisions and more turnover\" },\n  \"why\": \"each one changes the number. A backtest run on conditions nobody chose is a guess with\n          a decimal point on it.\" }\n```\n\n**\"Which of my alphas actually works?\"**\n`alpha_book` returns every alphaset with what it earned, so the agent compares rather than\nspeculates. `report` and `compare` open any two of them side by side.\n\n**\"Why did it lose money in March?\"**\n`report` gives the periods, `period` opens one of them in full — what was held, what each name\nreturned, what was traded to get there — and `weights` shows the portfolio it decided on.\n\n**\"Watch me work.\"** `open_console` serves the console from the same session, so you can see the\ngraph light up while the agent runs. One process, one store, nothing to keep in sync.\n\nNotes on the tool surface: **[docs\u002Fagents.md](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002Fdocs\u002Fagents.md)**.\n\n## How it works\n\nEverything is declared in one `qanat.yaml`: `stages`, `sources`, `steps`, schedules, `universes`.\nNothing hides in application code. Thirteen commands act on that file:\n\n| command | |\n| --- | --- |\n| `qanat init` | scaffold a project |\n| `qanat check` | hold the pipeline against the stage contract |\n| `qanat plan` | what would change if this file were applied. Configuration, not row values |\n| `qanat prune` | drop tables nothing produces any more |\n| `qanat ls` | stages, tables, jobs |\n| `qanat run` | one pass over the whole graph, or one job |\n| `qanat serve` | scheduler and console |\n| `qanat backtest` | replay the pipeline and price what it held |\n| `qanat backtests` | every replay this project has run |\n| `qanat report` | one backtest, period by period |\n| `qanat compare` | what moved between two backtests |\n| `qanat alphas` | the alphas that ship with qanat, and how to wire one |\n| `qanat mcp` | serve this project to an agent over MCP (stdio) |\n\nA step is a `.sql` file, wrapped into `CREATE OR REPLACE TABLE`, or a `.py` file with `run(ctx)`\nreturning a DataFrame. `ctx.read()` refuses any table the step did not list in `from:`, so a missing\ndependency is an error instead of a stale number.\n\nState is recorded when a job succeeds, not when the file loads. \"Changed\" in `qanat plan` means this\njob differs from the last time it worked, and a failed run is never the baseline for a later diff.\n\n```\nsources ─→ raw ─→ normalized ─→ features ─→ weights ─→ pnl\n   │         │         │            │          │        │\n rest      never     typed,      a chain    one table  what it\n sql       edited    deduped     of steps   per alpha  earned,\n csv                                       (no budget) per rebalance\n```\n\n## Console\n\n`qanat serve` starts the scheduler and a web console. Every box in it is a table and every arrow is\nthe step that makes it, laid out left to right by stage.\n\nYou configure the whole pipeline from the console — sources, steps, alphas, stages, retention — and\neverything you change is written to `qanat.yaml` on disk. There is no settings panel and no edit\nmode. See **[docs\u002Fconsole.md](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002Fdocs\u002Fconsole.md)**.\n\n## Five stages, named for what they hold\n\nA pipeline is **table — (step) — table — … — table**. The tables are what you have; the steps are\nthe named work between them.\n\n- **`raw`** — landed exactly as it arrived, **never edited**. No step may write into it.\n- **`normalized`** — typed, deduplicated, conformed to one key set.\n- **`features`** — a *chain*: a feature step may read another feature step.\n- **`weights`** — **one table per alpha.** A target weight per symbol, no budget. The step that\n  writes one of these tables is the alpha. What it reads is its lineage.\n- **`pnl`** — optional, and last. **What each alpha earned, per rebalance.** Nothing writes it by\n  hand: `qanat backtest` does, once it knows what the portfolio was worth.\n\nNo alpha may read another alpha's weights, so no edge is ever counted twice.\n\n`qanat check` enforces six rules, and refuses to serve a project that breaks one. They are\nwritten out with the reasoning in **[docs\u002Fcontract.md](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002Fdocs\u002Fcontract.md)**.\n\n## What a project looks like\n\n```yaml\nproject: equity\nstore: .\u002Fdata\u002Fqanat.duckdb\n\nuniverses:                         # which symbols a portfolio may hold\n  - id: sp500\n    index: S&P 500\n    symbols: .\u002Funiverses\u002Fsp500.csv   # symbol,name,sector,from,to\n\nstages:                          # order here is order in the pipeline\n  - { id: raw,        kind: raw }\n  - { id: normalized, kind: features }\n  - { id: features,   kind: features }\n  - { id: weights,    kind: weights }\n  - { id: pnl,        kind: pnl }    # optional, last; written by `qanat backtest`\n\nsources:                         # many sources may feed one stage\n  - id: prices\n    to: [raw.daily_prices]\n    connector: rest\n    schedule: \"*\u002F5 * * * *\"\n    options:\n      url: https:\u002F\u002Fapi.example.com\u002Fv1\u002Fbars\n      headers: { Authorization: \"Bearer ${PRICE_API_KEY}\" }\n      records: data.items\n\nsteps:                           # n:m -- `from` and `to` are both lists\n  - id: momentum\n    from:   [normalized.prices]\n    to:     [features.momentum]\n    script: steps\u002Fmomentum.py\n    when:   [normalized.prices]    # or `schedule:` for a clock, or neither\n    options: { lookback: 20 }\n\n  - id: alpha_momentum             # an alpha: the step that writes a weights table\n    from:   [features.momentum, features.risk]\n    to:     [weights.momentum]\n    script: steps\u002Falpha_momentum.py\n    universe: sp500\n    rebalance: 20d                 # how this alpha wants to be run; a backtest\n    decay: 4                       # uses these unless you say otherwise\n\nretention:                         # a source on a clock fills its table forever.\n  raw.daily_prices: 7d             # delete rows older than this; `24h` and `2w` work too\n  normalized.prices: 30d\n\nbacktest:                          # what prices the portfolio, and what it costs\n  prices: normalized.prices        # the rest of the keys are under Backtest, below\n  fee_bps: 5\n  slippage_bps: 10\n```\n\nRetention runs every minute. It finds the date in whichever of `ts`, `timestamp`, `time`, `date`,\n`as_of`, `datetime`, `created_at` or `updated_at` the table has.\n\nA `.py` step implements `run(ctx)`:\n\n```python\ndef run(ctx):\n    bars = ctx.read(\"normalized.prices\")      # only what the step declared in `from`\n    held = ctx.universe()                     # the symbols it may hold\n    ctx.log(\"scoring\")\n    return df                                 # or {\"table\": df, ...} for n:m\n```\n\nEach term has one meaning and one spelling. See **[docs\u002Fwords.md](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002Fdocs\u002Fwords.md)**.\n\n## Backward and forward\n\nThe same graph runs two ways.\n\n**Backward** — one pass over a period that already happened. `qanat backtest` replays the pipeline\nonce per as-of date and prices what it held. You run it, you read the number, it is done. See\n[Backtest](#backtest).\n\n**Forward** — the graph keeps up as new data arrives. Some feeds only tell you today's number, so\nthe only way to have last year's is to have saved it.\n\nA **job** is anything that runs: a source or a step. A job goes forward in one of two ways.\n\n**On a clock.** Give the job a cron line:\n\n```yaml\n- id: prices              # a source: poll the feed every five minutes\n  schedule: \"*\u002F5 * * * *\"\n- id: momentum            # a step: recompute the feature after it lands\n  schedule: \"*\u002F15 * * * *\"      # cron is UTC\n```\n\nSet it here, or in the console: click a source or a step and fill in `fetch again`. The graph then\nreads `on *\u002F5 * * * *` instead of `only when you ask`. A job that is still running is never started\ntwice.\n\n**When its input changes.** A clock is a guess about when the data arrives. This is the arrival\nitself:\n\n```yaml\n- id: momentum\n  from: [normalized.prices]\n  when: [normalized.prices]     # run when this gets new rows\n```\n\nEach woken step wakes whatever waits on its own tables, so one source landing rows reaches the far\nend of the graph on its own. A step may set both `schedule:` and `when:`, or neither, in which case\nit runs when you ask. It can only wait on a table it reads.\n\nSources stay on a clock either way. A source waits on something outside qanat, so the only way to\nfind out is to ask.\n\n`sh scripts\u002Fdemo-run.sh` polls a mock feed for two minutes while the console fills.\nSee [`examples\u002Fscheduled-ingest`](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Ftree\u002Fmain\u002Fexamples\u002Fscheduled-ingest).\n\n## Backtest\n\nA backtest is the same run loop with a clock and a bill.\n\n```bash\nqanat backtest --from 2026-01-05 --to 2026-06-01 --rebalance 10d --seed 7\n```\n\n```\n    gross            +25.150%\n    fees              -0.636%\n    slippage          -1.272%\n    ------------------------------\n    net              +23.242%\n\n    per period        +1.660%  over 14 periods\n    hit rate           64.3%\n```\n\n**The clock.** Before each pass, every table is shadowed by a view holding only the rows that\nexisted at that moment. A step reads the past without knowing it is being replayed, so a step that\nforgot to filter still cannot see the future.\n\n**The bill.** The weights table says what to hold. Turnover is how much had to be traded to get\nthere. Fees and slippage are charged on turnover, and what is printed is net.\n\nSet it in `qanat.yaml`. Any of it can be overridden on the command line for one run:\n\n```yaml\nbacktest:\n  prices: normalized.prices     # where a symbol's price per day comes from\n  price_column: close\n  fee_bps: 5                    # charged on turnover\n  slippage_bps: 10              # estimated, charged on turnover too\n  rebalance: 5d                 # gap between as-of dates\n  purge: 0d                     # hold rows back this long before a step may read them\n  embargo: 0d                   # wait this long after as_of before a return counts\n  decay: 0                      # blend the last N portfolios; 0 or 1 is off\n```\n\n**Decay usually changes the verdict.** An alpha is often right about direction and wrong about how\noften. Acting on every twitch pays fees for noise. `--decay N` holds a blend of the last N\nportfolios, newest heaviest:\n\n```\n                    turnover      net\n  --decay off          45.00    -5.05%\n  --decay 4            25.32    -2.88%\n```\n\nRebalancing does the same thing from the other side. Same alpha, same window, only `--rebalance`\nchanged:\n\n```\n  --rebalance 10d     turnover   10.8     net  +23.2%\n  --rebalance 1d      turnover  128.5     net  -27.1%\n```\n\nThat gap is why net is the headline number and gross is not. Raising the fee until the edge dies is\nhow you find out how much of it was real.\n\n**In sample and out of sample are separated.** `--split \u003Cdate>` cuts the run in two and reports\nboth. The lookback, the rebalance and the decay were all chosen by someone who could see the\nin-sample half, so that number is partly a measure of the choosing:\n\n```\n    in sample        -22.055%  19 periods,  -1.161% each\n    out of sample     -2.843%  14 periods,  -0.203% each\n    split at 2026-03-01 — out of sample is the line to believe\n```\n\n**Several alphas can be priced as one book.** That is a different strategy from either alpha alone:\n\n```bash\nqanat backtest --alpha alpha_momentum,alpha_low_vol --allocation momentum=3,low_vol=1 \\\n               --from … --to … --rebalance 5d\n```\n\nEach alpha keeps its own weights table. The run holds the sum, renormalised so the book size stays\n1, and the result lands in one PnL table fed by all of them.\n\n**Same seed, same answer.** `--seed` pins every generator a step might reach for, and the run\nrecords a digest of the project, the window and the seed. Two runs with the same digest that give\ndifferent numbers mean something moved underneath.\n\nMore in **[docs\u002Fbacktest.md](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002Fdocs\u002Fbacktest.md)**: point-in-time universes and survivorship bias,\nhow net compounds, opening a single rebalance, scoring as the run goes, and how an alpha states its\nown rebalance and decay.\n\n## Alphas on the shelf\n\nFour plain alphas ship with Qanat, so the first replay is three commands away:\n\n```bash\nqanat alphas                                        # what is on the shelf\nqanat alphas momentum --reads normalized.prices     # wire one up; add as many as you like\nqanat run && qanat backtest --from … --to …\n```\n\n| | |\n| --- | --- |\n| `momentum` | rank by trailing return, hold the top names. Long only |\n| `reversal` | the same over days rather than months, buying what just fell |\n| `low_vol` | hold the quietest names, sized inversely to their own volatility |\n| `neutral_momentum` | momentum with the average taken out. Long \u002F short, equal sides |\n\nThey are rules you could write on a napkin, and that is the point: something real to replay on\nday one. `qanat alphas \u003Cname>` writes an ordinary step script into `steps\u002F`. Edit it, or throw it\naway and write your own. **The tool is what is given away here. The alpha never is.**\n\n## Connectors\n\n| connector | what it is |\n| --- | --- |\n| `rest` | An HTTP endpoint returning JSON. `${ENV_VAR}` is expanded in url, params, and headers, so keys never enter the file |\n| `sql` | Any database SQLAlchemy can reach. `pip install \"qanat-fdtl[sql]\"` |\n| `csv` | A local path or a URL |\n| `synthetic` | A deterministic fake market, so a new project runs green before any API key exists |\n\nA connector is one function, `fetch(source, root) -> DataFrame`, registered in\n`qanat\u002Fsources\u002F__init__.py`. That is the entire plugin surface.\n\n## Status\n\nBeta. Working end to end: the stage contract, the runner, the DuckDB\u002FPostgres store, the console,\nper-table retention, cron scheduling, Docker, `plan` \u002F `prune`, the\n**point-in-time replay engine** with its net-edge report, and the **MCP server**.\n\nFour examples ship with it:\n\n| | |\n| --- | --- |\n| `examples\u002Fequity` | synthetic prices, runs green with no network and no keys. `cd examples\u002Fequity && qanat run && qanat serve` |\n| `examples\u002Ffx-bundled` | **real data, in the repo**: 27 years of ECB rates, 126 KB, no key and no network |\n| `examples\u002Ffx-real` | the same project fetching the same rates over HTTP instead, from `api.frankfurter.dev` |\n| `examples\u002Fscheduled-ingest` | a source on a cron, fetching over HTTP while you watch the console |\n\nNot implemented: live backtesting (scoring a period as each rebalance date arrives), backfills,\nincremental windows, diffs over the data itself, and a live trading path. Qanat produces a\nportfolio, it does not place an order.\n\nQanat runs one kind of pipeline: an alpha that ends in a portfolio. Airflow, Dagster and Prefect\nhandle arbitrary DAGs, distributed execution and large connector ecosystems. Reach for those when\nyou need them.\n\n**Known limits of the numbers.** These are real, and worth knowing before you trust a figure:\n\n- **There is no benchmark.** Nothing separates edge from beta, so a long-only alpha in a rising\n  market looks good and the report cannot tell you why.\n- **`purge` and `embargo` are borrowed words.** Here they mean \"hold rows back before a step may\n  read them\" and \"wait before a return counts\". Related to purging and embargoing in\n  cross-validation, but not the same thing.\n\nIssues and pull requests are welcome. See **[CONTRIBUTING.md](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002FCONTRIBUTING.md)**. Questions on\n[Discord](https:\u002F\u002Fdiscord.gg\u002FJUmwATScS8).\n\n## License\n\nMIT License. See [LICENSE](https:\u002F\u002Fgithub.com\u002Ffidetolabs\u002Fqanat\u002Fblob\u002Fmain\u002FLICENSE).\n\nCopyright (c) 2026 fidetolabs\n","Qanat 是一个面向量化研究的代理原生工作流引擎，用于以有向无环图（DAG）形式声明、构建和回测交易信号（alpha）。其核心功能包括：将 alpha 定义为输出权重表的 DAG 节点，支持 SQL 和 Python 脚本作为计算步骤，自动解析依赖关系并按序执行；内置轻量级控制台实时可视化数据流与中间表状态；集成 DuckDB 作为默认嵌入式分析引擎，并提供标准化阶段契约与连接器扩展能力。适用于量化研究员快速迭代多 alpha 策略、进行本地化离线回测及特征工程流水线编排等场景。",2,"2026-09-11 02:30:03","CREATED_QUERY"]