Skip to content

makedirectory/tradeflow

Repository files navigation

TradeFlow

CI Docs

A small, layered, broker-agnostic algorithmic-trading research engine — and an honest one, which mostly means it's very good at telling you your brilliant strategy is actually noise. It scans markets, tests strategies, validates them out-of-sample, allocates portfolios, and exposes the whole research surface to AI agents without ever handing them order-execution authority. It ships with an Alpaca adapter, but everything above the broker layer is vendor-neutral. It scans a universe of symbols, runs a strategy over them, and either backtests on history or trades live (paper by default) — with optional parameter optimization, walk-forward validation, and constraint-solver portfolio allocation.

Full docs — usage guide and engineering wiki — live at tradeflow.mk-dir.com.

Making money in markets is genuinely hard. This project won't change that — but it will at least stop you from fooling yourself quite so quickly, which is most of the battle.

Designed to be easy to try and easy to read:

  • No TA-Lib / no native build step — indicators are pure pandas/numpy, so uv sync is all you need and the Docker image carries no compiler toolchain.
  • Broker-agnostic — everything is written against a Broker / MarketDataProvider interface. Alpaca is the first implementation; dropping in another venue means writing one adapter, nothing else.
  • Strict separation of concerns — each layer does one job (see below).

⚠️ Educational software. Trading is risky; use paper trading. No warranty.

TradeFlow demo — an in-sample equity curve that looks tradeable, beside the walk-forward verdict that refuses to promote it

make demo runs the whole pipeline on synthetic data (no keys, no network) and renders this: a strategy that looks profitable in-sample, and the out-of-sample verdict that calls it noise. The refusal is the product. The same panels render for any run — pass --chart PATH to backtest or walkforward.

The mental model: two clocks

The one idea that explains everything else — TradeFlow runs on two clocks that never touch:

  • Research clock (offline, slow, exploratory): backtest → optimize → walk-forward, plus the optional AI agent. Non-determinism and LLMs are allowed here. It only ever proposes — writing provenance-stamped configs to disk.
  • Trade clock (live, deterministic, LLM-free): live bar → signal → order. No model sits in the order path, so there's nothing to prompt-inject and nothing non-deterministic to debug when real money is at stake.

Promotion is a manual human step — automation never flips PAPER_TRADE or places an order. The MCP server enforces this structurally: it builds only a data client, so it physically cannot trade. See the architecture docs for the full picture.

Requirements

You need either of these — not both:

  • uv — the Python package manager used to run everything locally, or
  • Docker — to build and run the app in a container (no local Python or uv needed).

The Makefile targets run through uv (e.g. make backtestuv run …), and there are separate Docker targets (make docker-build, make docker-run). Pick whichever you prefer.

Either way you'll need free Alpaca paper-trading API keys from the Alpaca dashboardPaper Account → API Keys.

Quickstart (uv)

# 1. Install uv:  https://docs.astral.sh/uv/
# 2. Install dependencies
make install                          # uv sync

# 3. See it work — no keys, no network
make demo                             # full pipeline on synthetic data + verdict

# 4. Point it at real data: add your free Alpaca paper keys
cp .env.example .env                  # then edit .env

# 5. Try it (preconfigured combos)
make scan                             # which symbols are flagged right now?
make backtest                         # scan -> volume_spike strategy -> report
make live                             # paper-trade the scanned universe

Quickstart (Docker)

No local Python or uv required — just Docker:

cp .env.example .env                  # add your Alpaca paper keys
make docker-build                     # build the image (uv runs inside it)
make docker-run                       # paper live-trading; mounts your .env

# or run any command in the container directly:
docker run --rm -v $(pwd)/.env:/app/.env tradeflow \
    uv run python main.py backtest --symbols NVDA,META --start 2024-01-02 --end 2024-04-01

Run make help to see every target. Anything is overridable inline:

make backtest SYMBOLS=AAPL,MSFT,NVDA START=2024-06-01 END=2024-09-01 CAPITAL=50000

Or call the CLI directly:

uv run python main.py backtest --strategy volume_spike --scanner volume \
    --symbols NVDA,META,TSLA --start 2024-01-02 --end 2024-04-01

See it run

make demo runs the entire pipeline on a seeded random walk — no keys, no network — and ends in a promotion verdict. The point isn't a winning strategy; it's that the refusal to promote a noise strategy is the product:

$ make demo

======================================================================
  TradeFlow demo — synthetic data, no API keys, no network
  (a seeded random walk: realistic-looking, no actual edge)
======================================================================

1) In-sample backtest of every registered strategy
   In-sample, almost anything looks tradeable. That's the trap.

   STRATEGY              RETURN   SHARPE  TRADES
   -------------------------------------------
   volume_spike           0.00%     0.00       0
   ma_crossover          16.80%     0.48      52
   mean_reversion       -15.77%    -0.52      52

