Skip to main content

PyFortis

Config-driven risk control plane for trading systems: pre-trade limits, circuit breakers, risk metrics — deterministic, explainable, fail-closed. Define the rules in YAML. Get a verdict, and the exact repair that would make it pass.

Python 3.11+ License: MIT Ruff


60 seconds to a verdict

pip install pyfortis
pyfortis init          # writes policies/risk_policy.yaml + a golden PolicyTest

1. The policy init wrote (policies/risk_policy.yaml):

api_version: pyfortis.io/v1
kind: RiskPolicy
metadata: { name: starter, namespace: examples/minimal, version: "0.1.0" }
spec:
  mode: shadow                       # shadow | enforce — REQUIRED, no default
  base_currency: USD
  limits:
    - { name: max_order_notional, type: order_notional, scope: order, params: { max: 50000 }, severity: CRITICAL }
    - { name: fat_finger, type: price_collar, scope: order, params: { max_deviation_pct: 0.05, reference: last }, severity: CRITICAL }
  circuit_breakers:
    - name: daily_loss
      type: daily_pnl
      scope: portfolio
      params: { max_loss_pct_nav: 0.02 }
      trip: { action: halt_trading }
      rearm: manual
      rearm_roles: [risk_manager]

2. Run this Python:

from datetime import UTC, datetime
from decimal import Decimal

from pyfortis import Account, Order, RiskContext, RiskEngine, Side

engine = RiskEngine.from_yaml("policies/risk_policy.yaml")
context = RiskContext(
    as_of=datetime.now(UTC),
    account=Account(nav=Decimal("10000000")),
    prices={"AAPL": Decimal("190.00")},
)
order = Order(order_id="o-1", symbol="AAPL", side=Side.BUY, quantity=Decimal("400"))

result = engine.evaluate(order, context)
print(result.verdict)                 # APPROVED_WITH_WARNING — shadow lets it through
print(result.shadow_verdict)          # REJECTED    — what enforce would have said
print(result.headroom)                # 263         — the quantity that would pass
print(result.failed_checks[0].fix)    # reduce quantity to <= 263

The starter policy ships in mode: shadow, so the breach is recorded rather than blocked. Flip spec.mode to enforce and the same order comes back REJECTED.

That's it. No server, no database, no migrations, no broker connection.

3. Prove it before you trust it:

