Fonflo System Architecture

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

Recent updates (April 2026)

Databento migrated as primary historical data source (561K bars / 12mo of 1-min data, local parquet)

Strategy lab throttled from 8,640 → 12 cycles/day, switched to Opus 4.6 (premium hypotheses, $14/wk budget)

10pt minimum edge floor applied to both signal validation and strategy grading (volatile market hardening)

Hallucination guard validates every price in narrative against known levels

Schwab auto-fallback on token expiry (yfinance + Discord DM)

JSON file locking via safe_json.py atomic writes

IBKR auto-reconnect at 5:30 AM PST

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
Historical Data (Primary)Databento — local parquet (561K bars, 12mo, no API auth issues)
Live Quotes (Primary)Schwab API — current price, auto-fallback to yfinance on token expiry
Live DOM/TicksIBKR TWS — Level 2 DOM, tick-by-tick (auto-reconnect at 5:30 AM)
Fallback Tieryfinance — used when Schwab and Databento both fail
LLMClaude Haiku (high-volume), Sonnet (morning report narrative)
CommunicationDiscord webhooks + bot token
IntelligenceDiscord user token, Substack RSS, Gmail IMAP
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, gamma, inventory, prev-day classifylevel_engine, gamma_monitor, etc.
6:15 AMMorning report draft + spawn monitormorning_report.generate_draft()
6:25 AMOpening auction classifieropening_auction
6:00-2:00 PMMarket-hours components activeregime, orderflow, price_alerts, etc.
1:55 PMRTH snapshot (before Schwab resets)scheduler.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

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)

IBKR TWS Level 2 DOM + tick data. Generates orderflow-based signals.

  • 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

The largest module (2786 lines). Generates a pre-market narrative report as PNG for Discord at 6:15 AM PST.

Generation Flow

1Fetch RTH data — prior session H/L/C from snapshot, Schwab, or yfinance (3-tier fallback)
2Fetch context — 5-day prices, overnight range, VIX trend, economic calendar, news
3Compile intel — latest from mancini_log.json + tictoc_log.json (silent, never attributed)
4Calculate levels — standard pivots + gap detection (if current price >100pts from pivot, use 24hr H/L/C)
5Claude narrative — Sonnet generates 2-3 paragraphs + BULLISH/BEARISH scenarios
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 → poll immediately for reactions

Thumbs up: Publish instantly to #premarket-report

No response after 5 min: Auto-publish

Thumbs down: Hold for edits, extend deadline to 15 min

Duplicate guard: _published_today flag prevents double posting

Levels Engine

level_engine.py (994 lines) — Three-tier data sourcing with LLM-based smart money cross-reference.

Prior RTH Data Fallback Chain

  1. RTH Snapshot (data/rth_session.json) — captured at 1:55 PM PST. Must match yesterday's date or skipped.
  2. yfinance daily bars — clean RTH H/L/C from ES=F ticker
  3. yfinance 5m intraday — filter to 9:30 AM - 4:00 PM ET window

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.

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 narrativeSonnet 4.61 call$0.05
Verify deep-checkHaiku 4.51 call$0.005
Intel parsing (Discord/Substack/Email)Haiku 4.5~20 calls$0.02
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 priority: Schwab first, IBKR second (DOM only), yfinance last. Always log which source used.

RTH snapshot: Must capture at 1:55 PM PST or data is lost (Schwab resets H/L/C after close).

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

Schwab token: Expires every 7 days. Run reauth_schwab.py to refresh.

Active contract: ESM6 (June 2026, expires 2026-06-20). Symbol format: single-digit year.

File Map

Core Pipeline

FileLinesPurpose
scheduler.py572Master controller, task scheduling, component lifecycle
morning_report.py2786Pre-market report generation, draft workflow, Claude narrative
price_alerts.py802Primary signal detection (level rejection/bounce/breakout)
orderflow_engine.py989IBKR DOM/tick signal detection (CVD, absorption, delta)
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.
schwab_data.py240Schwab API client (quotes, snapshots)
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)
gamma_monitor.pyGamma exposure zones
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
gamma_state.jsonGamma zone + flip level
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

