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
| Component | Technology |
|---|---|
| Language | Python 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 handoff | Streaming feed persists its price every 60s (data/feed_price.json) so out-of-process builds always have a live quote |
| VIX | Front VX future via Databento (no third-party caches) |
| LLM | Claude Opus (morning narrative + verification auditor), Claude Haiku (high-volume classification) |
| Communication | Discord webhooks + bot token |
| Intelligence | External-intel layer currently disabled (operator switch, 2026-08) |
| Graphics | Playwright HTML-to-PNG rendering |
| Hosting | Cloudflare Pages (web), local Windows machine (runtime) |
| State | JSON files in data/ directory (no database) |
Task Schedule (PST)
| Time | Task | Component |
|---|---|---|
| 6:00 AM | Level calc, inventory, prev-day classify | level_engine, etc. |
| 6:00 AM | Morning 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 AM | Opening auction classifier | opening_auction |
| 6:00-2:00 PM | Market-hours components active | regime, orderflow, price_alerts, etc. |
| 1:55 PM | RTH session snapshot | scheduler.snapshot_rth_session() |
| 2:00 PM | EOD report grading | report_grader |
| 2:05 PM | EOD summary PNG | scheduler.send_eod_summary() |
| 2:15 PM | Production audit + missed opp analysis | production_tracker, missed_opp |
| 2:30 PM | Lab site deploy | lab_publisher |
| Always-on | Discord/Substack/Email/X/News monitors | discord_monitor, email_monitor, etc. |
Component Groups
- Always-running: welcome_bot (external-intel monitors currently disabled — see Intelligence Sources)
- Market-hours (6 AM - 2 PM PST): regime_detector, session_manager, correlation_filter, internals_monitor, orderflow signals, price_alerts, alert_engine, quant_signals
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.
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.
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.
Signal Detection
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
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
Verification Checks
- HOD/LOD: Validates high-of-day and low-of-day references against real data
- RTH/ETH confusion: Catches mixing of RTH close with overnight prices
- Price accuracy: Compares every mentioned price against actual data
- Narrative tone: Flags "flat" for +2% days, "sold off" for +1% days, etc.
- Level ordering: R3 > R2 > R1 > Pivot > S1 > S2 > S3
- Level staleness: Flags pivot >100pts from current live price
- VIX accuracy: Compares mentioned VIX against actual
- Economic events: Validates event times and names
- Claude deep-check: LLM reviews for subtle logical errors
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
- RTH Snapshot (data/rth_session.json) — captured at 1:55 PM PST. Must match yesterday's date or skipped.
- 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
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
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)
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
Grade Criteria
| Criteria | Grade A | Grade B | Grade F |
|---|---|---|---|
| Mean P&L per trade | ≥10pts | ≥10pts | <10pts (auto-kill) |
| Full PF | >1.5 | >1.3 | Below |
| OOS PF | >1.3 | >1.0 | Below |
| OOS Win Rate | >50% (Wilson CI) | >45% | Below |
| Edge vs Random | >2x | >1x | No edge |
| Sample (IS/OOS) | ≥30/20 | ≥25/15 | Below |
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
| Component | Model | Volume | Cost/day |
|---|---|---|---|
| Strategy lab (Opus) | Opus 4.6 | 12 cycles | $2.04 |
| Morning report narrative | Opus | 1 call | — |
| Verify deep-check | Opus (auditor ≥ generator) | 1 call | — |
| Intel parsing | disabled 2026-08-13 | 0 calls | $0 |
| Level engine LLM | Haiku 4.5 | 1 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
| File | Lines | Purpose |
|---|---|---|
| scheduler.py | 572 | Master controller, task scheduling, component lifecycle |
| morning_report.py | 4100+ | Pre-market report generation + Claude narrative (split 2026-07-28 into morning_report_text.py + morning_report_draft.py) |
| price_alerts.py | 3800+ | Primary signal detection (level rejection/bounce/breakdown, ORB failure, midday fade) |
| orderflow_signals.py | — | Databento tick signal detection (CVD divergence, absorption, delta thrust) |
| alert_engine.py | 1000 | Confidence scoring, multi-stage filtering, Discord posting |
| level_engine.py | 994 | S/R level calculation, LLM cross-reference |
| verify_daily_alpha.py | 800+ | Report auditor (price, tone, levels, VIX, events) |
| fonflo_graphics.py | 852 | PNG rendering (signal cards, results, reports, EOD) |
| signal_logger.py | 446 | Signal dedup, Edge Detection, outcome tracking |
| signal_gateway.py | ~200 | Unified dedup across engines (built, not wired in) |
| realtime_flo.py | 951 | Smart setups: IB fade, failed auction, single prints, etc. |
| databento_feed.py / databento_client.py | — | Live streaming feed + licensed historical client (the only market-data path) |
| level_break_monitor.py | — | Market Pulse break cards: outermost published rung only, flow-confirmed, staleness veto, cooldown |
| config.py | 166 | Centralized .env config, all thresholds |
Intelligence
| File | Lines | Purpose |
|---|---|---|
| discord_monitor.py | 1273 | Source 1: Discord signal extraction + accuracy tracking |
| substack_monitor.py | 520 | Source 2: Substack RSS → Claude parsing |
| email_monitor.py | 657 | Source 3: Gmail IMAP → Claude parsing |
| x_monitor.py | ~400 | Source 1 X/Twitter feed via Grok API |
| news_monitor.py | ~500 | Overnight news aggregation via Grok |
Market Context
| File | Purpose |
|---|---|
| regime_detector.py | Real-time market regime classification |
| correlation_filter.py | NQ/CL divergence checks |
| internals_monitor.py | Market breadth (TICK, TRIN, ADD) |
| contract_manager.py | ES auto-roll (quarterly) |
| inventory_analyzer.py | Market maker positioning |
| previous_day_classifier.py | Prior session day-type classification |
| opening_auction.py | Opening type detection (gap, drive, auction) |
Quant Lab
| File | Lines | Purpose |
|---|---|---|
| quant/strategy_lab.py | 1100 | Autonomous discovery framework (detect → backtest → grade) |
| quant/strategy_inventor.py | 550 | Claude Haiku generates Python detection code |
| quant/signal_engine.py | 700 | Live signal generation from promoted strategies |
| quant/day_classifier.py | 400 | 6 day types + real-time prediction at 4 checkpoints |
| quant/production_tracker.py | 400 | Strategy lifecycle: probation → active → degraded → killed |
| quant/lab_publisher.py | 2200 | HTML report generation + Cloudflare deployment |
State Files (data/)
| File | Purpose |
|---|---|
| draft_state.json | Current morning report draft + approval status |
| level_cache.json | Cached level engine output |
| levels.json | Full level detail with notes and confluences |
| rth_session.json | RTH snapshot (H/L/C) from 1:55 PM capture |
| session_state.json | Inventory, prev-day, opening auction state |
| feed_price.json | Live-price handoff from the streaming feed (60s heartbeat) |
| published_ladder.json | The morning card's final level labels (one vocabulary for all intraday producers) |
| adam_log.json | Source 1 parsed signals |
| mancini_log.json | Source 2 parsed analyses |
| tictoc_log.json | Source 3 parsed plans |
| news_log.json | Aggregated overnight news |
| signals_database.json | All signals with outcomes |
| econ_calendar_cache.json | Economic 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
- 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
- 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_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
- 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
- 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
| Parameter | Value | Source |
|---|---|---|
| ALERT_MIN_CONFIDENCE | 8 | config.py |
| EDGE_DETECTION_THRESHOLD | 85% | signal_logger.py |
| MAX_STOP_PTS | 20 | price_alerts.py |
| MAX_TARGET_PTS | 30 | price_alerts.py |
| MIN_TARGET_PTS | 10 | price_alerts.py (NEW) |
| MAX_DAILY_SIGNALS | 3 | price_alerts.py (NEW) |
| MIN_RR_RATIO | 1.2 | price_alerts.py |
| MAX_RR_RATIO | 3.0 | price_alerts.py |
| PROXIMITY_PTS | 8 | price_alerts.py |
| REJECTION_PTS | 15 | price_alerts.py |
| MIN_PRICE_MOVE | 15 pts | price_alerts.py |
| FLIP_COOLDOWN | 15 min | price_alerts.py |
Orderflow
| Parameter | Value | Source |
|---|---|---|
| CVD_DIVERGENCE_BARS | 3 | config.py |
| ABSORPTION_VOLUME_MULTIPLIER | 2.0x | config.py |
| LARGE_PRINT_THRESHOLD | 100 contracts | config.py |
Deduplication
| Parameter | Value | Source |
|---|---|---|
| PRICE_THRESHOLD | 4.0 pts | signal_logger.py |
| TIME_WINDOW | 15 min | signal_logger.py |
| MAX_SAME_DIRECTION | 2 | signal_logger.py |
Confidence Adjustments
| Factor | Value | Notes |
|---|---|---|
| Intel source match | +2 | Per source, decays after 120min |
| Level confluence | +1 to +2 | Strong level = +2, weak = +1 |
| Pivot confluence | +2 to +3 | +2 base, +1 reclaim/loss bonus |
| All-three-agree | +1 | Triple confluence bonus |
| Counter-trend regime | -4 | Heavy penalty |
| Volatile regime | -1 | Additional penalty |
| Event lockout | -2 | Within 5min of data release |
| VIX extreme | -1 | VIX > 28 |
| Inventory extreme | -2 | Market maker at limits |
Session Weights
| Session | Time (ET) | Threshold Adj |
|---|---|---|
| Pre-market | 6:00-9:30 | +2 |
| Open | 9:30-10:30 | -1 |
| Midmorning | 10:30-12:00 | 0 |
| Lunch | 12:00-13:00 | +3 |
| Afternoon | 13:00-16:00 | -1 |
| Power hour | 16:00-17:00 | -1 |
Market Internals
| Parameter | Value | Source |
|---|---|---|
| TICK_BULL_THRESHOLD | +600 | config.py |
| TICK_BEAR_THRESHOLD | -600 | config.py |
| TRIN_BULL_THRESHOLD | 0.75 | config.py |
| TRIN_BEAR_THRESHOLD | 1.50 | config.py |
| VIX_HIGH_THRESHOLD | 28 | config.py |
| GAP_THRESHOLD | 100 pts | morning_report.py |
Backtest
| Parameter | Value | Source |
|---|---|---|
| SLIPPAGE | 0.25 pts/side | strategy_lab.py |
| COMMISSION_RT | $4.50 (0.09pts) | strategy_lab.py |
| MONTE_CARLO_ITERATIONS | 200 | strategy_lab.py |
| WILSON_SCORE_Z | 1.96 (95% CI) | strategy_lab.py |
| WALK_FORWARD_TRAIN | Months 1-4 | strategy_lab.py |
| WALK_FORWARD_TEST | Months 5-6 | strategy_lab.py |
Production Tracker
| Parameter | Value | Trigger |
|---|---|---|
| Probation limit | 10 trades | First 10 trades monitored |
| Probation kill | PF < 1.0 @ 5 trades | Immediate kill |
| Active kill | PF < 0.6 @ 30 trades | Auto-kill |
| Degradation | PF < 0.8 @ 20 trades | Flag degraded |
| WR degradation | WR drops 20pp vs backtest | Flag degraded |
| Restoration | PF ≥ 0.8 AND WR within 15pp | Restore to active |
| Dormancy | 15 days no signals | Flag dormant |
Last updated: — Auto-generated from codebase audit