2) Walk-forward validation of 'ma_crossover' (the honest scorecard)
   Optimize in-sample, score out-of-sample across folds, then gate it.

   ... (per-fold parameter search elided) ...
   Best sharpe_ratio = 1.1499 with {fast_ema_period: 13, slow_ema_period: 60, ...}

   OOS Sharpe (median): -0.42   efficiency (OOS/IS): -0.37   OOS trades: 29

   Promotion gates:
     [FAIL] oos_sharpe:              -0.42  (threshold 1.0)
     [FAIL] oos_profit_factor:        0.71  (threshold 1.3)
     [FAIL] walk_forward_efficiency: -0.37  (threshold 0.4)
     [FAIL] oos_drawdown_vs_is:      23.57  (threshold 15.43)
     [FAIL] min_oos_trades:          29     (threshold 100)
     [FAIL] deflated_sharpe:          0.00  (threshold 0.5)

   Verdict: NOT promotable

   No edge in a random walk -> the gates refuse to promote it. That refusal
   is the product. Point TradeFlow at real data with `make backtest`.

Notice the arc: ma_crossover looks great in-sample (+16.8%, Sharpe 0.48), but once it's optimized in-sample and scored out-of-sample the edge evaporates (median OOS Sharpe −0.42) and every promotion gate fails. That's walk-forward validation doing its job.

What it does

Command What happens
demo Run the whole pipeline on synthetic data — no keys, no network — ending in an honest promotion verdict
scan Run the universe scanner and print flagged symbols
backtest Scan → run a strategy over history → performance report
live Scan → warm up indicators → stream bars → place paper/live orders
optimize Search strategy parameters by backtest objective (grid / random / Bayesian)
allocate Weight a portfolio: scalar-score sizing (OR-Tools), or --objective utility for mean-variance construction from alpha + Σ
alphas Rank a universe by continuous alpha — a comparable, annualized residual-return forecast per name; --combine blends several signals, --neutralize-factors regresses out the risk model's factor exposures (read-only)
risk Estimate the universe covariance Σ (Ledoit–Wolf shrinkage) and summarize its risk structure (read-only)
info Information report: measure IC, breadth, and predicted-vs-realized IR — skill vs luck (read-only)
horizon Measure alpha decay / half-life; recommend rebalance cadence + current/lagged blend (read-only)
walkforward Out-of-sample validation: optimize in-sample, score out-of-sample across folds, with a sacred holdout and promotion gates
mcp Serve TradeFlow over MCP so an agent (Claude Code / Desktop) can drive scan/backtest/optimize/walk-forward/alphas/risk/portfolio/info — read-only, no live trading

Three strategies ship today — pick one with --strategy. Each defines a single continuous score (its conviction); the trade clock's BUY/SELL/HOLD and the continuous alpha are both derived from it — one source of truth.

  • volume_spike — long/short EMA-trend strength scaled by volume confirmation (intraday, 5-minute bars).
  • ma_crossover — long-only EMA trend follower: the normalized fast−slow gap, whose sign crossings are the golden / death cross (daily).
  • mean_reversion — long-only RSI mean reversion: score is oversold-ness, enter the dip and exit on the rebound (daily).

Adding a fourth is a one-file change — see Extending.

Optional features

Capabilities are optional extras so the base install stays lean:

make install-optimize     # scikit-learn, for `optimize --method bayesian`
make install-portfolio    # Google OR-Tools, for `allocate`
uv sync --extra mcp       # the MCP SDK, for `python main.py mcp`

Project status

This is an evolving research project, not a production trading platform. To keep that honest, here's what's load-bearing versus what's still maturing:

Capability Status
Broker / market-data abstractions ✅ Stable
Offline backtesting + analytics ✅ Stable
Pure pandas/numpy indicators ✅ Stable
Universe scanning ✅ Stable
Offline test suite (in-memory fakes) ✅ Stable
Parameter optimization — grid / random ✅ Stable
Walk-forward validation + promotion gates 🧪 Experimental
Parameter optimization — Bayesian 🧪 Experimental
Portfolio allocation (OR-Tools) 🧪 Experimental
Live paper trading 🧪 Experimental
MCP server 🧪 Experimental
Research agent 🧪 Experimental

"Experimental" means the interfaces and gate thresholds may still change — not that the code is untested. Everything ships with offline tests.

Agent integration (MCP)

AI-assisted research without AI-controlled trading.

python main.py mcp exposes TradeFlow's deterministic capabilities to any MCP client (Claude Code / Claude Desktop) as tools: discovery, run_scan, run_backtest, run_optimization, run_walk_forward, get_metrics_glossary, summarize_bars, and save_config/load_config/list_configs. Every call is logged to logs/mcp_audit.jsonl for replay.

The safety model is structural. The server constructs only a data client — no trading client, no broker — so it is incapable of placing an order. There is no place_order, start_live, cancel, or set_paper_trade tool; promoting a config to live is a manual human step outside MCP. The capability simply isn't wired in, so it can't be prompt-injected around. The agent works on the research clock (offline, exploratory); the live order path stays LLM-free.

Register it with a client (Claude Desktop / Claude Code mcpServers):

{ "mcpServers": { "tradeflow": {
    "command": "uv",
    "args": ["run", "--extra", "mcp", "python", "main.py", "mcp"],
    "cwd": "/path/to/tradeflow" } } }

Research agent (optional)

python main.py research runs a bounded, offline loop that proposes hypotheses, validates them out-of-sample with walk-forward, and writes a shortlist of provenance-stamped candidate configs to configs/ for a human to review. It never promotes anything to live trading.

The proposer is provider-agnostic — choose with --provider:

Provider Install Default model Credential
anthropic (default) uv sync --extra ai claude-opus-4-8 ANTHROPIC_API_KEY
openai uv sync --extra openai gpt-4o OPENAI_API_KEY
ollama (local) none llama3.1 none

Set the credential in .env (alongside your Alpaca keys — see .env.example) or as the standard environment variable. Ollama runs locally and needs no key.

uv run python main.py research --provider ollama --model llama3.1 \
  --symbols NVDA,AAPL --start 2024-01-01 --end 2025-12-31 \
  --goal "improve OOS Sharpe without raising max drawdown" --holdout-days 60

See the docs (Usage → AI agents) for the full tool surface, guardrails, and provider setup.

Architecture

The codebase is organized into single-responsibility layers. Nothing above the broker layer imports a vendor SDK. The two clocks never touch — automation only ever proposes a config; a human promotes it:

flowchart LR
    subgraph research["Research clock — offline · LLM-allowed"]
        direction TB
        H[hypothesis] --> B[backtest] --> O[optimize] --> W[walk-forward]
        W --> C[(provenance-stamped<br/>config)]
    end
    subgraph trade["Trade clock — live · deterministic · LLM-free"]
        direction TB
        Bar[live bar] --> Sig[signal] --> Ord[broker order]
    end
    C -. "human promotes<br/>(nothing auto-flips)" .-> Bar
Loading

The layers themselves:

brokers/        Broker interface + domain types  ── alpaca/ (AlpacaBroker, AlpacaMarketData)
marketdata/     MarketDataProvider interface, Timeframe, MarketDataClient
indicators/     Pure pandas/numpy technical indicators
strategies/     Strategy base + signals + volume_spike (signals, sizing, risk)
scanners/       ScannerStrategy base + volume scanner + SymbolScanner (universe)
execution/      LiveTrader (signals -> broker orders)
analytics/      Performance metrics + reporting
engine/         BacktestEngine + LiveEngine (orchestration only)
optimization/   ParameterSpace + ParameterOptimizer (tune params via backtest)
portfolio/      PortfolioAllocator (OR-Tools MIP position weighting)
utils/          logging, numeric, time helpers

Data flows the same way in both modes:

marketdata → strategy.process_data → strategy.generate_signals
           → engine (simulate fills | route to execution) → analytics

To work on them locally:

make docs        # serve the Docusaurus site at http://localhost:3000

Docker

make docker-build
make docker-run            # paper live-trading; mounts your .env

Tests

make test                  # offline suite — no API keys or network required

The whole stack is testable offline because every layer depends on the broker/data abstractions; tests inject in-memory fakes.

Lint and format with ruff (what CI runs):

uv run ruff check .          # lint
uv run ruff format --check . # format

Contributing

Contributions and ideas are very welcome — whether it's a bug fix, a new strategy or scanner, an additional broker/data adapter, an LLM provider, docs, or just a feature request or suggestion. If you have an idea, open an issue to start the conversation; if you have a fix, open a PR. No contribution is too small, and feedback on what would make TradeFlow more useful is always appreciated.

And hey — don't be greedy: share your algos, let's make money together. 📈 (Worst case, we lose money together, which is basically friendship.)

See CONTRIBUTING.md for setup, the pre-push checks, and the coding standards (layering rules, separation of concerns, no vendor SDK above the broker layer). CI runs ruff lint + format + the test suite on every PR.

Account utilities

make cancel-orders         # cancel all open orders
make close-positions       # liquidate all positions (and cancel orders)

☕ Coffee?

If this code somehow makes you money, I'd genuinely love to hear about it. If it loses you money — we've never met, and this is the first you're hearing of it.

Either way, if it saved you some time, you can buy me a coffee:

buy me a coffee →

About

Layered, broker-agnostic algorithmic trading engine (Alpaca adapter included): scanner → strategy → backtest/live, with parameter optimization and OR-Tools portfolio allocation. Educational.

Topics

Resources

License

Contributing

Stars

6 stars

Watchers

1 watching

Forks

Contributors

Languages