pyfortis validate policies/risk_policy.yaml                # would it enforce what it says?
pyfortis lint     policies/risk_policy.yaml                # is it wise?
pyfortis test     policies/tests/*.policy_test.yaml        # does it decide what you think?

validate imports the built-in registries, merges extends, and holds every limit to its check type's params model — so a misspelt type or param is a load-time error naming what it could have meant, not a rejection at 09:31.


What it does

  • Pre-trade gating — price collars, notional and quantity caps, position and concentration limits, buying power, restricted lists, session windows, message rates, duplicate and self-trade prevention, short-sale locates, actor budgets, leverage.
  • Post-trade assessment — exposure, liquidity, VaR-family metric limits, stress losses, and the same limit vocabulary against the actual book.
  • Circuit breakers — stateful, scoped, with cooldown and role-gated re-arm: daily P&L, drawdown, volatility, reject storms, stale data, execution throttles, breach counts, consecutive losses.
  • Governance — versioned policy manifests, extends composition, packaged regulatory baselines, expiring approved overrides, and PolicyTest files that run in CI.
  • Agent safety — actor profiles that scale limits down, cap daily budgets, route to a human above a threshold, and treat a warning as a stop.

What it does not do

PyFortis is a control plane, not a trading system. It deliberately does not:

  • place, amend, or cancel orders — it returns a verdict; your execution layer acts on it;
  • hold the book — positions, prices, and P&L are passed in as a RiskContext snapshot;
  • generate signals or size positions — that is an optimiser's job;
  • put a model in the decision path — every verdict is arithmetic over declared limits. Nothing in the gate is learned, sampled, or prompted.

Grow as you need

Each rung is optional. Take the lowest one that answers your question.

Rung 1 — Library                 pip install pyfortis                      ✅ release 1
  RiskEngine.evaluate() · headroom() · assess() · pure, stateless, no infra

Rung 2 — Monitor                 pip install pyfortis                      ✅ release 1
  RiskMonitor · in-memory book, activity windows, live breaker state, hooks

Rung 3 — Orchestrator            pip install pyfortis                      ✅ release 1
  RiskOrchestrator over RiskStore protocols · in-memory store
  Persistent SQLite / Postgres / Mongo stores + decision log                ◻ release 2

Rung 4 — Service                 (the [api] extra lands with it)           ◻ release 2
  FastAPI gate service on :8008 · pyfortis.cfg
  MCP server · events and sinks · worker                                    ◻ release 3
  Next.js UI on :3008                                                       ◻ release 4

Release 1 is the whole core: the engine, every check, calculator and breaker, the config layer, the monitor, the orchestrator with the in-memory store, the CLI, and the PolicyTest runner. The examples/ ladder walks the same rungs.

Concepts

Concept What it is
Policy A versioned RiskPolicy manifest: limits, metrics, breakers, actors, escalation. The unit of review and deployment.
Limit One named rule of a declared type, with params, a scope, a selector, and a severity.
Stage When the limit runs: pre_trade (hypothetical post-fill book), post_trade (actual book), or both.
Breaker Stateful kill-switch: trips, applies a trip.action, waits a cooldown, re-arms automatically or by a role-holding human.
Escalation Severity → actions (log, notify, block_order, require_approval, cancel_open_orders, flatten_positions, halt_trading).
Actor profile A posture for a human, agent, or system caller: limit_scale, require_approval_above, budgets, deny_on.
Headroom The largest additional quantity that would still pass — a rejection you can act on.
Shadow vs enforce shadow evaluates and records but never blocks (shadow_verdict holds what enforce would have said). enforce blocks.
Override An approved, scoped, expiring replacement for one limit's params, with approved_by, reason, and a ticket.

Configuration-Driven Engineering

The rules are data, not code:

- name: max_position_size
  type: position
  stage: both                 # gate the order AND audit the book
  scope: instrument
  params:
    max_long: 10000
    max_short: 5000
    params_by: { key: symbol, values: { AAPL: { max_long: 15000 } } }
  thresholds: { warn_at: 0.8 }
  severity: CRITICAL
  tags: [reg:rts6-art17]

A risk manager can read it. A reviewer can diff it. CI can test it. And policies reference checks, calculators, breakers, and handlers by name — never by import path — so a policy file can never make PyFortis execute arbitrary code. Names bind through a registry or an operator-owned Catalog.

Compose instead of copying:

spec:
  mode: enforce
  extends:
    - pyfortis://contrib/equity/us_cash_baseline@1.0.0   # regulatory baseline pack
    - pyfortis://contrib/agents/ramp_up@1.0.0            # agent actor profiles

CLI

Command Purpose
pyfortis init Scaffold a policy and a golden PolicyTest.
pyfortis validate <paths> Envelope, schema, refs — and, under strict_mode, check types and their params.
pyfortis lint <paths> Gaps, dead limits, risky defaults. --fail-on warning|error.
pyfortis diff <a> <b> What changed — and --fail-on-loosening when it widens.
pyfortis test <paths> Run PolicyTest cases (build the engine too). -k to filter.
pyfortis schema <kind> JSON Schema for a manifest kind.
pyfortis capabilities Registered checks, calculators, breakers, handlers.
pyfortis gate --policy P --order-json J Evaluate one order; exit 0 only when the verdict passes.
pyfortis headroom --policy P --symbol S --side buy How much is allowed?
pyfortis assess --policy P --context-file F Post-trade report for a book.
pyfortis migrate <paths> Rewrite a legacy config as a manifest.
pyfortis version The installed version.

Exit codes: 0 success · 1 your policy or your run needs fixing (that includes a rejected gate, a breached assess and an unreadable file) · 2 argparse rejected your command line. Every read command takes --json, and every path argument is a file — use a shell glob for "everything here".

Documentation

Full docs: https://optophi.github.io/pyfortis/

Status and roadmap

Release 1, "core truth", is the stateless core and the config layer. It is the entire list under rungs 1–3 above. What is not in release 1:

Release Adds
2 — persistence and service SQLite/Postgres/Mongo stores, breaker-state and override persistence, decision log, FastAPI gate service (:8008), pyfortis.cfg.
3 — integration MCP server, events and sinks, worker, and the sibling seams (pyoptima, pyactuator, pystator, pygubernator, pycustodian).
4 — surface Next.js UI (:3008): policy editor, decision explorer, breaker board, headroom inspector.

Card-by-card detail: BACKLOG.md.

Development

git clone https://github.com/optophi/pyfortis && cd pyfortis
uv pip install -e ".[dev]"     # or: pip install -e ".[dev]" — extras: metrics, docs, ci, all, dev
pytest
./scripts/ci.sh                # lint + types + tests + docs, exactly as CI runs them

License

MIT — see LICENSE.

Links

Metadata

Release files for pyfortis 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyfortis 0.0.2
File Size Uploaded Provenance Actions
pyfortis-0.0.2.tar.gz 636.2 kB With provenance Details for pyfortis-0.0.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pyfortis 0.0.2
File Interpreter ABI Platform Actions
pyfortis-0.0.2-py3-none-any.whl Python 3 none any Details for pyfortis-0.0.2-py3-none-any.whl

Total release size: 951.8 kB

Release files / pyfortis-0.0.2.tar.gz

Download URL pyfortis-0.0.2.tar.gz
Size 636.2 kB
Tags Source
SHA-256 checksum
How to use checksums
87e57599b28e9de2fa67eb363c52f11aae3c248cab76ec94e226f7ce8077e79b
BLAKE2b-256 checksum
How to use checksums
20fbc0fc2023333c3de2aaf6a8847beb7ff47812fdc22860f66be967c14a8282
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / pyfortis-0.0.2-py3-none-any.whl

Download URL pyfortis-0.0.2-py3-none-any.whl
Size 315.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f5a67527be2e1c02751ec0f5dbd144b98bd82925d71d4f7cf8737a9171a2c07a
BLAKE2b-256 checksum
How to use checksums
7e254953a927be949db9c5bfd49a3553df538d1480d930fc3b2a07a8447ad2a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Security for pyfortis 0.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pyfortis-0.0.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page