Skip to content

Instantly share code, notes, and snippets.

@jonasvanderhaegen
Last active July 6, 2026 19:21
Show Gist options
  • Select an option

  • Save jonasvanderhaegen/7488b1af51a91160c2f7da6e4ac27a87 to your computer and use it in GitHub Desktop.

Select an option

Save jonasvanderhaegen/7488b1af51a91160c2f7da6e4ac27a87 to your computer and use it in GitHub Desktop.
nautilus-tastytrade — strategy engine vision (AI builder + deterministic compute-DAG)

Board alignment addendum (2026-07-06)

This file records how the vision in strategy-vision.md maps onto the live GitHub board after the 2026-07-05 program restructure (epic #37), and what changed on 2026-07-06. The vision doc itself is unchanged; treat this as its tracking companion.

Status since the vision doc was written

  • Live foundation VALIDATED during market hours (2026-07-06): quotes, trades, 1m bars flowing and persisted with real timestamps (#23 market-hours pass; fixes #75, #77, #79).
  • Recorder professionalization program filed AND half-executed the same day (epic #80): internal bars merged (#64, closes #49), per-symbol quote/Greeks sampling + candle upsert + TimescaleDB compression merged (#88). Remaining lanes: policy doc #81, sampling cutovers, dynamic scanner universe #83, live Greeks #86.
  • DAG engine foundation merged: schema + canonical definition_hash (E-01), static validator (E-02 groundwork), op registry + indicator allowlist (E-03), versioned storage with migration 0007 (E-04). The interpreter MVP is #52.

Vision -> board map

Vision section Board home
Two-layer split (builder vs deterministic engine) Epic #37 north star; docs/nautilus/program-north-star.md
Runtime graph as one nautilus Strategy #52 interpreter MVP (Phase E, epic #41)
Node library: TA indicators #50 native indicators (Phase D)
Node library: candlestick/chart patterns existing code (indicators/patterns.py, patterns/detector.py); wrapped during #52/#50
Node library: sentiment (index/sector/peer) #96 (new, 2026-07-06): derived from recorded proxy-symbol bars, no external feed in v1
Node library: news buzzword scoring #97 (new, 2026-07-06): offline AI scorer writes plain rows; runtime gate node is deterministic
Sizing / entry / exit nodes #52 vocabulary + #15 (brackets/trailing, shipped, closed)
Live == backtest parity + catalog replay Phase C (#39: #47 backfill, #48 evaluation harness)
Evolution loop + PR promotion #98 (new, 2026-07-06): nightly skyway workflow, operator-gated promotion; criteria in #54
Sub-millisecond design goal performance gate added to #52 acceptance (comment, 2026-07-06)
skyway = builder only, never runtime boundary restated in #98; unchanged

One policy interaction to know about

The replay promise ("a session traded live can be replayed bar-for-bar") holds for bar- and trade-driven nodes: those streams are recorded complete. Since 2026-07-06 (#88), QUOTES and GREEKS are sampled per symbol (default 1000ms) for volume control. A future DAG node consuming raw quote ticks requires flipping quote_sample_interval_ms for the affected symbols before its data is recorded, or accepting approximate replay. The recording-policy doc (#81) carries this as a revisit trigger.

Corrected tracking (the footer in strategy-vision.md predates the restructure)

  • #18 (strategy-DAG spike): CLOSED, absorbed into epic #37 / Phase E (#41: #51 #52 #53).
  • #21 (native indicators backlog): CLOSED, re-scoped into Phase D (#40: #49 shipped via PR #64, #50 open); portfolio_stats MCP tool shipped.
  • #19 (warm-up): CLOSED, shipped.
  • #15 (brackets/trailing via OrderEmulator): CLOSED, shipped.
  • #23 (live-run status): market-hours validation passed 2026-07-06.
  • Current top-level tracking: epic #37 (record -> backtest -> paper -> live), recording epic #80, DAG phase epic #41.

Node library status (2026-07-06)

Companion to strategy-vision.md §3: where each node category stands in code and on the board.

Node category Code today Board item Runnable via DAG engine?
TA indicators (vwap, rsi, macd, atr, delta, rel-strength, gap, premarket, prior-day) src/strategy/indicators/ (hand-rolled, tested) #50 swaps to nautilus natives; wrapped as ops via #52 after #52
Candlestick patterns src/strategy/indicators/patterns.py wrap during #52 after #52
Chart patterns (flags, breakouts, VWAP reclaim, volume spike) src/strategy/patterns/detector.py wrap during #52; expansion backlog after #52
Sentiment: index / sector / peer derivable from recorded ETF bars (no code yet) #96 after #52 + #96
News buzzword/impact none #97 (offline scorer + gate node) after #52 + #97
Sizing / risk src/strategy/momentum config + RiskEngine caps vocabulary in #52 after #52
Entry / exit subgraphs (pullback, breakout, trailing, targets) brackets/trailing shipped (#15, OrderEmulator) intent-node vocabulary in #52 after #52
Option nodes (chain slices, Greeks) OptionsChainLoader + live Greeks capture landing via #86; historical options via DoltHub (#73) later phase, after #86 later

Gate order (from epic #37): #52 interpreter MVP with its performance gate is the choke point; every node category above waits on it, by design. The op registry and allowlist (E-03) plus the versioned spec store with definition_hash (E-04) are already merged, so adding a node category is registry + pure function + tests, not engine work.

Vision — A dynamic, AI-composed trading strategy engine

Status: the live trading node (NautilusTrader + Tastytrade + dxFeed) now connects, reconciles, and runs in sandbox. This doc is the forward architecture for how strategies themselves should work. Feedback very welcome — see the linked GitHub issues at the end.

TL;DR

  1. A strategy becomes DATA — a declarative graph spec, not hardcoded Python.
  2. An AI "builder" composes & evolves strategies at design/research time (creative, allowed to be non-deterministic).
  3. A deterministic engine runs them on live realtime data and in backtest — identically, with no AI in the decision path.
  4. The graph is assembled from a reusable library of nodes (indicators, candlestick/chart patterns, sentiment, news, sizing, entry/exit).

The whole point: instead of hand-coding N strategies, we build a node library once and let the builder compose a wide space of strategies — each a fixed, testable, reproducible artifact.


1. The two layers (the core idea)

The apparent contradiction — "as dynamic as possible" and "no AI in the strategy" and "must backtest identically" — dissolves once you split build time from run time.

flowchart LR
  subgraph BUILD["BUILD TIME -- AI builder (creative, non-deterministic, offline)"]
    direction TB
    perf["Past performance / regime / market structure"] --> builder
    lib["Node library"] --> builder
    builder["AI builder<br/>(skyway .sky nightly workflow)<br/>'understands how to build a strategy'"]
    builder --> spec[["Strategy graph spec<br/>(DATA, not code)"]]
  end
  subgraph RUN["RUN TIME -- Deterministic engine (NO AI)"]
    direction TB
    spec --> engine["Compute-DAG runtime<br/>(a nautilus Strategy)"]
    engine --> intents["Order intents"]
  end
  intents --> results["Fills / PnL"]
  results -. "fitness signal" .-> builder
Loading
  • Build time is where creativity lives: an AI reads the node library + recent performance and authors or edits a strategy graph. Non-determinism is fine here — it produces an artifact, then stops.
  • Run time is pure and deterministic: the engine loads the graph (data) and executes it. No LLM, no wall-clock, no un-seeded randomness — so the same event stream always yields the same decisions.

Key enabler — strategy-as-data: because a strategy is a graph definition, an AI can author/evolve it and an engine can run it deterministically. That single choice unlocks everything else.


2. The runtime strategy graph (streaming dataflow)

At run time the strategy is a stateful streaming operator graph: realtime data arrives on the message bus, propagates incrementally through nodes, and the terminal nodes emit order intents. It is reactive, not batch — a node fires when its inputs tick.

flowchart LR
  subgraph DATA["Realtime data on the bus (multi-rate)"]
    ticks["Quote / Trade ticks (fast)"]
    bars["1m / 5m bars"]
    greeks["Option greeks"]
    sent["Sentiment: index / sector / peer (slow)"]
    news["News buzzwords (slow)"]
  end
  ticks --> ind["Indicator nodes<br/>vwap, rsi, macd, atr, delta, rel-strength"]
  bars --> ind
  bars --> pat["Pattern nodes<br/>candlestick + chart patterns"]
  greeks --> ind
  ind --> sig["Signal node<br/>multi-timeframe alignment + score"]
  pat --> sig
  sig --> filt["Filter / gate nodes"]
  sent --> filt
  news --> filt
  filt --> size["Sizing / risk node"]
  size --> entry["Entry subgraph -> entry intent"]
  size --> exitn["Exit / position-mgmt subgraph -> exit intent"]
  entry --> out(["Order intents"])
  exitn --> out
Loading

Properties that matter:

  • Stateful nodes (rolling indicator windows) ride nautilus's existing per-event callbacks.
  • Multi-rate: slow sentiment/news inputs hold their last value between updates while fast nodes tick every bar.
  • Target: sub-millisecond per event (precompiled topological order; no per-event allocation) — a design goal to validate, not a measured number.

3. The node library + strategy archetypes

Every input dimension is a reusable typed node. Different strategy archetypes are just different graphs composed from the same shared library.

Node category Examples Status
TA indicators vwap, rsi, macd, atr, delta, relative-strength, gap, premarket, prior-day mostly exist — wrap as nodes
Candlestick patterns hammer, engulfing, doji, morning/evening star, shooting star exists (indicators/patterns.py) — wrap
Chart patterns bull/bear flag, consolidation breakout, VWAP reclaim, volume spike, higher-low/lower-high exists (patterns/detector.py) — wrap; expand (triangles, wedges, H&S)
Sentiment index (SPY/QQQ), sector (XLK/XLF/...), peer / other impactful companies new ingestion + nodes
News buzzword/impact score from articles new (AI scores it offline; runtime sees plain data)
Sizing / risk position sizing, exposure caps exists
Entry / exit pullback/breakout confirmation, trailing/target/regime-flip exits new subgraphs
flowchart TB
  lib["Shared node library"]
  lib --> dt["Daytrading graph<br/>(intraday, tight risk, flat by close)"]
  lib --> sw["Swing graph<br/>(multi-day, regime + sentiment weighted)"]
  lib --> lg["Long / position graph<br/>(slow, macro sentiment)"]
  lib --> ee["Entry/exit-optimized graph<br/>(specialized entry + exit subgraphs)"]
Loading

4. One strategy, two contexts: live == backtest

A nautilus Strategy runs unchanged in both the live TradingNode and the BacktestEngine — both drive it through the same on_bar / on_tick handlers. The strategy is agnostic to where data comes from.

flowchart TB
  subgraph LIVE["Live"]
    feed["DxFeed / Tastytrade feed"] --> nodeL["nautilus TradingNode"]
  end
  subgraph BACKTEST["Backtest"]
    cat["Parquet catalog replay (history)"] --> nodeB["nautilus BacktestEngine"]
  end
  nodeL --> strat
  nodeB --> strat
  strat["THE SAME strategy graph<br/>(same handlers, deterministic nodes)"]
  strat --> same["Identical outputs for an identical event sequence"]
Loading

Because live ticks/bars are also persisted to the catalog, a session traded live can be replayed bar-for-bar through the very same graph — same-day validation and full reproducibility.


5. What does a strategy output?

An order intent — not a percentage. Three things kept distinct:

Thing What it is Who consumes it
Runtime output (per event) enter/exit/size/stop intent, or no-op nautilus exec engine
Win-rate / Sharpe / valid-invalid evaluation metrics, computed after trades resolve the AI builder (fitness signal)
Order placement / closing submit/cancel/OCO/trailing/reconcile nautilus (deterministic)

You do not need a separate DAG for placing/closing orders. Order execution is already nautilus's deterministic job (ExecEngine, RiskEngine pre-trade caps, OrderEmulator for brackets/trailing, reconciliation). The strategy graph's leaf nodes are the entry/exit intent nodes; nautilus executes those intents.


6. The evolution loop

flowchart LR
  builder["AI builder"] --> spec["Strategy graph spec"]
  spec --> rt["Deterministic runtime"]
  rt --> ord["Order intents"]
  ord --> exec["nautilus exec / risk / emulator"]
  exec --> fills["Fills / PnL"]
  fills --> an["Analyzer: win-rate / Sharpe / valid-invalid"]
  an -->|fitness| builder
  spec -. "backtest on history" .-> bt["BacktestEngine"]
  bt -->|score| builder
Loading

Nightly (skyway .sky workflow): pull yesterday's fills → analyze regime/PnL → propose a new/edited graph → backtest it → if it beats the incumbent, open a PR to promote it. The candidate then runs deterministically on live data, and its results feed back to the builder.


7. Why not just run strategies on skyway directly?

skyway's .sky DAG is an LLM-agent orchestration graph — each node shells out to an AI model (seconds-to-minutes, non-deterministic, costs money, not reproducible). That's perfect for the builder (offline, creative) and a category error for a per-bar decision on a real-money account.

So we borrow skyway's grammar idea (nodes + dependencies + guards) for the runtime graph, and use skyway's actual runtime one layer up — to evolve strategies, not to run them.

Build-time builder (skyway) Run-time engine (this)
Driver AI / LLM deterministic code
Latency minutes sub-millisecond
Reproducible no (creative) yes (required)
In the order path never yes

8. Where we are today

The live foundation works (sandbox, dry-run): the node connects DxFeed + Tastytrade, reconciles the account, starts the strategy, and subscribes quotes/trades/1m-bars/greeks. The current momentum/swing strategies are already nautilus Strategy subclasses (they run in both engines) — the spike just makes the implicit pipeline an explicit, declarative graph while keeping that property.

First step of the spike (not a big-bang migration): define the node interface + graph-spec format, then a 2-3 node prototype wired declaratively that runs in both a backtest and a synthetic live feed with identical outputs.


Tracking

  • Strategy-DAG design: GitHub issue #18
  • Related strategy lanes: #19 (warm-up), #21 (native indicators / portfolio stats), #15 (brackets/trailing)
  • Live-run status: #23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment