Fonflo System Architecture

ES futures trading intelligence system. Auto-generated documentation for continuous improvement.

What's New

August 2026

Bulletproof morning report — if a data provider stalls at 6:00 AM, the build now self-retries every minute until 6:30 (then every 5 min to 7:30) and pulls its live price from the streaming feed instead of the lagging historical API. One honest status note instead of alarm spam; a typed retry in the review channel forces a rebuild.

Conversational draft review — typing edits on the pre-market draft regenerates it (v2, v3…); "send it" publishes immediately; every message gets an answer, never silence. Auto-publish window is ~13 minutes per revision; 👎 holds indefinitely.

Smarter break alerts — Market Pulse cards now fire only when the ladder's outermost published rung (S3/R3) breaks with order-flow confirmation: no cards for THE LINE (it trades through constantly), no stale "just broke" alerts once price has run >12 pts past, one handover per moment (10-min cooldown, duplicate-target dedup).

Honest ladders — when no real structure exists beyond the ladder's edge, the card extends its own grid math instead of dressing a round number as a rung; rungs no scenario names render dimmed.

Clean results — daily and monthly scorecards show gross quarter-point results ("+36", "-6.5", "+1.25") straight from entry/exit fills. No cost-model decimals; every platform's costs differ, many are free. Wins and losses always shown.

Card design — bias reasoning in uniform two-line format with accent markers; on directional-bias days the favored scenario is boxed with a full accent border and fading fill.

June–July 2026

Databento-only data path — Schwab, yfinance and IBKR fully removed from every price/VIX path; live streaming + licensed historical with cost caps; research reads a cleaned continuous series (calendar-spread and roll-gap artifacts filtered).

morning_report split (5,033 → 4,117 lines + two extracted modules) and contract auto-roll to ESU6 (September 2026) via contract_manager.

Cost-adjusted internal ledgers — every fired signal graded net-of-cost internally; per-signal auto-disable when 30-day profit factor stays below 1.05.

Supervised scheduler — auto-restart on failure, draft state survives restarts, watchdog heartbeats.

System Overview

Fonflo is an automated ES (E-mini S&P 500) futures signal generation and monitoring system. It runs 24/7 with market-hours task scheduling, integrates multiple external signal sources, applies institutional-grade confidence scoring, and publishes signals and analytics to Discord.

Tech Stack

ComponentTechnology
LanguagePython 3.14
Market Data (only source)Databento — live streaming feed (trades/BBO/bars) + licensed historical, local parquet. Schwab/yfinance/IBKR fully removed 2026-06.
Live price handoffStreaming feed persists its price every 60s (data/feed_price.json) so out-of-process builds always have a live quote
VIXFront VX future via Databento (no third-party caches)
LLMClaude Opus (morning narrative + verification auditor), Claude Haiku (high-volume classification)
CommunicationDiscord webhooks + bot token
IntelligenceExternal-intel layer currently disabled (operator switch, 2026-08)
GraphicsPlaywright HTML-to-PNG rendering
HostingCloudflare Pages (web), local Windows machine (runtime)
StateJSON files in data/ directory (no database)

Task Schedule (PST)

TimeTaskComponent
6:00 AMLevel calc, inventory, prev-day classifylevel_engine, etc.
6:00 AMMorning report draft (isolated subprocess; auto-retries every 60s until 6:30 on data failure, then 5-min until 7:30)morning_report.generate_draft()
6:25 AMOpening auction classifieropening_auction
6:00-2:00 PMMarket-hours components activeregime, orderflow, price_alerts, etc.
1:55 PMRTH session snapshotscheduler.snapshot_rth_session()
2:00 PMEOD report gradingreport_grader
2:05 PMEOD summary PNGscheduler.send_eod_summary()
2:15 PMProduction audit + missed opp analysisproduction_tracker, missed_opp
2:30 PMLab site deploylab_publisher
Always-onDiscord/Substack/Email/X/News monitorsdiscord_monitor, email_monitor, etc.

Component Groups

Pricing

Everything the system publishes — the 6:00 AM pre-market report, live signals with entry/stop/target and threaded result cards, Market Pulse level-break alerts, and the daily + monthly scorecards (every win and every loss, in clean gross points) — is delivered in one private Discord.

Fonflo Pro — $50/month

7-day free trial. Full access from day one: pre-market report, live signals, break alerts, scorecards, risk warnings. Cancel anytime during the trial and pay nothing.

Start 7-day free trial

Not financial advice. Trade at your own risk. Futures trading involves substantial risk of loss.

Signal Pipeline

Every signal flows through a strict pipeline: Detection → Dedup → Scoring → Validation → Discord → Outcome Tracking.

1Detection — price_alerts or orderflow_engine detects pattern at key level
2Signal Logger — dedup check (4pt price, 15min time, 2 max same-direction)
3Alert Engine — confidence scoring (session + regime + confluence + risk penalties)
4Validation Gate — max 20pt stop, 30pt target, 1.2-3.0 R:R, stale entry check
5PNG Rendering — fonflo_graphics generates signal card (540x540px)
6Discord Post — bot token POST to #signals channel with signal card
7Outcome Tracking — monitor stop/target hit, post result PNG as reply

Signal Detection

price_alerts.py (802 lines)

Primary signal engine. Monitors ES price against pivot/R1/R2/S1/S2 levels. Fires on rejection/bounce patterns.

  • Bearish: R1/R2 rejection — price touched resistance (within 8pts), pulled back 15pts, trend down
  • Bullish: S1/S2/Pivot bounce — price touched support (within 8pts), bounced 15pts, trend up
  • Breakout: R1 break with momentum + intel confirmation
  • Breakdown: S1 break with momentum + intel confirmation
orderflow_engine.py (989 lines)

Databento tick + BBO stream. Generates orderflow-based signals (CVD divergence, absorption, delta thrust).

  • CVD Divergence: Price makes new high/low but cumulative delta diverges
  • Absorption: Large unfilled prints on breakout, absorbed quickly
  • Large Print Bounce: Single mega-print fails to push price
  • Delta Imbalance: Consistent positive/negative delta bars

Confidence Scoring

Base confidence from signal engine (6 default)

Session adjustment:
  PREMARKET (6-9:30 AM):    base - 1
  OPEN (9:30-11 AM):        base - 2  (choppy)
  MIDMORNING (11-12):       base + 0
  LUNCH (12-1 PM):          base + 1
  AFTERNOON (1-3 PM):       base - 1
  POWERHOUR (3-4 PM):       base - 2  (wild)

Regime adjustment:
  TRENDING:    +0
  RANGING:     +2  (mean reversion setups)
  VOLATILE:    +3  (tight stops)

Confluence bonuses:
  + intel source match:     +2
  + level confluence:       +1
  + internals (tick/trin):  +1
  + correlation (NQ/CL):   +1
  + aplus filter:           +1

Risk penalties:
  - Event lockout:          -2
  - VIX extreme:            -1
  - Counter-trend regime:   -4
  - Inventory extreme:      -2

Final: clipped to 4-9 range
Gate: Only post if confidence >= 8

Deduplication Rules

Price threshold: 4 points — signals within 4pts of an active signal are suppressed

Time window: 15 minutes — same-direction signals within 15min are suppressed

Max same-direction: 2 active signals in same direction at once

Min price move: 15 points between consecutive signals

Flip cooldown: 15 minutes after direction change

Signal 4+: Escalating confidence — must meet Edge Detection (85%+) threshold

Edge Detection

Formula: 60% * (sources_aligned / 2) + 40% * (confluence / 5)
Threshold: >= 85% triggers "Fonflo Edge Detection" badge

Sources: intel_1 alignment, intel_2 alignment
Confluence: VIX, major level, price action, time-of-day, volume

Signal 1-3: No Edge Detection required
Signal 4+:  Must pass Edge Detection or suppressed

Morning Report

Generates a pre-market narrative report as PNG for Discord at 6:00 AM PST sharp (split 2026-07-28 into three modules; build runs in an isolated, killable subprocess with its own retry loop).

Generation Flow

1Fetch RTH data — prior session H/L/C from Databento (snapshot first, self-healing parquet backfill)
2Fetch context — 5-day prices, overnight range, VIX trend, economic calendar, news
3Context build — VIX trend, overnight session, regime, day-type (external intel layer disabled 2026-08-13; report is source-agnostic)
4Calculate levels — standard pivots + gap detection (if current price >100pts from pivot, use 24hr H/L/C)
5Claude narrative — Opus generates 2-3 paragraphs + BULLISH/BEARISH scenarios (audited by an equal-tier verifier)
6Verify — verify_daily_alpha.py audits prices, tone, levels, VIX, events
7Generate PNG — Playwright renders HTML template to PNG
8Post draft — to #premarket-report-draft, spawn monitoring thread

Verification Checks

Draft Approval Workflow

Draft posted → reviewed every 60s (reactions + typed text)

Thumbs up or typing send it/publish/approve: publish instantly to #premarket-report

Typed edits: regenerate and repost as v2/v3… (each revision restarts the window); a no-change regen answers honestly instead of "failed"

No response after ~13 min: auto-publish

Thumbs down: hold indefinitely until a thumbs up

Data failure: never posts on a failed price/VIX pull — build auto-retries every 60s until 6:30 (then 5-min until 7:30); typing retry forces a rebuild

Duplicate guard: cross-process publish stamps prevent double posting

Levels Engine