IBKR TWS ──→ orderflow_engine ──→ signal_queue ──→ alert_engine ──→ Discord
                  │                                      ↑
                  └──→ bar_queue ──→ regime_detector ─────┘
                                          │
Schwab API ──→ price_alerts ─────────────→ alert_engine
    │              │
    └──→ level_engine ──→ levels.json ──→ morning_report
    │                                         │
    └──→ schwab_data ──→ fonflo_graphics     ↓
                              │          PNG → Discord
Discord ──→ discord_monitor ──→ adam_log.json ──→ alert_engine (bonus)
Substack ──→ substack_monitor ──→ mancini_log.json ──→ morning_report
Gmail ──→ email_monitor ──→ tictoc_log.json ──→ morning_report

Claude Haiku ──→ 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
  • Schwab token expires every 7 days — manual refresh required via reauth_schwab.py
  • IBKR TWS auto-logs out at 11:59 PM daily — must re-login before 5:45 AM
  • Discord user token auth (not bot) for monitors — violates Discord TOS
  • yfinance rate limiting can fail silently — returns empty DataFrame
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
  • 2786 lines — single largest file, doing too much (data fetch + LLM + verify + PNG + draft workflow)
  • Claude narrative can reference levels not in CALCULATED_LEVELS (hallucination)
  • Level fallback chain has 3 tiers but error recovery between tiers is inconsistent
  • Contract expiry hardcoded to 2026-06-20 — needs manual update for roll
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
    IF mancini_bias == "BULLISH" OR tictoc_bias == "BULLISH":  SKIP

    # Confluence check: count aligned factors
    confluence = 0
    IF mancini_bias == "BEARISH":  confluence += 1
    IF tictoc_bias == "BEARISH":   confluence += 1
    IF vix > 25:                   confluence += 1
    IF price_action_bearish:       confluence += 1

    # If both sources SILENT: require 2+ non-intel factors
    IF mancini_bias is None AND tictoc_bias is None:
      IF confluence < 2:  SKIP
    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)
IF current_price > R1 + 8pts
AND recent_low < R1              # was below R1 recently
AND trend == UP
AND (mancini == "BULLISH" OR tictoc == "BULLISH"):  # requires intel confirmation
  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
AND (mancini == "BEARISH" OR tictoc == "BEARISH"):
  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 = schwab.get_es_quote()["last"]
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 IBKR via bar_queue
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: yfinance daily bars (clean RTH H/L/C)
daily = yfinance.Ticker("ES=F").history(period="5d", interval="1d")
IF len(daily) >= 2:
  yesterday_row = daily.iloc[-2]    # second to last (last = today's partial)
  IF high > 0 AND low > 0 AND close > 0:
    RETURN {open, high, low, close from yesterday_row}

# TIER 3: yfinance 5m intraday bars, filtered to RTH window
hist = yfinance.Ticker("ES=F").history(period="2d", interval="5m")
rth_bars = [bar for bar in hist if 9:30 AM ET <= bar.time < 4:00 PM ET
            AND bar.date == yesterday]
IF rth_bars:
  RETURN {open=first.open, high=max(highs), low=min(lows), close=last.close}

RETURN {0, 0, 0, 0}  # all tiers failed

Gap Detection — morning_report.py _get_levels()

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

current_price = schwab.get_es_quote()["last"]
pivot = levels["pivot"]

IF abs(current_price - pivot) > 100:    # MASSIVE GAP
  # Standard RTH pivots are useless — recalculate with 24hr data
  gap_h = schwab_quote["high"]     # includes overnight
  gap_l = schwab_quote["low"]      # includes overnight
  gap_c = schwab_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 — 64% WR]
  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