{ "meta": { "spec_doc": "docs/SMC_FVG_SPEC_v1.md", "spec_version": "v1", "phase": "P3-S.4", "principle": "Expected results derived from PROJECT SEMANTIC SPECIFICATION (S-1..S-13), NOT from current implementation. Code port results are reported for conformance observation, not as truth.", "constants": { "FVG_LOOKBACK": 40, "ENGINE2_LOOKBACK_C": 40, "ENGINE2_LOOKBACK_E": 20, "DISPLAY_MIN_ATR": 0.02, "LEGACY_AUTO_MIN_ATR": 0.25, "SYMBOL": "XAUUSD", "DECISION_TF": "M15" }, "bar_format": "[open, high, low, close] per bar, chronological (index 0 = oldest). All bars are CLOSED up to decision_bar (the newest closed bar); bars beyond decision_bar are future/forming and MUST NOT influence the decision.", "zone_format": "[formation_bar(C3), dir, top, bot, mit] — dir +1 bullish / -1 bearish; top/bot zone bounds (S-1); mit 1 = mitigated (wick full-fill, S-9).", "newest_unmitigated_format": "[bar, dir, top, bot] of the newest UNMITIGATED zone at the decision bar (canonical consumer query S-9), or null if none.", "code_newest_expected": "Engine-2 AF_FindFVG observed result [dir, top, bot] (or null): newest FVG with NO mitigation filter and 1-bar lag (C3 min = decision_bar-1). Reported as conformance observation; divergence documents BUG-P3S4-001/-002.", "note": "All 20 required cases (FVG-T01..T20). Expected outputs are the SPEC truth; code outputs are observations." }, "cases": [ { "id": "FVG-T01", "title": "Valid bullish FVG", "rule": "SPEC S-1/S-4 — Low(C3) > High(C1), zone [High(C1), Low(C3)], dir=+1", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]], [5, [100.6, 100.8, 100.45, 100.7]]] }, "decision_bar": 5, "expected": { "zones": [[4, 1, 100.4, 100.2, 0]], "zone_count": 1, "newest_unmitigated": [4, 1, 100.4, 100.2], "code_newest_expected": [1, 100.4, 100.2] } }, { "id": "FVG-T02", "title": "Valid bearish FVG", "rule": "SPEC S-1/S-4 — High(C3) < Low(C1), zone [High(C3), Low(C1)], dir=-1", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [[2, [100.3, 100.5, 100.0, 100.4]], [3, [100.0, 100.2, 99.6, 99.8]], [4, [99.6, 99.8, 99.2, 99.5]], [5, [99.4, 99.6, 99.1, 99.5]]] }, "decision_bar": 5, "expected": { "zones": [[4, -1, 100.0, 99.8, 0]], "zone_count": 1, "newest_unmitigated": [4, -1, 100.0, 99.8], "code_newest_expected": [-1, 100.0, 99.8] } }, { "id": "FVG-T03", "title": "No gap", "rule": "SPEC S-1 — no Low(C3) > High(C1) / High(C3) < Low(C1) anywhere -> NO zones", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [] }, "decision_bar": 5, "expected": { "zones": [], "zone_count": 0, "newest_unmitigated": null, "code_newest_expected": null } }, { "id": "FVG-T04", "title": "Gap exactly at minimum threshold (1 tick)", "rule": "SPEC S-3 — strictly positive gap is valid; gap = 1 tick (0.1) -> FVG", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.3, 100.9]], [5, [100.6, 100.8, 100.45, 100.7]]] }, "decision_bar": 5, "expected": { "zones": [[4, 1, 100.3, 100.2, 0]], "zone_count": 1, "newest_unmitigated": [4, 1, 100.3, 100.2], "code_newest_expected": [1, 100.3, 100.2] } }, { "id": "FVG-T05", "title": "Gap one tick below threshold (zero gap)", "rule": "SPEC S-3 — Low(C3) == High(C1) is NOT an FVG (strict >) -> NO zone", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.3, 100.8, 100.2, 100.7]], [5, [100.6, 100.8, 100.45, 100.7]]] }, "decision_bar": 5, "expected": { "zones": [], "zone_count": 0, "newest_unmitigated": null, "code_newest_expected": null } }, { "id": "FVG-T06", "title": "Gap one tick above threshold (2 ticks)", "rule": "SPEC S-3 — gap = 2 ticks (0.2) -> valid FVG (spec has no larger minimum)", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]], [5, [100.6, 100.8, 100.45, 100.7]]] }, "decision_bar": 5, "expected": { "zones": [[4, 1, 100.4, 100.2, 0]], "zone_count": 1, "newest_unmitigated": [4, 1, 100.4, 100.2], "code_newest_expected": [1, 100.4, 100.2] } }, { "id": "FVG-T07", "title": "Current candle still forming cannot create an FVG", "rule": "SPEC S-6/S-7 — the forming bar (beyond decision_bar) is not closed; a candidate C3 beyond t is NOT observable", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 7, "patches": [[4, [100.0, 100.2, 99.9, 100.1]], [5, [100.3, 100.9, 100.25, 100.8]], [6, [100.5, 101.0, 100.4, 100.9]]] }, "decision_bar": 5, "expected": { "zones": [], "zone_count": 0, "newest_unmitigated": null, "code_newest_expected": null, "note": "bar 6 (forming, Low 100.4 > High 4 = 100.2) would form an FVG but is beyond decision_bar 5 -> not observable (S-6)." } }, { "id": "FVG-T08", "title": "FVG becomes valid only after the third candle closes", "rule": "SPEC S-6 — C3 must be closed (b <= t); at t=3 the C3=4 formation is not yet valid", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 5, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]]] }, "decision_bar": 3, "expected": { "zones": [], "zone_count": 0, "newest_unmitigated": null, "code_newest_expected": null, "note": "pair with FVG-T01: at t=3 (C3=4 not closed) NO FVG; at t>=4 the FVG exists." } }, { "id": "FVG-T09", "title": "Partial mitigation is NOT mitigation", "rule": "SPEC S-9 — price entering the zone (Low inside [bot,top]) but not reaching bot -> UNMITIGATED", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 7, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]], [5, [100.6, 100.8, 100.45, 100.7]], [6, [100.1, 100.45, 100.3, 100.2]]] }, "decision_bar": 6, "expected": { "zones": [[4, 1, 100.4, 100.2, 0]], "zone_count": 1, "newest_unmitigated": [4, 1, 100.4, 100.2], "code_newest_expected": [1, 100.4, 100.2], "note": "bar 6 Low 100.3 is INSIDE the zone [100.2, 100.4] (partial fill) -> NOT mitigated (S-9)." } }, { "id": "FVG-T10", "title": "Full mitigation (wick reaches zone bottom)", "rule": "SPEC S-9 — subsequent Low <= bot -> MITIGATED; canonical consumer query returns null", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 7, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]], [5, [100.6, 100.8, 100.45, 100.7]], [6, [100.1, 100.45, 100.1, 100.2]]] }, "decision_bar": 6, "expected": { "zones": [[4, 1, 100.4, 100.2, 1]], "zone_count": 1, "newest_unmitigated": null, "code_newest_expected": [1, 100.4, 100.2], "note": "bar 6 Low 100.1 <= bot 100.2 -> full fill by wick -> mitigated. Engine-2 code STILL returns it (BUG-P3S4-001): code [1,...] vs spec null." } }, { "id": "FVG-T11", "title": "Invalidation is NOT modeled (close-through + opposite displacement)", "rule": "SPEC S-10 — a close-through / opposite move only MITIGATES the zone; there is no separate INVALIDATED state", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 8, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]], [5, [100.6, 100.8, 100.45, 100.7]], [6, [100.55, 100.7, 100.5, 100.6]], [7, [100.2, 100.55, 99.9, 100.0]]] }, "decision_bar": 7, "expected": { "zones": [[4, 1, 100.4, 100.2, 1]], "zone_count": 1, "newest_unmitigated": null, "code_newest_expected": [1, 100.4, 100.2], "note": "bar 7 closes through the zone (close 100.0 < bot) with an opposite move -> zone is MITIGATED only; no invalidation state exists (S-10)." } }, { "id": "FVG-T12", "title": "Overlapping FVGs (4-bar move, 3 candidate C3 bars)", "rule": "SPEC S-11 — every qualifying bar produces its OWN zone; zones are NOT merged", "bar_spec": { "neutral": [100.0, 100.3, 99.9, 100.2], "count": 6, "patches": [[1, [100.1, 100.5, 100.0, 100.4]], [2, [100.3, 100.6, 100.2, 100.5]], [3, [100.6, 100.9, 100.55, 100.8]], [4, [100.7, 101.0, 100.65, 100.9]], [5, [100.9, 101.2, 100.95, 101.1]]] }, "decision_bar": 5, "expected": { "zones": [[3, 1, 100.55, 100.5, 0], [4, 1, 100.65, 100.6, 0], [5, 1, 100.95, 100.9, 0]], "zone_count": 3, "newest_unmitigated": [5, 1, 100.95, 100.9], "code_newest_expected": [1, 100.65, 100.6], "note": "3 separate zones (b=3,4,5). Engine-2 code returns the bar-4 zone (C3 = decision_bar-1 = 4, 1-bar lag): code [1,100.65,100.6] vs spec newest [5,1,100.95,100.9] (BUG-P3S4-002)." } }, { "id": "FVG-T13", "title": "Multiple consecutive FVGs (separated formations)", "rule": "SPEC S-11 — separate formations produce separate zones; no merge/replace", "bar_spec": { "neutral": [100.0, 100.3, 99.9, 100.2], "count": 7, "patches": [[1, [100.1, 100.5, 100.0, 100.4]], [2, [100.3, 100.6, 100.2, 100.5]], [3, [100.6, 100.9, 100.55, 100.8]], [4, [100.7, 100.9, 100.6, 100.8]], [5, [100.7, 101.0, 100.65, 100.9]], [6, [101.0, 101.3, 100.95, 101.2]]] }, "decision_bar": 6, "expected": { "zones": [[3, 1, 100.55, 100.5, 0], [6, 1, 100.95, 100.9, 0]], "zone_count": 2, "newest_unmitigated": [6, 1, 100.95, 100.9], "code_newest_expected": [1, 100.55, 100.5], "note": "zones at b=3 and b=6 coexist. Engine-2 code (lag + scan) returns the OLDER b=3 zone: code [1,100.55,100.5] vs spec newest [6,...] (BUG-P3S4-002)." } }, { "id": "FVG-T14", "title": "Persistent zone is ONE FVG, not many events", "rule": "SPEC S-8 — a zone untraded for 20 bars is ONE zone; no repeated events", "bar_spec": { "neutral": [100.6, 100.9, 100.5, 100.8], "count": 25, "patches": [[0, [100.6, 100.9, 100.2, 100.8]], [1, [100.6, 100.9, 100.2, 100.8]], [2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]]] }, "decision_bar": 24, "expected": { "zones": [[4, 1, 100.4, 100.2, 0]], "zone_count": 1, "newest_unmitigated": [4, 1, 100.4, 100.2], "code_newest_expected": [1, 100.4, 100.2], "note": "20 bars after formation the zone is STILL ONE zone (zone_count=1), unmitigated (no low <= 100.2), no age expiry (S-8/S-10). The f7/f9/f18 duplication pattern does NOT apply to FVG." } }, { "id": "FVG-T15", "title": "Future-bar mutation must not change the historical FVG decision", "rule": "SPEC S-7 — property test: mutating bars beyond decision_bar leaves zones unchanged", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]], [5, [100.6, 100.8, 100.45, 100.7]]] }, "decision_bar": 5, "mutations": [[20, [200.0, 210.0, 199.0, 205.0]]], "expected": { "zones": [[4, 1, 100.4, 100.2, 0]], "zone_count": 1, "newest_unmitigated": [4, 1, 100.4, 100.2], "code_newest_expected": [1, 100.4, 100.2] } }, { "id": "FVG-T16", "title": "Bullish/bearish symmetry", "rule": "SPEC S-4 — mirror of FVG-T01: symmetric bearish zone; runner also verifies mirror property", "bar_spec": { "neutral": [100.0, 100.2, 99.5, 99.7], "count": 6, "patches": [[2, [100.0, 100.1, 99.8, 99.9]], [3, [99.7, 99.8, 99.2, 99.4]], [4, [99.4, 99.6, 99.0, 99.3]], [5, [99.3, 99.5, 99.0, 99.4]]] }, "decision_bar": 5, "expected": { "zones": [[4, -1, 99.8, 99.6, 0]], "zone_count": 1, "newest_unmitigated": [4, -1, 99.8, 99.6], "code_newest_expected": [-1, 99.8, 99.6] } }, { "id": "FVG-T17", "title": "Insufficient history (< 3 bars)", "rule": "SPEC S-1/S-6 — fewer than 3 closed bars -> NO FVG possible", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 2, "patches": [] }, "decision_bar": 1, "expected": { "zones": [], "zone_count": 0, "newest_unmitigated": null, "code_newest_expected": null } }, { "id": "FVG-T18", "title": "Timeframe boundary (TF-agnostic geometry)", "rule": "SPEC S-12 — the FVG geometry is TF-agnostic; the same closed sequence yields the same zone on any TF; canonical project FVG TF = AMBIGUOUS (A-3)", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.3, 100.9, 100.25, 100.8]], [4, [100.5, 101.0, 100.4, 100.9]], [5, [100.6, 100.8, 100.45, 100.7]]] }, "decision_bar": 5, "tf_agnostic": true, "expected": { "zones": [[4, 1, 100.4, 100.2, 0]], "zone_count": 1, "newest_unmitigated": [4, 1, 100.4, 100.2], "code_newest_expected": [1, 100.4, 100.2], "note": "same data on M15 vs H4 -> identical zone (per-TF independence, Engine 2). Which TF is the canonical entry FVG = SPECIFICATION AMBIGUOUS (A-3)." } }, { "id": "FVG-T19", "title": "Displacement is NOT required (small-body gap candle)", "rule": "SPEC S-5 — a geometric gap alone is sufficient; the middle candle C2 may have a tiny body", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [[2, [100.0, 100.2, 99.9, 100.1]], [3, [100.25, 100.3, 100.2, 100.25]], [4, [100.5, 101.0, 100.4, 100.9]], [5, [100.35, 100.6, 100.3, 100.5]]] }, "decision_bar": 5, "expected": { "zones": [[4, 1, 100.4, 100.2, 0]], "zone_count": 1, "newest_unmitigated": [4, 1, 100.4, 100.2], "code_newest_expected": [1, 100.4, 100.2], "note": "C2 body = 0.05 (weak displacement) -> FVG still valid (DISPLACEMENT = NOT REQUIRED BY CURRENT SPEC)." } }, { "id": "FVG-T20", "title": "Runtime/training parity: N/A by absence", "rule": "SPEC S-13 — FVG is NOT a Feature Contract feature; no training-side FVG exists; the runner scans FEATURE_CONTRACT.md + ml/**/*.py for FVG references and expects 0", "bar_spec": { "neutral": [100.0, 100.5, 99.8, 100.3], "count": 6, "patches": [] }, "decision_bar": 5, "parity_na": true, "expected": { "zones": [], "zone_count": 0, "newest_unmitigated": null, "code_newest_expected": null, "note": "structural: FVG absent from f0-f18 (FEATURE_CONTRACT), EA runtime, and Python training -> no parity target (documented, not a mismatch)." } } ] }