level_engine.py — Databento-only data sourcing. The published card names its rungs relative to THE LINE (the day's decision level); when no real structure exists beyond the ladder's edge, the card extends its own grid math — never a round number dressed as a rung.

Prior RTH Data Chain

  1. RTH Snapshot (data/rth_session.json) — captured at 1:55 PM PST. Must match yesterday's date or skipped.
  2. Databento historical — prior-day RTH OHLCV from local parquet, self-healing backfill on gaps

Level Calculation

Pivot = (H + L + C) / 3
R1 = 2*Pivot - L    R2 = Pivot + (H-L)    R3 = H + 2*(Pivot-L)
S1 = 2*Pivot - H    S2 = Pivot - (H-L)    S3 = L - 2*(H-L)

Separation: min 8pts between levels (or ATR*0.5)
Rounding: nearest 0.25

Gap Detection: if current price >100pts from pivot,
recalculate using 24hr H/L/C (includes overnight)

Regime Detector

ATR_short = ATR(10 bars)    ATR_long = ATR(30 bars)
MA_fast = SMA(20)           MA_slow = SMA(50)

VOLATILE:      ATR_short/ATR_long >= 1.5 and range_ratio > 1.3
TRENDING_UP:   close > MA_fast > MA_slow and close > VWAP and 3 higher closes
TRENDING_DOWN: close < MA_fast < MA_slow and close < VWAP and 3 lower closes
RANGING:       ATR_ratio <= 0.85
DEFAULT:       VWAP bias + close direction

Intelligence Sources

Three proprietary intelligence feeds. Never mentioned by name in public channels.

Layer disabled 2026-08-13 (operator switch DISABLE_SMART_MONEY): the monitors below are not started and every confluence/strength input from this layer is zeroed — signals, levels and the morning report run on price + order-flow only. Re-enabling is one switch + restart.

Source 1 — Discord Monitor

discord_monitor.py (1273 lines)

Monitors external Discord channel. Classifies messages: SIGNAL, REPOST, DUPLICATE, COMMENTARY, IMAGE_ONLY. Extracts prices/levels/bias via regex + Claude. Tracks accuracy. Posts to internal channel. Applies +2 confidence bonus to matching signals (decays after 120min).

State: adam_log.json, adam_accuracy.json, adam_posts_cache.json

Source 2 — Substack RSS

substack_monitor.py (520 lines)

Polls Substack RSS every 15min (market hours) / 60min (off). HTML → text → Claude Haiku → structured output. Extracts bias, levels, summary. Finds confluence with level_engine (±5pts).

State: mancini_log.json, mancini_cache.json

Source 3 — Email (Gmail IMAP)

email_monitor.py (657 lines)

Monitors Gmail for daily plans from external source. Skips free-version stubs. Extracts HTML → text → Claude. Parses bias, scenarios, levels, macro context. Finds confluence with level_engine.

State: tictoc_log.json, email_cache.json

Quant Lab

Autonomous AI-powered strategy discovery engine running 24/7.

Strategy Discovery Pipeline

1Claude Haiku generates hypothesis — novel Python detection code targeting microstructure patterns
2Walk-forward backtest — train months 1-4, test months 5-6 (strict OOS). 1-min bars. Entry at t+1 bar open.
3Monte Carlo control — 200 random entry iterations. Must beat random by 2x (Grade A) or 1x (Grade B).
4VIX regime decomposition — performance across low/normal/elevated vol. Wilson score 95% CI.
5Grade assignment — A/B/F based on PF, WR, OOS, edge, sample size
6A/B strategies promoted — straight to production with 10-trade probation

Grade Criteria

CriteriaGrade AGrade BGrade F
Mean P&L per trade≥10pts≥10pts<10pts (auto-kill)
Full PF>1.5>1.3Below
OOS PF>1.3>1.0Below
OOS Win Rate>50% (Wilson CI)>45%Below
Edge vs Random>2x>1xNo edge
Sample (IS/OOS)≥30/20≥25/15Below

10pt floor rationale: Volatile ES sessions have 50-100pt daily ranges. Sub-10pt edges get eaten by spread, slippage, and intra-bar variance. Strategies must demonstrate they can pull at least 10pts per trade (after costs) to survive. This applies to both in-sample and out-of-sample mean P&L.

Production Tracking

Probation: First 10 trades. PF <1.0 after 5 trades = immediate kill.

Active: Normal monitoring. PF <0.8 = DEGRADED. PF <0.6 after 30 trades = auto-kill.

Dormant: No signal in 15 trading days. Flagged for review.

Killed: Logged with reason. Fed back as negative constraint to AI inventor.

Lab Token Budget

Cap: $20/week. Strategy lab uses Opus 4.6 for premium hypothesis generation.

Throttle: 12 cycles/day max, one every 2 hours. Daily counter resets at midnight ET.

Per-cycle cost (Opus 4.6): ~1,150 input + 2,000 output tokens = $0.17/cycle

Daily lab spend: 12 × $0.17 = $2.04/day

Weekly lab spend: ~$14.28/week (leaves ~$5/week headroom for morning report + verification)

Why Opus over Haiku: 12 high-quality strategies/day beats 8,640 mediocre ones. Sample size is wasted if hypotheses are noise.

Lab Cost Breakdown

ComponentModelVolumeCost/day
Strategy lab (Opus)Opus 4.612 cycles$2.04
Morning report narrativeOpus1 call
Verify deep-checkOpus (auditor ≥ generator)1 call
Intel parsingdisabled 2026-08-130 calls$0
Level engine LLMHaiku 4.51 call$0.005
Total daily~$2.12
Total weekly~$14.84

Rules & Guardrails

Signal Rules

Min target: 10 points. Sub-10pt edges get eaten by spread/slippage in volatile markets.

Max target: 30 points. T1 capped at 30pts from entry.

Max stop: 20 points. No signal with stop wider than 20pts.

R:R range: 1.2 to 3.0. Outside this = suppressed.

Daily cap: 3 signals/day from price_alerts. Goes silent after 3.

First-touch filter: Each level alerts once per session. Re-alert only after price travels 8+pts away.

Stale entry: If price moved >10pts from detection, suppress. 5-10pts → re-price.

Counter-trend penalty: -4 confidence for counter-regime signals.

Min price move: 15pts between consecutive signals.

Flip cooldown: 15 minutes after direction change.

Invalidation: Bypasses all cooldowns and gates. Immediate post.

Morning Report Rules

Voice: Professional trader, dry, confident, honest about uncertainty. Active voice.

Paragraphs: Max 2-3 per section. Max 3 sentences each.

No decimals: All prices are whole numbers (6563 not 6563.25).

No commas: 6563 not 6,563.

No parentheses around prices: "the 6563 pivot" not "the pivot (6563)".

Gap days: If current price >100pts from pivot, reference where price IS, not where it closed.

Scenarios: Technical only — no events, news, or macro. Price levels, volume, structure.

Never say: globex, POC, net short inventory, spike day, settlement, structural deterioration, gamma posture, value area, traders may want to, consider, potential

Public Channel Rules

NEVER mention external trader names, sources, or services.

Replace with: "confluence confirmed", "key pivot", "multiple confluences align"

Level confidence labels:

  • CALCULATED: our methodology only
  • CONFIRMED: our level + one internal source agrees
  • CONFIRMED (double): our level + two sources agree

No confidence scores in public output.

Data Rules

Never use hardcoded or sample data. Every number from live APIs or real logs.

Data source: Databento ONLY — live streaming feed + licensed historical. Never reintroduce a non-Databento price source; level_cache.json is a same-session-only emergency fallback.

Live price handoff: the feed persists its price every 60s (data/feed_price.json) so subprocesses always see a live quote before any historical fallback.

Stale snapshot detection: Check date matches yesterday's trading day before using.

Research reads: only the cleaned continuous series (research_data.load_clean) — raw 1-min parquet is guard-blocked (calendar spreads + roll gaps contaminate it).

Active contract: ESU6 (September 2026, expires 2026-09-19) — ES auto-rolls via contract_manager. Symbol format: single-digit year.

File Map

Core Pipeline

FileLinesPurpose
scheduler.py572Master controller, task scheduling, component lifecycle
morning_report.py4100+Pre-market report generation + Claude narrative (split 2026-07-28 into morning_report_text.py + morning_report_draft.py)
price_alerts.py3800+Primary signal detection (level rejection/bounce/breakdown, ORB failure, midday fade)
orderflow_signals.pyDatabento tick signal detection (CVD divergence, absorption, delta thrust)
alert_engine.py1000Confidence scoring, multi-stage filtering, Discord posting
level_engine.py994S/R level calculation, LLM cross-reference
verify_daily_alpha.py800+Report auditor (price, tone, levels, VIX, events)
fonflo_graphics.py852PNG rendering (signal cards, results, reports, EOD)
signal_logger.py446Signal dedup, Edge Detection, outcome tracking
signal_gateway.py~200Unified dedup across engines (built, not wired in)
realtime_flo.py951Smart setups: IB fade, failed auction, single prints, etc.
databento_feed.py / databento_client.pyLive streaming feed + licensed historical client (the only market-data path)
level_break_monitor.pyMarket Pulse break cards: outermost published rung only, flow-confirmed, staleness veto, cooldown
config.py166Centralized .env config, all thresholds

Intelligence

FileLinesPurpose
discord_monitor.py1273Source 1: Discord signal extraction + accuracy tracking
substack_monitor.py520Source 2: Substack RSS → Claude parsing
email_monitor.py657Source 3: Gmail IMAP → Claude parsing
x_monitor.py~400Source 1 X/Twitter feed via Grok API
news_monitor.py~500Overnight news aggregation via Grok

Market Context

FilePurpose
regime_detector.pyReal-time market regime classification
correlation_filter.pyNQ/CL divergence checks
internals_monitor.pyMarket breadth (TICK, TRIN, ADD)
contract_manager.pyES auto-roll (quarterly)
inventory_analyzer.pyMarket maker positioning
previous_day_classifier.pyPrior session day-type classification
opening_auction.pyOpening type detection (gap, drive, auction)

Quant Lab

FileLinesPurpose
quant/strategy_lab.py1100Autonomous discovery framework (detect → backtest → grade)
quant/strategy_inventor.py550Claude Haiku generates Python detection code
quant/signal_engine.py700Live signal generation from promoted strategies
quant/day_classifier.py4006 day types + real-time prediction at 4 checkpoints
quant/production_tracker.py400Strategy lifecycle: probation → active → degraded → killed
quant/lab_publisher.py2200HTML report generation + Cloudflare deployment

State Files (data/)

FilePurpose
draft_state.jsonCurrent morning report draft + approval status
level_cache.jsonCached level engine output
levels.jsonFull level detail with notes and confluences
rth_session.jsonRTH snapshot (H/L/C) from 1:55 PM capture
session_state.jsonInventory, prev-day, opening auction state
feed_price.jsonLive-price handoff from the streaming feed (60s heartbeat)
published_ladder.jsonThe morning card's final level labels (one vocabulary for all intraday producers)
adam_log.jsonSource 1 parsed signals
mancini_log.jsonSource 2 parsed analyses
tictoc_log.jsonSource 3 parsed plans
news_log.jsonAggregated overnight news
signals_database.jsonAll signals with outcomes
econ_calendar_cache.jsonEconomic events for today

Data Flow Summary

Databento live ──→ orderflow signals ──→ signal_queue ──→ alert_engine ──→ Discord
      │                  │                                     ↑
      │                  └──→ bar_queue ──→ regime_detector ────┘
      │
      ├──→ price_alerts ──────────────────→ alert_engine
      ├──→ feed_price.json (60s heartbeat) ──→ any subprocess needing a live quote
      │
Databento historical ──→ level_engine ──→ levels.json ──→ morning_report
                                              │               │
                              published_ladder.json           ↓
                                              │          PNG → Discord
                          level_break_monitor ┘  (break cards, tape reads)

[external-intel monitors: DISABLED 2026-08-13 — no inputs to any path]

Claude ──→ strategy_inventor ──→ strategy_lab ──→ production_tracker
                                                        │
                                              signal_engine ──→ Discord

Known Issues & Fragile Patterns

State Management
  • Global state via module singletons (regime_detector._regime_state) — no synchronization for async access
  • JSON state files are last-write-wins — no file locking, concurrent writes can corrupt
  • Signal master log can grow unbounded; no archiving or pruning
  • RTH snapshot stale detection added (date check) but rth_session.json has no file locking
API Dependencies
  • Databento historical horizon can lag the live feed by hours during vendor incidents — mitigated by the feed_price.json live handoff + the 60s draft retry loop (2026-08-13)
  • Discord user token auth for intel monitors violates Discord TOS — layer currently disabled; token dead since 2026-08-04
Signal Pipeline
  • signal_gateway.py built but not wired in — dedup is per-engine, not unified
  • Single global _active_signal — only one direction tracked at a time
  • Outcome tracking relies on same module's price feed — if price lags, outcome is wrong
  • Entry re-pricing moves stop but recalculates distance from OLD entry
Morning Report
  • Split 2026-07-28 (was 5,033 lines) into narrative post-processing + draft plumbing modules — draft-review features live in the scheduler tick, not the build subprocess
  • Claude narrative can reference levels not on the card — verifier snaps or drops them (hallucination guard)
  • ES auto-rolls via contract_manager; NQ/CL/ZN remain manual rolls in .env + config.py
Config & Thresholds
  • All algo thresholds are static in config.py — no dynamic adaptation, requires restart to change
  • Market holidays hardcoded through 2027 — needs annual update
  • Session time weights are fixed; no adaptation to observed win rates
  • Signal queues are unbounded — could grow without limit under load

Implementation Logic (Pseudocode)

Actual decision logic extracted from source code. Line numbers reference the Python files.

Signal Detection — price_alerts.py _check_signals()

# FREQUENCY GATES (applied before any detection)
IF abs(current_price - last_signal_price) < 15pts:  SKIP    # MIN_PRICE_MOVE
IF (now - last_signal_time) < 15min AND direction flipped:  SKIP  # FLIP_COOLDOWN
IF len(price_history) < 5:  SKIP   # need min samples for rejection detection

# BEARISH at R1/R2 (resistance rejection)
FOR each level in [R1, R2]:
  recent_high = max(price_history[-5:])
  IF recent_high >= level - 8pts          # touched resistance (PROXIMITY_PTS=8)
  AND current_price < level - 15pts       # pulled back (REJECTION_PTS=15)
  AND trend == DOWN:                      # confirmed downtrend

    # Intel filter: skip if any source is explicitly BULLISH
    # (external-intel bias gates removed — Adam-only 2026-06-06, layer disabled 2026-08-13)

    # Confluence check: count aligned factors
    # (intel-source factors zeroed since 2026-08-13 — price + vol only)
    confluence = 0
    IF vix > 25:                   confluence += 1
    IF price_action_bearish:       confluence += 1
    IF confluence == 0:  SKIP

    entry = int(current_price)
    stop  = min(level + 8, entry + 20)    # MAX_STOP_PTS = 20
    target = next_lower_level(pivot, S1)

    # R:R validation
    risk = abs(entry - stop)
    reward = abs(entry - target)
    IF reward > 30:  target = entry - 30   # MAX_TARGET_PTS = 30
    rr = reward / risk
    IF rr < 1.2 OR rr > 3.0:  SKIP        # MIN/MAX_RR_RATIO

    FIRE BEARISH signal

# BULLISH at S1/S2/Pivot (support bounce) — mirror logic
FOR each level in [S1, S2, Pivot]:
  recent_low = min(price_history[-5:])
  IF recent_low <= level + 8pts AND current_price > level + 15pts AND trend == UP:
    [same intel/confluence checks, reversed direction]
    entry = int(current_price)
    stop  = max(level - 8, entry - 20)
    target = next_higher_level(pivot, R1)
    FIRE BULLISH signal

# BREAKOUT (R1 break with momentum) — currently DISABLED (kill switch)
IF current_price > R1 + 8pts
AND recent_low < R1              # was below R1 recently
AND trend == UP:                 # intel-confirmation gates removed 2026-08-13
  entry = current_price
  stop = max(R1 - 5, entry - 20)
  target = R2
  FIRE BULLISH breakout

# BREAKDOWN (S1 break with momentum) — mirror of breakout
IF current_price < S1 - 8pts
AND recent_high > S1 AND trend == DOWN:
  FIRE BEARISH breakdown

Validation Gate — price_alerts.py _send_alert()

# HARD VALIDATION (before anything posts to Discord)
IF abs(entry - stop) > 20:  SUPPRESS          # stop too wide
IF reward/risk < 1.2 OR > 3.0:  SUPPRESS      # R:R out of range

# STALE ENTRY CHECK
current_price = databento_feed.get_es_quote()["last"]  # live feed, feed_price.json handoff, historical fallback
IF direction == LONG:
  overshoot = current_price - entry
  IF overshoot > 10pts:  SUPPRESS              # entry too stale, price ran away
  IF overshoot > 5pts:   RE-PRICE entry to current, adjust stop/target
ELIF direction == SHORT:
  overshoot = entry - current_price
  [same logic reversed]

# EDGE DETECTION GATE (signal #4+)
IF daily_signal_count >= 3:
  confidence = (sources_aligned/2 * 0.6) + (confluence_count/5 * 0.4)
  IF confidence < 0.75:  SUPPRESS              # not enough edge for 4th+ signal

# If all gates pass:
#   1. Generate PNG via fonflo_graphics
#   2. POST to Discord
#   3. Set _active_signal for outcome tracking
#   4. Increment counters

Confidence Scoring — alert_engine.py

# Step-by-step confidence calculation
total = signal.base_confidence    # Tier 1 = 3, Tier 2 = 2

# DELTA STRENGTH
IF delta_strength == "Strong":  total += 2
ELIF delta_strength == "Weak":  total -= 1

# LEVEL CONFLUENCE
IF strong_level within 3.0pts:  total += 2
ELIF weak_level nearby:         total += 1

# LEVEL RECLAIM PRIORITY
IF signal_type == "LEVEL_RECLAIM":
  IF priority <= 2:  total += 2
  ELIF priority <= 4: total += 1

# PIVOT CONFLUENCE
IF price within 5.0 of pivot:  total += 2
  IF LONG AND price >= pivot:  total += 1    # pivot reclaim bonus
  IF SHORT AND price <= pivot: total += 1    # pivot loss bonus

# INTEL AGREEMENT
IF source1_signal matches direction AND not expired (120min):  total += 2
IF source2_level within 5pts of signal:  total += 2 (failed breakdown) or +1 (regular)
IF source3_bias matches direction:  total += 1
IF all three agree:  total += 1  (triple confluence bonus)

# REGIME
IF regime matches signal direction:     total += 1
IF regime OPPOSES signal (counter-trend): total -= 4   # HEAVY PENALTY
IF regime == VOLATILE:                  total -= 1

# CORRELATION (NQ/CL)
total += correlation.confidence_adjustment    # typically -1 to +1

# MULTI-TIMEFRAME
IF signal.timeframe == 5min:  total += 1

# MARKET INTERNALS
total += internals.confidence_for_direction()  # -1 to +2

# SESSION WEIGHTS (time-of-day adjustment)
# Pre-market: threshold +2 | Open: -1 | Midmorning: 0
# Lunch: +3 | Afternoon: -1 | Power hour: -1

# GATE: Only post if total >= ALERT_MIN_CONFIDENCE (8)

Edge Detection Formula — signal_logger.py

# Edge Detection — determines if signal gets "Fonflo Edge Detection" badge
source_weight = 0.6      # 60% weight on source alignment
confluence_weight = 0.4   # 40% weight on confluence factors

source_score = (sources_aligned / 3) * 100 * 0.6
# sources: intel_1, intel_2, level_at_major

confluence_score = (confluence_factors / 5) * 100 * 0.4
# factors: VIX, alert signal, time-of-day, price action, volume

confidence = round(source_score + confluence_score)
EDGE_TRIGGERED = confidence >= 85

# Examples:
# 3/3 sources + 4/5 factors = (1.0 * 60) + (0.8 * 40) = 92% ✓ TRIGGERED
# 2/3 sources + 5/5 factors = (0.67 * 60) + (1.0 * 40) = 80% ✗ NOT TRIGGERED
# 2/3 sources + 4/5 factors = (0.67 * 60) + (0.8 * 40) = 72% ✗ NOT TRIGGERED
# 3/3 sources + 3/5 factors = (1.0 * 60) + (0.6 * 40) = 84% ✗ NOT TRIGGERED

Deduplication Logic — signal_logger.py

PRICE_THRESHOLD = 4.0 pts
TIME_WINDOW = 15 minutes
MAX_SAME_DIRECTION = 2

# HARD CAP: max 2 active signals in same direction
same_dir_active = [s for s in active_signals
                   if s.direction == new.direction
                   and s.status in (PENDING, ACTIVE)]
IF len(same_dir_active) >= 2:  SUPPRESS "MAX_SAME_DIRECTION"

# PROXIMITY CHECK: same area + recent = duplicate
FOR each active signal of same direction:
  price_diff = abs(signal.entry - new_entry)
  time_diff = (now - signal.timestamp).minutes

  IF price_diff <= 4.0 AND time_diff <= 15:
    UNLESS signal.status in (STOPPED_OUT, SCRATCH):
      SUPPRESS "DUPLICATE"

# Cleanup: remove signals older than 8 hours
# Or older than 15min AND status is closed

Regime Classification — regime_detector.py

# Inputs: 1-min bars from the Databento live stream
atr_short = ATR(last 10 bars)     # 10-min momentum
atr_long  = ATR(last 30 bars)     # 30-min structural
atr_ratio = atr_short / atr_long

ma_fast = SMA(last 20 bars)       # 20-min MA
ma_slow = SMA(last 50 bars)       # 50-min MA
vwap = cumsum(price * volume) / cumsum(volume)

range_ratio = avg_range(last 10 bars) / avg_range(prior 10 bars)
higher_closes = bars[-3].close > bars[-4].close > bars[-5].close
lower_closes  = bars[-3].close < bars[-4].close < bars[-5].close

# Classification (checked in order — first match wins)
IF atr_ratio >= 1.5 AND range_ratio > 1.3:
  regime = VOLATILE                # both conditions required

ELIF close > ma_fast > ma_slow AND close > vwap AND higher_closes:
  regime = TRENDING_UP

ELIF close < ma_fast < ma_slow AND close < vwap AND lower_closes:
  regime = TRENDING_DOWN

ELIF atr_ratio <= 0.85:
  regime = RANGING                 # tight compression

ELSE:  # default fallback
  IF close > vwap AND higher_closes:   regime = TRENDING_UP
  ELIF close < vwap AND lower_closes:  regime = TRENDING_DOWN
  ELSE:                                regime = RANGING

# Confidence threshold adjustments
IF VOLATILE:  threshold_delta = +3   # raise thresholds (harder to fire)
IF RANGING:   threshold_delta = +2
ELSE:         threshold_delta = 0

RTH Data Fallback Chain — morning_report.py

# _get_prior_rth_ohlc() — 3-tier fallback for yesterday's RTH H/L/C

# Determine yesterday's trading date
yesterday = (now - 1 day)
WHILE yesterday is weekend:  yesterday -= 1 day
IF yesterday in MARKET_HOLIDAYS:  yesterday -= 1 day (skip holidays too)

# TIER 1: RTH Snapshot (data/rth_session.json)
snapshot = load("data/rth_session.json")
IF snapshot.date == str(yesterday):       # DATE MUST MATCH
  IF snapshot.high > 0 AND snapshot.low > 0:
    RETURN {open, high, low, close from snapshot}
ELSE:
  LOG WARNING "Snapshot STALE — date={snapshot.date}, expected={yesterday}"

# TIER 2: Databento local parquet — 1-min bars filtered to the RTH window
bars = databento_client.get_rth_ohlc(yesterday)     # self-heals missing
IF bars.high > 0 AND bars.low > 0:                  # sessions via
  RETURN {open, high, low, close from bars}         # ensure_sessions()

RETURN {0, 0, 0, 0}  # failed — the critical-data gate refuses to post

Gap Detection — morning_report.py _get_levels()

# After calculating standard RTH pivots, check if they're relevant

current_price = databento_feed.get_es_quote()["last"]  # live feed, feed_price.json handoff, historical fallback
pivot = levels["pivot"]

IF abs(current_price - pivot) > 100:    # MASSIVE GAP
  # Standard RTH pivots are useless — recalculate with 24hr data
  gap_h = live_quote["high"]     # includes overnight
  gap_l = live_quote["low"]      # includes overnight
  gap_c = live_quote["close"]    # prior RTH close

  gap_pivot = (gap_h + gap_l + gap_c) / 3
  gap_r1 = 2 * gap_pivot - gap_l
  gap_r2 = gap_pivot + (gap_h - gap_l)
  gap_r3 = gap_h + 2 * (gap_pivot - gap_l)
  gap_s1 = 2 * gap_pivot - gap_h
  gap_s2 = gap_pivot - (gap_h - gap_l)
  gap_s3 = gap_l - 2 * (gap_h - gap_l)

  LOG WARNING "GAP DETECTED: current={current}, RTH pivot={pivot}"
  REPLACE levels with gap-adjusted levels

# Scenario fallbacks also adapt:
IF current_price > R1:
  bull_scenario = "Hold above R1, push through R2. Extended target R3."
  bear_scenario = "Rejection at R2. Fade back to R1 then pivot."
ELIF current_price > pivot + 50:
  bull_scenario = "Hold above pivot. Target R1 then R2."
  bear_scenario = "Lose R1 and fail to hold pivot. Target S1."
ELSE:
  bull_scenario = "Reclaim pivot with volume. Target R1 then R2."
  bear_scenario = "Lose pivot. Target S1 then S2."

Verification Checks — verify_daily_alpha.py

# 9 verification checks run on every morning report draft

CHECK A: HOD/LOD
  Find "high" or "low" near 4-5 digit prices in draft
  IF mentioned_high differs from actual es_high by > 10pts:  ERROR
  IF mentioned_low differs from actual es_low by > 10pts:    ERROR

CHECK B: RTH/ETH Confusion
  IF (current_globex - rth_close) < 10:  SKIP (no confusion possible)
  Find "closed at X" mentions
  IF mentioned_price closer to globex than RTH:  ERROR "RTH/ETH confusion"
  Also flag recovery language ("bounced", "found support") without
  qualifier ("overnight", "globex") when RTH was actually down

CHECK C: Price Accuracy
  Find "closed at X" mentions
  IF abs(mentioned - es_close) > 2pts:  ERROR

CHECK D: Narrative Tone
  Map daily_pct_change to expected descriptors:
    abs(pct) < 0.3%:   "flat" OK
    pct < -0.3%:       "down/sold/declined" expected
    pct > +0.3%:       "up/rallied/gained" expected
  IF flat_word found but abs(pct) >= 0.3%:  ERROR
  IF up_word found but pct < -0.3% (without qualifier):  ERROR
  IF down_word found but pct > +0.3% (without qualifier):  ERROR

CHECK E: Level Ordering
  Extract R3, R2, R1, Pivot, S1, S2, S3 from draft
  Verify strict: R3 > R2 > R1 > Pivot > S1 > S2 > S3
  IF abs(pivot - es_close) > 50pts:  ERROR "unreasonable pivot"
  IF abs(pivot - current_live_price) > 100pts:  ERROR "stale levels"

CHECK F: VIX Accuracy
  Find "VIX at X" mentions
  IF abs(mentioned - vix_actual) > 2:  ERROR

CHECK G: Economic Events
  Extract "EVENT at HH:MM AM/PM ET" from draft
  Match against known calendar
  IF stated_time != expected_time:  ERROR

CHECK H: Claude Deep-Check
  Send draft + market data to Claude API
  Flag ONLY: tone vs reality, wrong close, wrong direction, RTH/ETH confusion
  Do NOT flag: style, opinions, level calculations, approximate language

Smart Setups — realtime_flo.py

# 5 intraday setups, max 3 alerts per session

SETUP 1: IB FADE [BACKTESTED]
  Delegates to quant.signal_engine.check_auction_fade()
  Condition: IB breakout fails, price traps back inside
  Posted as full signal card with entry/stop/target

SETUP 2: FAILED AUCTION [OBSERVATION ONLY]
  FOR each of last 3 bars:
    IF bar.high >= IB_high + 1.0 AND bar.high <= IB_high + 3.0:
      IF any of next 2 bars closes below IB_high:
        Signal: SHORT, stop = bar.high + 2, target = IB_mid
    [Mirror for IB low break → LONG]

SETUP 3: SINGLE PRINTS FILL [OBSERVATION ONLY]
  Track 5-min bars with no overlap to adjacent bars (profile gaps)
  When price returns to fill gap: setup triggered

SETUP 4: EXCESS VOLUME DIVERGENCE [OBSERVATION ONLY]
  New price extremes on declining volume = exhaustion
  Low-volume new highs → bearish divergence
  Low-volume new lows → bullish divergence

SETUP 5: VALUE AREA MIGRATION [OBSERVATION ONLY]
  Developing value area shifts direction mid-session
  Shift UP = bullish continuation, shift DOWN = bearish continuation

POSTING RULES:
  Market hours only (9:30 ET - 4:00 PM ET)
  Min 60 seconds between posts
  Max 3 setup alerts per session
  Backtested → signal card | Non-backtested → observation only
  If no setups by 10:30 ET → post silence message

Backtest Engine — quant/strategy_lab.py

# Walk-forward backtest on 1-min bars

CONSTANTS:
  SLIPPAGE = 0.25 pts per side
  COMMISSION = $4.50 RT = 0.09 pts equivalent
  MIN_SAMPLE_IS = 30 trades (in-sample)
  MIN_SAMPLE_OOS = 20 trades (out-of-sample)

_sim_trade(bars, entry, stop, target, direction):
  FOR each bar after entry:
    IF direction == SHORT:
      IF bar.high >= stop:
        exit = stop + SLIPPAGE
        pnl = entry - exit - COMMISSION_PTS
        RETURN LOSS
      IF bar.low <= target:
        exit = target + SLIPPAGE
        pnl = entry - exit - COMMISSION_PTS
        RETURN WIN

    IF direction == LONG:
      IF bar.low <= stop:
        exit = stop - SLIPPAGE
        pnl = exit - entry - COMMISSION_PTS
        RETURN LOSS
      IF bar.high >= target:
        exit = target - SLIPPAGE
        pnl = exit - entry - COMMISSION_PTS
        RETURN WIN

  # Session ended without hit → exit at last close
  pnl = (last_close - entry) * direction_sign - SLIPPAGE - COMMISSION_PTS
  RETURN WIN if pnl > 0 else LOSS

# WALK-FORWARD SPLIT:
#   Train: months 1-4 (in-sample)
#   Test:  months 5-6 (out-of-sample, strict)
#   Entry: t+1 bar open (zero lookahead)
#   Stops: checked intrabar on H/L, not close

# MONTE CARLO RANDOM CONTROL:
FOR i in 1..200:
  Generate random entries on same trading days
  Use identical stop/target distances
  Compute full P&L distribution
strategy_edge = strategy_mean_pnl / random_mean_pnl

# WILSON SCORE CI (95% on win rate):
z = 1.96
p = win_rate
denom = 1 + z²/n
center = (p + z²/(2n)) / denom
margin = z * sqrt(p(1-p)/n + z²/(4n²)) / denom
ci_lower = max(0, center - margin)
ci_upper = min(1, center + margin)

Strategy Grading — quant/strategy_lab.py

# Grade assignment after backtest + Monte Carlo + VIX decomposition

GRADE A (promote immediately):
  full_profit_factor > 1.5
  AND oos_profit_factor > 1.3
  AND oos_win_rate > 50% (Wilson CI lower bound)
  AND edge_vs_random > 2.0x
  AND n_trades_is >= 30
  AND n_trades_oos >= 20

GRADE B (promote with monitoring):
  full_profit_factor > 1.3
  AND oos_profit_factor > 1.0
  AND oos_win_rate > 45%
  AND edge_vs_random > 1.0x
  AND n_trades_is >= 25
  AND n_trades_oos >= 15

GRADE C (archive, do not promote):
  full_profit_factor > 1.0
  AND (marginal OOS OR thin sample)

GRADE F (killed):
  full_profit_factor <= 1.0
  OR oos_profit_factor < 1.0
  OR oos_win_rate < 45%
  OR fails random test (edge < 1.0x)
  OR n_total < 20

# VIX REGIME DECOMPOSITION:
# Performance split across: VIX < 16 (low), 16-22 (normal), > 22 (elevated)
# Strategy that only works in one regime = conditional bet, not robust edge
# Optimal regime encoded as production filter

Production State Machine — quant/production_tracker.py

# Strategy lifecycle: PROBATION → ACTIVE → DEGRADED → KILLED / DORMANT

track_live_trade(strategy_name, trade):

  # PROBATION PHASE (first 10 trades)
  IF n_trades <= 10:
    IF n_trades >= 5 AND live_pf < 1.0:
      status = KILLED
      reason = "Failed probation: PF < 1.0 after 5 trades"
    IF n_trades == 10:
      status = ACTIVE      # passed probation
      LOG "PROBATION_PASSED"

  # ACTIVE MONITORING
  IF n_trades >= 30 AND live_pf < 0.6:
    status = KILLED
    reason = "Live PF < 0.6 after 30+ trades"

  ELIF n_trades >= 20 AND live_pf < 0.8:
    status = DEGRADED
    reason = "Live PF < 0.8 after 20+ trades"

  ELIF n_trades >= 20 AND (backtest_wr - live_wr) > 0.20:
    status = DEGRADED
    reason = "Live WR dropped 20pp below backtest"

  # RESTORATION
  IF status == DEGRADED AND n_trades >= 20:
    IF live_pf >= 0.8 AND (backtest_wr - live_wr) <= 0.15:
      status = ACTIVE      # restored from degradation

  # DORMANCY
  IF days_since_last_signal >= 15 AND status == ACTIVE:
    status = DORMANT
    reason = "No signals in 15+ trading days"

# State diagram:
#   [NEW] → PROBATION(10 trades) → ACTIVE
#                    ↓ (PF<1.0 @ 5)     ↓ (PF<0.6 @ 30)
#                  KILLED              KILLED
#                                        ↑
#   ACTIVE → DEGRADED (PF<0.8 @ 20) → KILLED (if persists)
#      ↓         ↓ (PF recovers)
#   DORMANT    ACTIVE (restored)

All Configuration Thresholds

Signal Quality

ParameterValueSource
ALERT_MIN_CONFIDENCE8config.py
EDGE_DETECTION_THRESHOLD85%signal_logger.py
MAX_STOP_PTS20price_alerts.py
MAX_TARGET_PTS30price_alerts.py
MIN_TARGET_PTS10price_alerts.py (NEW)
MAX_DAILY_SIGNALS3price_alerts.py (NEW)
MIN_RR_RATIO1.2price_alerts.py
MAX_RR_RATIO3.0price_alerts.py
PROXIMITY_PTS8price_alerts.py
REJECTION_PTS15price_alerts.py
MIN_PRICE_MOVE15 ptsprice_alerts.py
FLIP_COOLDOWN15 minprice_alerts.py

Orderflow

ParameterValueSource
CVD_DIVERGENCE_BARS3config.py
ABSORPTION_VOLUME_MULTIPLIER2.0xconfig.py
LARGE_PRINT_THRESHOLD100 contractsconfig.py

Deduplication

ParameterValueSource
PRICE_THRESHOLD4.0 ptssignal_logger.py
TIME_WINDOW15 minsignal_logger.py
MAX_SAME_DIRECTION2signal_logger.py

Confidence Adjustments

FactorValueNotes
Intel source match+2Per source, decays after 120min
Level confluence+1 to +2Strong level = +2, weak = +1
Pivot confluence+2 to +3+2 base, +1 reclaim/loss bonus
All-three-agree+1Triple confluence bonus
Counter-trend regime-4Heavy penalty
Volatile regime-1Additional penalty
Event lockout-2Within 5min of data release
VIX extreme-1VIX > 28
Inventory extreme-2Market maker at limits

Session Weights

SessionTime (ET)Threshold Adj
Pre-market6:00-9:30+2
Open9:30-10:30-1
Midmorning10:30-12:000
Lunch12:00-13:00+3
Afternoon13:00-16:00-1
Power hour16:00-17:00-1

Market Internals

ParameterValueSource
TICK_BULL_THRESHOLD+600config.py
TICK_BEAR_THRESHOLD-600config.py
TRIN_BULL_THRESHOLD0.75config.py
TRIN_BEAR_THRESHOLD1.50config.py
VIX_HIGH_THRESHOLD28config.py
GAP_THRESHOLD100 ptsmorning_report.py

Backtest

ParameterValueSource
SLIPPAGE0.25 pts/sidestrategy_lab.py
COMMISSION_RT$4.50 (0.09pts)strategy_lab.py
MONTE_CARLO_ITERATIONS200strategy_lab.py
WILSON_SCORE_Z1.96 (95% CI)strategy_lab.py
WALK_FORWARD_TRAINMonths 1-4strategy_lab.py
WALK_FORWARD_TESTMonths 5-6strategy_lab.py

Production Tracker

ParameterValueTrigger
Probation limit10 tradesFirst 10 trades monitored
Probation killPF < 1.0 @ 5 tradesImmediate kill
Active killPF < 0.6 @ 30 tradesAuto-kill
DegradationPF < 0.8 @ 20 tradesFlag degraded
WR degradationWR drops 20pp vs backtestFlag degraded
RestorationPF ≥ 0.8 AND WR within 15ppRestore to active
Dormancy15 days no signalsFlag dormant

Last updated: — Auto-generated from codebase audit