diff --git a/CLAUDE.md b/CLAUDE.md index 8fcefec..be24af4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,7 @@ cp config.json.example config.json # then fill i venv/bin/pip install cupy-cuda12x "nvidia-cuda-runtime-cu12==12.9.*" # optional, dev box: backtester GPU backend # (CuPy from pip needs that header wheel to compile kernels) venv/bin/python test_all.py # QA suite, no network; prints its own count - # (552 tests / 36 modules as of 2026-09-18) + # (558 tests / 37 modules as of 2026-09-26) venv/bin/python main.py --pair XAUUSD --timeframe H1 --detail # one signal report in the terminal # --json full dump, --panel adds the advisory LLM panel, # --timeframe M5 | M15 | H1 | H4 @@ -133,7 +133,8 @@ against the blocked hours and allowed weekdays; the live wrapper and the backtes list to the bar-close time as well, see section 8); transitioning-regime boost (a B/C/D can become A or B when a trend is emerging); exhausted-trend cap (in a trending regime with two or more exhaustion tags, a C or D is pinned to D and loses its setup); SELL quality gate -(shorts need a grade in `sell_min_quality`; H1's list is empty, so every H1 short is blocked); +(shorts need a grade in `sell_min_quality`; H1's list is empty, so every H1 short is blocked; M15's +list is `A+, B`, so every M15 grade-A short is blocked since 2026-09-26); multi-timeframe opposition gate; SELL volatility gate (shorts are allowed only when volatility is high or extreme; low- or normal-volatility shorts are blocked); regime blocks (M15 blocks the mixed `trending` regime; M5 blocks shorts in `transitioning`); @@ -144,8 +145,13 @@ that names itself. **9. Trade setup.** Grades A+ and A get the gold ATR setup (`_generate_xau_atr_trade_setup`): stop 2 × ATR; target 1 at 2 × ATR for A+ and 1.5 × ATR for A (1 × ATR for an A carrying two or more -exhaustion tags); target 2 at 1.5 × the target-1 distance. Other tradeable grades get the generic -swing-based setup, which floors the stop at $8 (80 pips). Both are checked against the spread gate: +exhaustion tags); target 2 at 1.5 × the target-1 distance. Other tradeable grades get the generic swing-based setup, +which floors the stop at $8 (80 pips). Since 2026-09-26 **every generator prints target 1 at no less +than 1R** (`signals.tp_print_floor_rr`, effective floor `max(tp_floor_rr, tp_print_floor_rr)`), +applied **after** the min-RR and spread gates have judged the generator's own target, so which +generator serves a signal, the stop and the books are unchanged; only the exported `tp1` / +`rr_planned` columns move (a floor applied before the gate cost −24.6R on the M15 two-year book, +see the 2026-09-26 report). The planned ratio is a guide: the tested exit is the trailing stop. Both are checked against the spread gate: target 1 must be at least 30 × the round-trip cost ($9 at the $0.30 cost setting). Two extra entry paths run after the gates: the M5 pullback (`XAU_PB`, shipped off) and the chart-pattern entry (`XAU_CP`, `chart_patterns.py`: M5 only, buy-side patterns inverse head and shoulders, double bottom, bull @@ -163,8 +169,13 @@ later generator shipped a setup. **10. Delivery.** The `SignalReport` goes to the surface that asked for it: - *Telegram* (`telegram_bot.py`): an alert is sent only when the signal is not NEUTRAL, the grade - reaches `alert_min_quality`, **and** a setup exists. A fingerprint (signal + direction) stops - repeats; a grade upgrade re-alerts. The fingerprint is committed only after a successful send, + reaches `alert_min_quality`, **and** a setup exists. Since 2026-09-26 no second alert goes out on a + timeframe within 5 bars of the last alerted setup's bar (`_ALERT_COOLDOWN_BARS`, the backtester's + entry cooldown; consecutive-bar re-entries were −25.1R of the −37.8R live book of 2026-09-04 → + 09-25). After that, a fingerprint (signal + direction) stops repeats; a grade upgrade re-alerts. + The setup block prints the trailing rule first as the tested exit, TP1/TP2 as guides with their R, + the "Planned TP1 RR", and one line of validated-book statistics for the setup class + (`_book_rate_line`, from `agent_panel_base_rates.json`). The fingerprint is committed only after a successful send, so a failed send stays eligible next scan. Every send is logged with the signal-log row id. The level-breach monitor reuses the bar's cached M15 report (`_cached_or_scan`) instead of rescanning. - *CLI* (`main.py`): prints the report; `--json` dumps everything. @@ -370,7 +381,7 @@ day, 30 kept), `logs/dashboard.log`. Read the databases with `mode=ro`, never `i ## 6. Testing - `venv/bin/python test_all.py` runs everything with mocked network. Expect - `RESULTS: 552 passed, 0 failed, 552 total` (2026-09-18). On a box with a visible CUDA device + `RESULTS: 558 passed, 0 failed, 558 total` (2026-09-26). On a box with a visible CUDA device Module 36 fails if the GPU backend falls back (the reason is in the message); with `FB2_BACKEND=cpu` or no CUDA the GPU-path tests print a notice and the NumPy twins are tested. Module 37 (the CUDA decision stage) adds about 85 s on the GPU because its references replay @@ -442,13 +453,14 @@ overrides (each one has its validation record in the `pair_signals/xauusd.py` do | Timeframe | Confirmations | Overrides on top of the defaults | |---|---|---| | M5 | M15, H1 | hour 9 UTC blocked and hour 21 unblocked; chart-pattern entries on (buy-side only: inverse head and shoulders, double bottom, bull flag); pullback entries off; partial EMA alignment scores neutral; trailing stop arms at 0.60R and trails 0.85R; shorts blocked in the `transitioning` regime | -| M15 | M5, H1 | target floor 1R; the mixed `trending` regime blocked; shorts blocked at 15:00 and 17:00 UTC; trailing stop arms at 0.70R and trails 0.75R; RSI weight halved in every regime | +| M15 | M5, H1 | target floor 1R; the mixed `trending` regime blocked; shorts blocked at 15:00 and 17:00 UTC; trailing stop arms at 0.70R and trails 0.75R; RSI weight halved in every regime; grade-A shorts blocked (`sell_min_quality` `A+, B`) | | H1 | M15, H4 | every short blocked (`sell_min_quality` empty) | | H4 | H1, D1 | none. H4 remains "unproven" (few trades) | Shared defaults worth knowing: blocked hours 0, 1, 2, 10, 11, 13, 16, 21 UTC; trading days Monday to Friday plus Sunday evening; shorts need grade A+, A or B and high-or-extreme volatility; -trailing stop arms at 0.6R and trails 0.75R; target floor 0.5R; round-trip cost $0.30 with +trailing stop arms at 0.6R and trails 0.75R; gated target floor 0.5R and printed target floor 1R; +round-trip cost $0.30 with target 1 at least 30 × cost; the generic and chart-pattern setups floor the stop at $8; the gold ATR setup uses a 1 × ATR target before the grade scale (2× at A+, 1.5× at A) and a 2 × ATR stop; the H4 regime filter is off; news never gates. @@ -475,11 +487,18 @@ CuPy GPU backend and `--backend cpu` both reproduce every MD5 below; earlier rep | Book | Window | Trades / R / PF | MD5 of the CSV | |---|---|---|---| -| M5 W2 | 2024-09-05 → 2026-09-05 | 781 / +146.07 / 1.52 | `49f8e602fc6ae769713c94ef7b257810` | -| M15 W2 | 2024-09-05 → 2026-09-05 | 931 / +146.14 / 1.43 | `b4aa07e33e17e23774c486a6900d673b` | -| M15 OOS | 2024-06-01 → 2025-03-30 | 355 / +60.67 / 1.49 | `9fd624fb387c36052122bd1830cf9c32` | -| H1 W3 | 2023-09-05 → 2026-09-05 | 253 / +70.0 / 1.98 | `7b848c917dcb5c371ecbb9f8f7292278` | -| H4 W3 | 2023-09-05 → 2026-09-05 | 114 / +21.7 / 1.59 | `186e677fbbd53cca735c94094c23ce05` | +| M5 W2 | 2024-09-05 → 2026-09-05 | 781 / +146.07 / 1.52 | `e599c8a8652f95b7fcdf5a7ef2b564f5` | +| M15 W2 | 2024-09-05 → 2026-09-05 | 813 / +154.15 / 1.53 | `5b65ecf6fad9a8da412749066fd50d36` | +| M15 OOS | 2024-06-01 → 2025-03-30 | 313 / +68.50 / 1.67 | `09b8a8e7f5833436c20aef60ca0a7ff7` | +| H1 W3 | 2023-09-05 → 2026-09-05 | 253 / +70.0 / 1.98 | `a44fd19b4067f46e5bd98d50cc3eff92` | +| H4 W3 | 2023-09-05 → 2026-09-05 | 114 / +21.7 / 1.59 | `f3587bae3c5dc3eda468ea3588b17dcb` | + +Re-pinned 2026-09-26. M15: the grade-A SELL block (M15 override 6, 162 trades removed, +8.0R); +previous pins `b4aa07e3…` (931 / +146.14 / 1.43) and `9fd624fb…` (355 / +60.67 / 1.49); M15 W3 +(3-year) with the block 1105 / +195.79 / 1.49, `70195fb0e6865b3a46ee8a9aa8844e51`. All four +timeframes: the 1R printed-target floor, **label-only** (M5 W2: 544 rows change `tp1` / `rr_planned`, +H1 W3 252, H4 W3 108, M15 W2 80; trades and R identical to `49f8e602…`, `7b848c91…`, `186e677f…` +and to the M15 block-only book `497520ef…`; every `rr_planned` is now ≥ 1.0). Re-pinned 2026-09-17 for a **label-only** change: the exported `strategy` column now carries the generator's own stamp (`XAU_ATR` / `XAU_PB` / `XAU_CP` / `GENERIC`) instead of a tag inferred @@ -543,6 +562,8 @@ MD5s. Scorecards from it are in-book statistics, never decisions. market-outlook feature. Nothing reads it. - The news score is a trailing echo of the last day's price move (r ≈ 0.67 with the past day, ≈ 0.03 with the next). Both news gating flags stay off. +- The level-breach rescan and the candle loop both skip the settlement-pause window + (`_closed_bar_in_settlement`); a rescan there only re-reads the last pre-pause bar. - The agent panel never runs inside `analyze_from_dataframes`, and its cached verdict is never reused when the evidence changed; the daily cap is the real spend bound. - Live == backtest also means the backtester never writes to `signals.db` and never builds a @@ -577,6 +598,12 @@ MD5s. Scorecards from it are in-book statistics, never decisions. `count * 2`: 15/50·100 = 30.000000000000004 flips "low" to "normal"). A cleaner formulation flips a threshold somewhere in 500k bars. The as-of alignment compares int64 nanoseconds; a mixed-unit index once returned slice length 0 for every confirmation frame. +- A printed-target change is not free: the spread gate judges the target, so a bigger target can + switch a signal from the GENERIC swing stop to the XAU_ATR 2 × ATR stop. The 1R printed floor + (`tp_print_floor_rr`) sits after the gates in all three generators for that reason; keep it there, + and never feed the printed target back into a gate. +- "Stop hunting" was tested on the live log (2026-09-26): 71% of live stop-outs ran a full stop + distance or more past the stop, the same as the books' 70–72%. The losers were real moves. - The dashboard's due-timeframe check uses the raw wake time; flooring `wake − buffer` skipped every H1 and H4 scan once (2026-08-02). - The agent panel's price context reads the frames `analyze_pair` stashes on `report._frames`. @@ -597,6 +624,7 @@ One line per validated change or verdict, newest first. Full evidence in the nam | Date | Decision | Where the evidence is | |---|---|---| +| 2026-09-26 | Quant review + live log 2026-09-04 → 09-25 (100 setups replayed on the scans' stored candles): stop-hunt claim REFUTED (71% of stop-outs ran ≥ 1R past the stop, same as the books); −25.1R of the −37.8R raw live book were consecutive-bar re-entries the backtester's cooldown never takes; the cooldown view sits at the 3rd–5th percentile of the books' three-week windows. SHIPPED: 5-bar alert cooldown; alert wording (trail first as the tested exit, TP1 a guide with its R, planned RR, validated-book line per class); M15 grade-A SELL block (`sell_min_quality` `A+, B`, PASSED a–f: W2 +154.15R/813 PF 1.53 vs +146.14/931 PF 1.43, OOS +68.5 vs +60.67, W3 DD +8%); 1R printed-target floor in every generator after the gates (`tp_print_floor_rr`, owner rule "TP1 at least 1:1"; label-only re-pin of all four timeframes). FAILED: M15 `min_rr_ratio` 1.0 (trade-identical, −2.0R); a pre-gate target floor (−24.6R). OWNER'S CALL: M5 `tp_floor_rr` 1.0 adds +48.9R on W2 with both halves up but max DD 10.1 → 15.9R (ladder 0.6–1.0 non-monotone); not shipped. Telegram / dashboard AI block rewritten for a new trader (owner rule): one plain headline against the usual odds, the chance "in 100", one reason for and one against, no verdict word or confidence figure; persona prompts ban trading jargon (`PANEL_PROMPT_VERSION` v2.1). Two delivery bugs from the DB checks fixed: the macro-source panel verdict never stored its probability (all v2 rows NULL); the level-breach rescan re-logged the 20:45 M15 bar three times every evening after the settlement pause (guard added to `_handle_breach`). Indicator grid and volume / daily-direction screens (Part C) not run | `backtest/research/rr_and_sell_campaign_2026-09-26.md` | | 2026-09-18 | Full-GPU research twin SHIPPED: `backtest/cudaBasedBacktesting.py` + `backtest/cuda_signal_kernels.py` (owner decisions: research only, `gpu_backtest.py` stays the base reference, every research flag supported, the one exemption from instructions item 6 recorded there). Proof: the five reference MD5s, four 6-month acceptance books (2026-03-18 → 2026-09-18, M5 and M15, $0.65 and $0.30), the M15 OOS `--export-features` CSV and `--record --record-bars` tables, and ten research-flag variants, all byte-identical to the reference script; Module 37 (16 tests) pins every stage against the live code. Books take 0.8–2.4 s instead of 13–115 s. Rule added: any signal change is a parity event for the twin | `backtest/research/cuda_backtester_2026-09-18.md` | | 2026-09-18 | GPU backend made real: CuPy had never compiled a kernel on the dev box (`CUDA_PATH` missing for the pip layout), so every book to date was NumPy. SHIPPED: `backtest/gpu_kernels.py` (lazy, loud backend probe; `nvidia-cuda-runtime-cu12` header wheel; CUDA kernels for the EWM batch, the pandas rolling mean/var replica and the trade resolver, each a bit-for-bit twin of the CPU code), `--backend auto/gpu/cpu` + `FB2_BACKEND`, Module 36. All five reference MD5s reproduced on the GPU and with `--backend cpu`; precompute GPU == CPU on all 42 cached windows (2.34 M bars). Wall time roughly unchanged: the replay is CPU by design | `backtest/research/gpu_backend_2026-09-18.md` | | 2026-09-17 | Local news scorer evaluated (the owner's scikit TF-IDF model in `~/Development/ML-NEWS`, trained on 606 DeepSeek gold labels): golden set 39/78 vs DeepSeek 78/78 vs keywords 24/78; out-of-sample agreement with DeepSeek 64% (DeepSeek agrees with itself 90%); cleaning / negation marking / label weighting move nothing; backfilling the 961 missing gold labels lifts macro-headline agreement 38% → 52% but only reshuffles golden-set errors. Verdict: a fallback, not a replacement. SHIPPED to grow a complete corpus: `xau_score` archived for every headline, no-currency headlines scored and archived (never shown), every new headline sent in its cycle (25 per call). Dashboard process made database-read-only (`set_db_writes_for_process`); the bot is the single writer. Books unaffected (news never enters signals) | `~/Development/ML-NEWS/eval/RESULTS_2026-09-17.md` (evaluation scripts live outside the repo by owner instruction) | diff --git a/agent_panel.py b/agent_panel.py index 4d5d18b..36fbc7c 100644 --- a/agent_panel.py +++ b/agent_panel.py @@ -126,7 +126,7 @@ VERDICT_SOURCES = ("mean", "macro", "chair") #: Logged with every verdict and folded into the payload hash. Bump on #: ANY prompt or context-schema change so the scorecard can split eras #: (analyze_signal_log splits by panel_prompt_version). -PANEL_PROMPT_VERSION = "v2-2026-09-17" +PANEL_PROMPT_VERSION = "v2.1-2026-09-26" # v2.1: beginner wording rules in _TAKE_CONTRACT #: Symmetric probability bands (percent). A persona's stance and the #: mean verdict are DERIVED from p_win with the same thresholds, so @@ -216,7 +216,14 @@ Write everything in plain everyday English for someone who has never traded: short simple sentences, no trading jargon, no acronyms, no abbreviations. Say "profit versus loss", not "RR"; "the 4-hour chart", not "H4"; describe what an indicator MEANS ("the price has climbed -unusually fast"), never its name or number. +unusually fast"), never its name or number. Never use these words: +"priced in", "bullish", "bearish", "hawkish", "dovish", "macro", +"sentiment", "momentum", "volatility", "resistance", "support level", +"breakout", "pullback", "basis points", "yields". Say instead: "the +price already moved on that news", "news that is good for gold", +"news that is bad for gold", "higher interest rates", "the price +fell fast", "the price bounced". Each sentence must make sense to a +reader who does not know what gold or interest rates usually do. Reply with ONLY JSON: {"p_win": <0-100>, "reason_for": "", "reason_against": "", "note": ""} @@ -250,8 +257,8 @@ gold this way"; the payload's gold_influence already did that scoring. Example: brief direction SELL, price already fell 5 ATR in 4 hours, every bearish headline published before move_started_at, base_rate.win_rate 58 -> {"p_win": 55, "reason_for": "Recent news still leans against gold.", -"reason_against": "That news is older than the drop, so it is priced in.", -"note": "The bad news is already in the price; expect the usual odds."} +"reason_against": "That news came before the drop; the price already moved on it.", +"note": "The price already moved on the bad news; expect the usual odds."} Example: brief direction BUY, a high-impact release came out 20 minutes ago with a surprise the code marked bullish for gold, base_rate.win_rate 60 -> {"p_win": 68, "reason_for": "A fresh surprise in the data favours gold.", @@ -1244,11 +1251,19 @@ def run_panel(report: "SignalReport", if verdict_source == "macro": macro = next((t for t in takes if t.persona == "macro"), None) if macro is not None: + # The macro persona's own p_win is the verdict's probability + # (2026-09-26 signals.db review: every v2 row logged under + # verdict_source="macro" had panel_probability NULL — the + # verdict carried none, _write_fields wrote 0.0 and the logger + # dropped the falsy value — so the 100/300-outcome evaluation + # would have had no live probabilities to score). return _finish(PanelVerdict( verdict=_STANCE_TO_VERDICT[macro.stance], confidence=macro.confidence, rationale=macro.note[:240], takes=takes, created_ts=time.time(), - payload_hash=payload_hash, source="macro")) + payload_hash=payload_hash, source="macro", + probability=(round(float(macro.p_win), 1) + if macro.p_win is not None else None))) # Macro take missing (timed out / invalid) — fall back to the chair. # Quorum guards the CHAIR only: it weighs several voices, so one lone # take is not worth a synthesizer request. The macro path above needs diff --git a/agent_panel_base_rates.json b/agent_panel_base_rates.json index 3dc889c..96e25c6 100644 --- a/agent_panel_base_rates.json +++ b/agent_panel_base_rates.json @@ -1,33 +1,33 @@ { "schema": "agent-panel-base-rates-v1", - "generated_at": "2026-09-17T15:26:15+00:00", + "generated_at": "2026-09-27T00:45:18+00:00", "key": "timeframe|strategy|grade|direction ('*' = any)", "min_n_for_exact_class": 30, "books": { "5min": { "file": "m5_w2.csv", - "md5": "49f8e602fc6ae769713c94ef7b257810", + "md5": "e599c8a8652f95b7fcdf5a7ef2b564f5", "trades": 781, "first_entry": "2024-09-06 18:00:00+00:00", "last_entry": "2026-09-04 15:15:00+00:00" }, "15min": { "file": "m15_w2.csv", - "md5": "b4aa07e33e17e23774c486a6900d673b", - "trades": 931, + "md5": "5b65ecf6fad9a8da412749066fd50d36", + "trades": 813, "first_entry": "2024-09-05 06:15:00+00:00", - "last_entry": "2026-09-04 12:45:00+00:00" + "last_entry": "2026-09-04 08:00:00+00:00" }, "1h": { "file": "h1_w3.csv", - "md5": "7b848c917dcb5c371ecbb9f8f7292278", + "md5": "a44fd19b4067f46e5bd98d50cc3eff92", "trades": 253, "first_entry": "2023-09-20 18:00:00+00:00", "last_entry": "2026-08-21 07:00:00+00:00" }, "4h": { "file": "h4_w3.csv", - "md5": "186e677fbbd53cca735c94094c23ce05", + "md5": "f3587bae3c5dc3eda468ea3588b17dcb", "trades": 114, "first_entry": "2023-10-02 08:00:00+00:00", "last_entry": "2026-09-01 12:00:00+00:00" @@ -35,27 +35,27 @@ }, "classes": { "15min|*|*|*": { - "n": 931, - "win_rate": 60.47, - "mean_r": 0.157, - "tp1_reach_rate": 52.31, - "median_bars_held": 30, + "n": 813, + "win_rate": 62.24, + "mean_r": 0.1896, + "tp1_reach_rate": 50.43, + "median_bars_held": 32, "book": "m15_w2.csv" }, "15min|*|*|BUY": { "n": 635, "win_rate": 63.31, "mean_r": 0.2309, - "tp1_reach_rate": 53.54, + "tp1_reach_rate": 51.65, "median_bars_held": 33, "book": "m15_w2.csv" }, "15min|*|*|SELL": { - "n": 296, - "win_rate": 54.39, - "mean_r": -0.0016, - "tp1_reach_rate": 49.66, - "median_bars_held": 20, + "n": 178, + "win_rate": 58.43, + "mean_r": 0.0424, + "tp1_reach_rate": 46.07, + "median_bars_held": 28, "book": "m15_w2.csv" }, "15min|GENERIC|A+|BUY": { @@ -82,14 +82,6 @@ "median_bars_held": 44, "book": "m15_w2.csv" }, - "15min|GENERIC|A|SELL": { - "n": 84, - "win_rate": 52.38, - "mean_r": -0.0288, - "tp1_reach_rate": 42.86, - "median_bars_held": 43, - "book": "m15_w2.csv" - }, "15min|GENERIC|B|BUY": { "n": 282, "win_rate": 62.41, @@ -99,11 +91,11 @@ "book": "m15_w2.csv" }, "15min|GENERIC|B|SELL": { - "n": 120, - "win_rate": 57.5, - "mean_r": 0.0242, - "tp1_reach_rate": 45.0, - "median_bars_held": 27, + "n": 161, + "win_rate": 58.39, + "mean_r": 0.0273, + "tp1_reach_rate": 45.34, + "median_bars_held": 32, "book": "m15_w2.csv" }, "15min|GENERIC|C|BUY": { @@ -123,10 +115,10 @@ "book": "m15_w2.csv" }, "15min|XAU_ATR|A+|SELL": { - "n": 13, - "win_rate": 69.23, - "mean_r": 0.4437, - "tp1_reach_rate": 61.54, + "n": 16, + "win_rate": 62.5, + "mean_r": 0.2612, + "tp1_reach_rate": 56.25, "median_bars_held": 5, "book": "m15_w2.csv" }, @@ -134,23 +126,15 @@ "n": 80, "win_rate": 56.25, "mean_r": 0.06, - "tp1_reach_rate": 62.5, + "tp1_reach_rate": 47.5, "median_bars_held": 10, "book": "m15_w2.csv" }, - "15min|XAU_ATR|A|SELL": { - "n": 78, - "win_rate": 50.0, - "mean_r": -0.073, - "tp1_reach_rate": 62.82, - "median_bars_held": 6, - "book": "m15_w2.csv" - }, "1h|*|*|*": { "n": 253, "win_rate": 64.43, "mean_r": 0.2768, - "tp1_reach_rate": 74.31, + "tp1_reach_rate": 54.15, "median_bars_held": 29, "book": "h1_w3.csv" }, @@ -158,7 +142,7 @@ "n": 253, "win_rate": 64.43, "mean_r": 0.2768, - "tp1_reach_rate": 74.31, + "tp1_reach_rate": 54.15, "median_bars_held": 29, "book": "h1_w3.csv" }, @@ -166,7 +150,7 @@ "n": 1, "win_rate": 0.0, "mean_r": -0.1689, - "tp1_reach_rate": 100.0, + "tp1_reach_rate": 0.0, "median_bars_held": 45, "book": "h1_w3.csv" }, @@ -174,7 +158,7 @@ "n": 57, "win_rate": 66.67, "mean_r": 0.2243, - "tp1_reach_rate": 75.44, + "tp1_reach_rate": 52.63, "median_bars_held": 45, "book": "h1_w3.csv" }, @@ -182,7 +166,7 @@ "n": 112, "win_rate": 63.39, "mean_r": 0.2432, - "tp1_reach_rate": 74.11, + "tp1_reach_rate": 51.79, "median_bars_held": 38, "book": "h1_w3.csv" }, @@ -190,7 +174,7 @@ "n": 26, "win_rate": 73.08, "mean_r": 0.6826, - "tp1_reach_rate": 80.77, + "tp1_reach_rate": 73.08, "median_bars_held": 29, "book": "h1_w3.csv" }, @@ -206,7 +190,7 @@ "n": 56, "win_rate": 62.5, "mean_r": 0.224, - "tp1_reach_rate": 71.43, + "tp1_reach_rate": 53.57, "median_bars_held": 8, "book": "h1_w3.csv" }, @@ -214,7 +198,7 @@ "n": 114, "win_rate": 57.89, "mean_r": 0.1902, - "tp1_reach_rate": 67.54, + "tp1_reach_rate": 48.25, "median_bars_held": 12, "book": "h4_w3.csv" }, @@ -222,7 +206,7 @@ "n": 94, "win_rate": 61.7, "mean_r": 0.2409, - "tp1_reach_rate": 70.21, + "tp1_reach_rate": 52.13, "median_bars_held": 9, "book": "h4_w3.csv" }, @@ -230,7 +214,7 @@ "n": 20, "win_rate": 40.0, "mean_r": -0.0484, - "tp1_reach_rate": 55.0, + "tp1_reach_rate": 30.0, "median_bars_held": 20, "book": "h4_w3.csv" }, @@ -246,7 +230,7 @@ "n": 1, "win_rate": 0.0, "mean_r": -0.2277, - "tp1_reach_rate": 100.0, + "tp1_reach_rate": 0.0, "median_bars_held": 29, "book": "h4_w3.csv" }, @@ -254,7 +238,7 @@ "n": 39, "win_rate": 79.49, "mean_r": 0.4706, - "tp1_reach_rate": 82.05, + "tp1_reach_rate": 64.1, "median_bars_held": 27, "book": "h4_w3.csv" }, @@ -262,7 +246,7 @@ "n": 12, "win_rate": 25.0, "mean_r": -0.4106, - "tp1_reach_rate": 41.67, + "tp1_reach_rate": 16.67, "median_bars_held": 40, "book": "h4_w3.csv" }, @@ -270,7 +254,7 @@ "n": 5, "win_rate": 40.0, "mean_r": -0.3577, - "tp1_reach_rate": 60.0, + "tp1_reach_rate": 20.0, "median_bars_held": 8, "book": "h4_w3.csv" }, @@ -294,7 +278,7 @@ "n": 42, "win_rate": 47.62, "mean_r": 0.0547, - "tp1_reach_rate": 61.9, + "tp1_reach_rate": 42.86, "median_bars_held": 7, "book": "h4_w3.csv" }, @@ -302,7 +286,7 @@ "n": 5, "win_rate": 60.0, "mean_r": 0.5618, - "tp1_reach_rate": 80.0, + "tp1_reach_rate": 60.0, "median_bars_held": 14, "book": "h4_w3.csv" }, @@ -310,7 +294,7 @@ "n": 781, "win_rate": 52.88, "mean_r": 0.187, - "tp1_reach_rate": 56.34, + "tp1_reach_rate": 41.23, "median_bars_held": 34, "book": "m5_w2.csv" }, @@ -318,7 +302,7 @@ "n": 683, "win_rate": 53.15, "mean_r": 0.201, - "tp1_reach_rate": 54.61, + "tp1_reach_rate": 41.0, "median_bars_held": 33, "book": "m5_w2.csv" }, @@ -326,7 +310,7 @@ "n": 98, "win_rate": 51.02, "mean_r": 0.0894, - "tp1_reach_rate": 68.37, + "tp1_reach_rate": 42.86, "median_bars_held": 44, "book": "m5_w2.csv" }, @@ -334,7 +318,7 @@ "n": 139, "win_rate": 61.15, "mean_r": 0.2662, - "tp1_reach_rate": 74.82, + "tp1_reach_rate": 53.96, "median_bars_held": 77, "book": "m5_w2.csv" }, @@ -342,7 +326,7 @@ "n": 153, "win_rate": 50.98, "mean_r": 0.2315, - "tp1_reach_rate": 72.55, + "tp1_reach_rate": 42.48, "median_bars_held": 61, "book": "m5_w2.csv" }, @@ -350,7 +334,7 @@ "n": 98, "win_rate": 51.02, "mean_r": 0.0894, - "tp1_reach_rate": 68.37, + "tp1_reach_rate": 42.86, "median_bars_held": 44, "book": "m5_w2.csv" }, @@ -358,7 +342,7 @@ "n": 21, "win_rate": 47.62, "mean_r": -0.0144, - "tp1_reach_rate": 57.14, + "tp1_reach_rate": 42.86, "median_bars_held": 35, "book": "m5_w2.csv" }, @@ -374,7 +358,7 @@ "n": 31, "win_rate": 54.84, "mean_r": 0.288, - "tp1_reach_rate": 67.74, + "tp1_reach_rate": 51.61, "median_bars_held": 12, "book": "m5_w2.csv" }, @@ -390,7 +374,7 @@ "n": 321, "win_rate": 51.71, "mean_r": 0.1621, - "tp1_reach_rate": 37.38, + "tp1_reach_rate": 34.27, "median_bars_held": 19, "book": "m5_w2.csv" } diff --git a/backtest/cuda_signal_kernels.py b/backtest/cuda_signal_kernels.py index a2f2f7c..2297603 100644 --- a/backtest/cuda_signal_kernels.py +++ b/backtest/cuda_signal_kernels.py @@ -208,7 +208,7 @@ PARAMS_F = ( "rsi_overbought", "rsi_oversold", "rsi_neutral_upper", "rsi_neutral_lower", "adx_strong_trend", "adx_very_strong", "adx_slope_emerging_threshold", "roc_threshold", "min_rr_ratio", "tp_tightening_factor", "round_trip_cost", "min_tp_cost_multiple", - "min_sl_pips", "tp_floor_rr", + "min_sl_pips", "tp_floor_rr", "tp_print_floor_rr", "tp_atr_multiplier", "sl_atr_multiplier", "min_atr_threshold", "v2_sl_atr_multiplier", "m5_min_abs_score", "m5_min_confirmation_score", "m5_sell_min_rsi", "m5_pullback_min_score", "m5_pullback_adx_min", @@ -332,6 +332,7 @@ def pack_params(platform_cfg, bt_cfg) -> Tuple[np.ndarray, np.ndarray, np.ndarra "min_rr_ratio": s.min_rr_ratio, "tp_tightening_factor": s.tp_tightening_factor, "round_trip_cost": s.round_trip_cost, "min_tp_cost_multiple": s.min_tp_cost_multiple, "min_sl_pips": s.min_sl_pips, "tp_floor_rr": s.tp_floor_rr, + "tp_print_floor_rr": max(s.tp_floor_rr, getattr(s, "tp_print_floor_rr", 0.0)), "tp_atr_multiplier": x.tp_atr_multiplier, "sl_atr_multiplier": x.sl_atr_multiplier, "min_atr_threshold": x.min_atr_threshold, "v2_sl_atr_multiplier": x.v2_sl_atr_multiplier, "m5_min_abs_score": x.m5_min_abs_score, @@ -1804,11 +1805,20 @@ void fb2_aggregate(const double* __restrict__ open_p, const double* __restrict__ tp1 = price + (tp1 - price) * tfac; tp2 = price + (tp2 - price) * tfac; const double sl_distance = fabs(price - sl_); - const double tp_distance = fabs(tp1 - price); - const double rr = (sl_distance > 0.0) ? tp_distance / sl_distance : 0.0; + double tp_distance = fabs(tp1 - price); + double rr = (sl_distance > 0.0) ? tp_distance / sl_distance : 0.0; if (!(rr < min_rr - 1e-9)) { if (tp_distance < min_tp) { WARN(W_spread_gate_atr, tp_distance); } else { + { // printed-target floor AFTER the gates (signal_aggregator 2026-09-26) + const double floor_dist = sl_distance * PF[PF_tp_print_floor_rr]; + if (tp_distance < floor_dist) { + tp_distance = floor_dist; + if (is_buy) { tp1 = price + floor_dist; tp2 = price + floor_dist * 1.5; } + else { tp1 = price - floor_dist; tp2 = price - floor_dist * 1.5; } + rr = tp_distance / sl_distance; + } + } strategy = STRAT_XAU_ATR; direction = is_buy ? 1 : -1; setup_entry = price; setup_sl = fb2_pyround(sl_, 100.0); @@ -1865,11 +1875,20 @@ void fb2_aggregate(const double* __restrict__ open_p, const double* __restrict__ tp1 = entry + (tp1 - entry) * tfac; tp2 = entry + (tp2 - entry) * tfac; if (is_buy) tp2 = PYMAX(tp2, tp1 + atr); else tp2 = PYMIN(tp2, tp1 - atr); - const double tp1_distance = fabs(tp1 - entry); - const double rr = (sl_distance > 0.0) ? tp1_distance / sl_distance : 0.0; + double tp1_distance = fabs(tp1 - entry); + double rr = (sl_distance > 0.0) ? tp1_distance / sl_distance : 0.0; if (!(rr < min_rr - 1e-9)) { if (tp1_distance < min_tp) { WARN(W_spread_gate_generic, tp1_distance); } else { + { // printed-target floor AFTER the gates (signal_aggregator 2026-09-26) + const double floor_dist = sl_distance * PF[PF_tp_print_floor_rr]; + if (tp1_distance < floor_dist) { + tp1_distance = floor_dist; + if (is_buy) { tp1 = entry + floor_dist; tp2 = PYMAX(tp2, tp1 + atr); } + else { tp1 = entry - floor_dist; tp2 = PYMIN(tp2, tp1 - atr); } + rr = tp1_distance / sl_distance; + } + } strategy = STRAT_GENERIC; direction = is_buy ? 1 : -1; setup_entry = entry; setup_sl = sl_price; setup_tp1 = tp1; setup_tp2 = tp2; rr_rounded = fb2_pyround(rr, 100.0); @@ -2005,8 +2024,18 @@ void fb2_aggregate(const double* __restrict__ open_p, const double* __restrict__ sl_dist = PYMAX((hit.anchor + 0.5 * atr) - entry, min_sl_price); sl_ = entry + sl_dist; tp1 = entry - height; tp2 = entry - 1.5 * height; } - const double rr = (sl_dist > 0.0) ? height / sl_dist : 0.0; + double rr = (sl_dist > 0.0) ? height / sl_dist : 0.0; if (!(rr < min_rr - 1e-9) && !(height < min_tp)) { + { // printed-target floor AFTER the gates (signal_aggregator 2026-09-26) + double tp1_dist = height; + const double floor_dist = sl_dist * PF[PF_tp_print_floor_rr]; + if (tp1_dist < floor_dist) { + tp1_dist = floor_dist; + if (is_buy) { tp1 = entry + floor_dist; tp2 = entry + 1.5 * floor_dist; } + else { tp1 = entry - floor_dist; tp2 = entry - 1.5 * floor_dist; } + rr = tp1_dist / sl_dist; + } + } strategy = STRAT_XAU_CP; direction = hit.dir; setup_entry = entry; setup_sl = sl_; setup_tp1 = tp1; setup_tp2 = tp2; rr_rounded = fb2_pyround(rr, 100.0); diff --git a/backtest/research/loss_anatomy_2026-09-18.md b/backtest/research/loss_anatomy_2026-09-18.md new file mode 100644 index 0000000..e9fb096 --- /dev/null +++ b/backtest/research/loss_anatomy_2026-09-18.md @@ -0,0 +1,165 @@ +# Why the losing trades lost — two-year M5 and M15 books, 2026-09-18 + +Owner question: for every losing trade in the two-year books, was it the wrong direction, a late +entry, a target the market reversed before reaching, or a bad hour or day? Descriptive, in-book +statistics; nothing here is a decision (rule 1 needs a control-versus-candidate run). + +## 1. Data + +The validated two-year windows, 2024-09-05 → 2026-09-05, regenerated on the CUDA twin with the +decision-time features attached (`--export-features`), $0.65 round trip, factory configuration: + +``` +venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf 5min --start 2024-09-05 --end 2026-09-05 --spread 0.65 --export-features --export-csv w2_5min_feat.csv +venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf 15min --start 2024-09-05 --end 2026-09-05 --spread 0.65 --export-features --export-csv w2_15min_feat.csv +``` + +`Backend: CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)`, 0 cache fallbacks, 781 and +931 trades, the pinned books (`49f8e602…`, `b4aa07e3…`) with the research columns appended. Every +stretch feature is signed in the trade's direction (positive = price had already moved the +trade's way), so buys and sells read alike. "R" is risk units: profit or loss divided by the +distance from entry to the initial stop. A loser is any trade with negative R. + +| Book | Trades | Losers | Loss rate | Loss R | Win R | Net R | Full-stop losers | Trailing-stop losers | +|---|---|---|---|---|---|---|---|---| +| M5 | 781 | 368 | 47.1% | −278.9 | +424.9 | +146.1 | 247 (−257.8R) | 115 (−19.1R) | +| M15 | 931 | 368 | 39.5% | −341.0 | +487.1 | +146.1 | 324 (−336.4R) | 39 (−2.6R) | + +Median loss −1.02R / −1.03R: a loser is almost always a full stop. The trailing exits' losses are +small by construction. + +## 2. Price path of the losers (what the market did after entry) + +| Path after entry | M5 losers | Share | M5 loss R | M15 losers | Share | M15 loss R | +|---|---|---|---|---|---|---| +| Never went our way (best excursion under 0.25R) | 153 | 42% | −157.9 (57%) | 162 | 44% | −166.9 (49%) | +| Shallow move, then reversal (0.25R to 0.75R) | 165 | 45% | −115.7 (41%) | 185 | 50% | −171.6 (50%) | +| Gave back a real advance (0.75R or more) | 50 | 14% | −5.3 (2%) | 21 | 6% | −2.5 (1%) | + +- **Wrong from the start** is the largest single cause by R: over half the M5 loss and half the + M15 loss comes from trades whose best moment was under a quarter of the risk. Median hold 12 + bars (M5) and 18 bars (M15): they were stopped fast. +- **Reversals after a genuine advance cost almost nothing** (1–2% of loss R): the trailing stop + arms at 0.6R (M5) / 0.7R (M15) and limits the give-back. On M5, 81 losers (22%) did reach the + full target-1 distance (median target-1 is only 0.51R on M5) and still closed red; they cost + −23.8R. On M15 that is 15 losers, −7.1R. +- Losers that reached at least half the target-1 distance: M5 157 (43%, −78.8R), M15 115 (31%, + −80.8R). So roughly a third to two fifths of losers "were winning at some point". + +### Would a nearer hard target have helped? (all trades; exit at X when the best excursion reached X) + +| Hard target | M5 losers rescued | M5 winners capped | M5 net | M15 losers rescued | M15 winners capped | M15 net | +|---|---|---|---|---|---|---| +| 0.50R | 142 (+118.0R) | 267 (−259.2R) | +45.4 | 102 (+118.5R) | 329 (−266.0R) | +58.6 | +| 0.75R | 50 (+42.8R) | 203 (−201.4R) | +72.1 | 21 (+18.2R) | 244 (−194.6R) | +97.5 | +| 1.00R | 3 (+4.8R) | 166 (−154.5R) | +97.3 | 2 (+2.4R) | 180 (−142.1R) | +128.5 | +| 1.50R | 0 | 94 (−91.5R) | +117.2 | 0 | 97 (−74.7R) | +143.5 | +| 2.00R | 0 | 53 (−56.2R) | +132.6 | 0 | 58 (−37.5R) | +152.2 | +| actual (trailing) | | | **+146.1** | | | **+146.1** | + +A nearer target rescues losers but caps far more winners; the book's R lives in the right tail +(top 5% of trades = 94–96% of R, cycle 1). Only a 2R hard target on M15 edges the trailing exit +(+6R), which is the 2026-08-05 fixed-RR finding: it passes on 2024+ windows and is negative in +2021 and 2023 with double the drawdown. Not re-proposed. + +## 3. Late entry: does chasing lose? + +No. Loss rate falls as the entry gets "later"; the pullback-style entries are the worst bucket. + +| Move over the previous 12 bars in the trade's direction | M5 trades | Loss rate | Avg R | M15 trades | Loss rate | Avg R | +|---|---|---|---|---|---|---| +| Against the last 12 bars (pullback entry) | 5 | 80% | −0.39 | 14 | 64% | −0.13 | +| 0 to 1 ATR | 60 | 50% | +0.18 | 43 | 44% | −0.06 | +| 1 to 2 ATR | 157 | 50% | +0.14 | 148 | 38% | +0.23 | +| 2 to 3 ATR | 180 | 42% | +0.28 | 288 | 41% | +0.19 | +| 3 ATR or more (chased) | 379 | 47% | +0.17 | 438 | 38% | +0.14 | + +| Hourly stretch (distance above the 20-bar hourly mean) | M5 loss rate | M5 net R | M15 loss rate | M15 net R | +|---|---|---|---|---| +| Below the mean | 49% | +19.4 | 43% | +12.7 | +| 0 to 1 ATR | 50% | +21.9 | 45% | +25.0 | +| 1 to 2 ATR | 47% | +42.6 | 41% | +52.4 | +| 2 ATR or more (stretched) | 45% | +62.2 | 35% | +56.1 | + +Same picture for the 48-bar move (M5: 4 ATR or more = 44% loss rate and +128.5R of the +146.1R), +the distance from the 21-bar average, consecutive closes and the exhaustion tags (two or more +tags: 45% / 40% loss rate, the best bucket on M5). This is the 2026-08-04 age/stretch verdict +again: the best trades are the stretched runs. "Late entry" is not a cause of losses here. + +## 4. Wrong direction: what predicted the wrong side? + +| Condition at entry | M5 trades | Loss rate | Net R | M15 trades | Loss rate | Net R | +|---|---|---|---|---|---|---| +| Timeframes agreed | 579 | 46% | +128.4 | 897 | 39% | +135.3 | +| Timeframes did not agree | 202 | 51% | +17.7 | 34 | 44% | +10.9 | +| With the trend | 475 | 45% | +93.5 | 596 | 40% | +68.1 | +| No trend, ranging | 169 | 49% | +25.7 | 120 | 37% | +47.0 | +| No trend, transitioning | 76 | 51% | +24.0 | 213 | 41% | +31.7 | +| No trend, mixed trending | 61 | 54% | +2.9 | (blocked on M15) | | | +| Confidence penalty 20 points or more | 94 | 55% | −3.3 | 11 | 64% | −0.4 | +| Buy | 683 | 47% | +137.3 | 635 | 37% | +146.6 | +| Sell | 98 | 49% | +8.8 | 296 | 46% | −0.5 | + +- Disagreement between timeframes and a mixed regime raise the loss rate by about 5 points on M5 + and remove most of the average R; the heavily penalised entries (20+ points) are net negative + on both books but are few. +- Indicator opposition does not predict the wrong side: the M5 bucket with **no** opposing + indicator loses most often (58%, 40 trades). Direction was not "obviously wrong" at entry. +- M15 shorts: 296 trades, 46% losers, −0.5R net. Every M15 R came from buys. This is the parked + cycle-2 candidate (`cycle_2026-09-10.md`); unchanged status. + +## 5. When we lost + +Entry hour is the open of the bar after the signal bar (Coordinated Universal Time). Cells under +30 trades are noise. + +| M5 hour | Trades | Loss rate | Net R | | M15 hour | Trades | Loss rate | Net R | +|---|---|---|---|---|---|---|---|---| +| 08:00 | 62 | 55% | −0.7 | | 07:00 | 68 | 47% | −4.1 | +| 14:00 | 117 | 53% | +4.7 | | 09:00 | 75 | 47% | +3.8 | +| 17:00 | 35 | 49% | −4.0 | | 18:00 | 45 | 47% | −2.1 | +| 22:00 | 31 | 55% | +4.0 | | 04:00 | 28 | 43% | +1.9 | +| 20:00 | 42 | 52% | +30.1 | | 14:00 | 154 | 40% | +16.9 | +| 06:00 | 60 | 40% | +26.5 | | 12:00 | 68 | 31% | +33.8 | + +The weakest hours are 08:00 and 17:00 on M5 (14:00 is the busiest hour and carries the most +loss R, −44.9R, at a 53% loss rate but still nets +4.7R) and 07:00, 09:00 and 18:00 on M15. No +hour is both large and clearly negative; 20:00 on M5 loses 52% of the time and is the second +best hour by R (+30.1R), which is the whole shape of this edge. + +| Weekday | M5 trades | Loss rate | Net R | M15 trades | Loss rate | Net R | +|---|---|---|---|---|---|---| +| Monday | 153 | 45% | +17.6 | 169 | 39% | +28.2 | +| Tuesday | 159 | 48% | +21.3 | 212 | 33% | +50.4 | +| Wednesday | 176 | 45% | +46.6 | 189 | **51%** | **−4.0** | +| Thursday | 135 | 51% | +9.2 | 179 | 40% | +30.6 | +| Friday | 151 | 48% | +47.7 | 169 | 36% | +34.2 | + +Wednesday is the one clear day effect: on M15 it loses more than half the time and is the only +negative weekday (−86.2R of loss R, the worst of any day); the worst M15 weekday-hour cells are +all Wednesday (14:00: 26 trades, 16 losers, −7.2R; 18:00: 13, 8, −4.7R; 09:00: 20, 14, −3.3R; +08:00: 16, 9, −3.0R). On M5 Wednesday is fine (+46.6R) and Thursday 20:00 is the worst cell (11 +trades, 9 losers, −6.5R). + +Calendar-day clusters are not the story: the ten worst days hold 11% (M5) and 9% (M15) of all +loss R, no single day cost more than −4.2R, and only 27 of 333 (M5) and 15 of 449 (M15) entry +days had three or more losers. Losers entered within two hours of the previous loser (re-entries +into the same move): M5 46 (−39.5R, 14% of loss R), M15 22 (−21.7R, 6%). By month, July is +negative on M15 in both years (2025-07 −7.5R at 57% losers, 2026-07 −8.6R at 58%) and M5 lost in +2024-Q4 and 2025-07/08. + +## 6. Summary for the owner + +| Question | Answer (both books unless stated) | +|---|---| +| Wrong direction? | Yes for about 43% of losers by count and about half of the loss R: they never moved a quarter of the risk our way and were stopped within 12–18 bars. Nothing at entry flagged them: indicator opposition does not predict it, timeframe disagreement and mixed regimes add only ~5 points of loss rate. | +| Late entry? | No. Loss rate falls as the prior move grows; the few pullback-style entries lose 64–80%. Stretched entries carry most of the R. (Confirms the 2026-08-04 verdict.) | +| Target too far, market reversed first? | Rarely the cause of a loss: reversals after a 0.75R advance cost 1–2% of loss R because the trailing stop limits them. About a third of losers were up at least half the target-1 distance before losing; a nearer hard target would rescue them but cap far more winners (net +45R to +133R against +146R). | +| Bad hours? | M5: 08:00 and 17:00 lose most often; 14:00 carries the most loss R but nets positive. M15: 07:00, 09:00 and 18:00. No large hour is clearly negative. | +| Bad days? | Wednesday on M15 (51% losers, −4.0R net, all four worst weekday-hour cells). Not a single-day-cluster problem on either book. | + +None of this is a change proposal. A Wednesday or hour block on M15, or a shorter M15 SELL list, +would each be a rule-1 campaign with a control run, the OOS window and the refuted-hypothesis +list in mind (the 2026-07 M15 loss analysis rejected several hour variants). The most useful +follow-up, if any, is the parked M15 grade-A SELL block, which this table supports without +adding to it. diff --git a/backtest/research/rr_and_sell_campaign_2026-09-26.md b/backtest/research/rr_and_sell_campaign_2026-09-26.md new file mode 100644 index 0000000..7d00073 --- /dev/null +++ b/backtest/research/rr_and_sell_campaign_2026-09-26.md @@ -0,0 +1,183 @@ +# Risk-reward floor and M15 grade-A SELL block — pre-registered campaign, 2026-09-26 + +Owner questions (2026-09-26): plans sometimes show a target smaller than the stop (RR 0.75 / 0.5); +and "we still lose money". The live review of `logs/signals.db` (2026-09-04 → 2026-09-25, 100 +unique setups replayed on the scans' own stored candles) found: no stop-hunt signature (52 stop-outs, +71% ran ≥ 1R past the stop, the same as the books' 70–72%); −25.1R of the −37.8R raw live book were +consecutive-bar re-entries the backtester's 5-bar cooldown never takes; the cooldown view +(−12.7R / 57) sits at the 3rd–5th percentile of three-week windows of the two-year books. + +This file was written BEFORE any candidate run. Rules first, results appended below them. + +## Harness + +`venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf --start +--end --spread <0.65|0.30> [--set ...] --export-csv ` (CUDA research twin; its parity +with `gpu_backtest.py` was re-proven today: the M15 and M5 two-year controls reproduce the pinned +MD5s `b4aa07e33e17e23774c486a6900d673b` and `49f8e602fc6ae769713c94ef7b257810`). Factory +configuration; `--set` is the only override. A passing candidate is re-run once on +`gpu_backtest.py` before it ships and the two MD5s must be equal. + +Windows: W2 2024-09-05 → 2026-09-05 (halves split at 2025-09-05); OOS 2024-06-01 → 2025-03-30 +(halves at the midpoint 2024-10-30); W3 2023-09-05 → 2026-09-05 (the owner's 3-year maximum, +halves at 2025-03-05; used for the max-DD clause). Costs $0.65 (headline) and $0.30. + +## Candidates + +| # | TF | `--set` | Hypothesis | +|---|---|---|---| +| B1 | M15 | `signals.min_rr_ratio=1.0` | Vetoes the grade-A XAU_ATR plan (RR 0.75 / 0.5); the signal falls through to the GENERIC swing setup whose M15 floor is already 1R. Every M15 plan then shows RR ≥ 1. Two-year book: XAU_ATR 174 / +5.7R / 24% wrong-direction vs GENERIC 757 / +140.5R / 16% | +| B2 | M5 | `signals.tp_floor_rr=1.0` | Retest of the 1R target floor on M5 (failed 2026-07-11 on Twelve Data bars at $0.30). Expected to fail again: it lifts low-ATR targets past the $9 spread gate | +| B3 | M15 | `xau_strategy.sell_min_quality=["A+","B"]` | Parked cycle-2 candidate: M15 grade-A SELLs 230 / −14.4R, negative in y1, y2, y3 and OOS (cycle_2026-09-10.md). Counter-fact on record: those shorts paid in 2021–22 | +| B4 | M15 | B1 + B3 | The shipped combination if both pass alone | + +## Acceptance rule (CLAUDE.md rule 1, written before the runs) + +A candidate PASSES only if, at $0.65, on W2: (a) total R ≥ control + 5R; (b) both halves ≥ the +control's halves − 1R (no half may fund the other); (c) PF ≥ 1.3 and ≥ control − 0.02; (d) OOS +total R ≥ control − 1R; (e) W3 max drawdown ≤ control + 10%; (f) the same sign of ΔR at $0.30. +A candidate that changes fewer than 20 trades on W2 is "no evidence", not a pass. B4 must pass +the rule on its own numbers, not by inheritance. + +Judge run-level numbers, never the added/removed bucket alone (the 5-bar cooldown displaces +entries). Trade diff keyed on (entry time, direction). + +## Results (all runs on the CUDA twin, `Backend: CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)`, 0 cache fallbacks; controls reproduce the pinned MD5s) + +| TF | Window | Cost | Candidate | Trades | Total R | H1 | H2 | WR | PF | Max DD | MD5 (8) | +|---|---|---|---|---|---|---|---|---|---|---|---| +| M15 | W2 | $0.65 | control | 931 | +146.14 | +66.14 | +80.00 | 60.5% | 1.43 | 16.1 | b4aa07e3 | +| M15 | W2 | $0.65 | B1 | 931 | +144.16 | +65.09 | +79.07 | 60.3% | 1.42 | 16.1 | 80428e98 | +| M15 | W2 | $0.65 | **B3** | 813 | **+154.15** | +70.10 | +84.04 | 62.2% | 1.53 | 15.6 | 497520ef | +| M15 | W2 | $0.65 | B4 | 813 | +153.41 | +70.73 | +82.68 | 62.1% | 1.53 | 15.6 | 177b716d | +| M15 | W2 | $0.30 | control | 931 | +161.44 | +75.63 | +85.81 | 61.0% | 1.48 | 14.9 | 6bdea010 | +| M15 | W2 | $0.30 | B1 | 931 | +158.17 | +74.41 | +83.75 | 60.7% | 1.47 | 14.9 | 21dcfc9e | +| M15 | W2 | $0.30 | B3 | 813 | +167.53 | +78.61 | +88.91 | 62.6% | 1.59 | 14.5 | aff050f7 | +| M15 | W2 | $0.30 | B4 | 813 | +166.06 | +79.12 | +86.95 | 62.5% | 1.58 | 14.8 | 03a412b8 | +| M15 | OOS | $0.65 | control | 355 | +60.67 | +9.14 | +51.52 | 60.8% | 1.49 | 17.1 | 9fd624fb | +| M15 | OOS | $0.65 | B1 | 355 | +60.67 | +9.14 | +51.52 | 60.8% | 1.49 | 17.1 | 9fd624fb (identical) | +| M15 | OOS | $0.65 | B3 = B4 | 313 | +68.50 | +18.87 | +49.63 | 63.6% | 1.67 | 12.4 | 09b8a8e7 | +| M15 | OOS | $0.30 | control | 355 | +69.18 | +13.48 | +55.70 | 61.7% | 1.57 | 14.9 | 30a82817 | +| M15 | OOS | $0.30 | B3 = B4 | 313 | +76.21 | +22.92 | +53.29 | 64.5% | 1.77 | 11.1 | 5fc34cef | +| M15 | W3 | $0.65 | control | 1273 | +186.61 | +75.87 | +110.74 | 59.4% | 1.39 | 19.3 | 5194e5d4 | +| M15 | W3 | $0.65 | B1 | 1273 | +183.78 | +75.83 | +107.95 | 59.2% | 1.39 | 19.3 | 2d5cfc31 | +| M15 | W3 | $0.65 | B3 | 1105 | +195.79 | +76.87 | +118.92 | 61.1% | 1.49 | 20.9 | 8bed7199 | +| M15 | W3 | $0.65 | B4 | 1105 | +194.19 | +76.00 | +118.18 | 61.0% | 1.48 | 20.9 | 05201726 | +| M15 | W3 | $0.30 | control / B1 / B3 / B4 | 1273 / 1273 / 1105 / 1105 | +210.42 / +206.25 / +216.53 / +214.19 | | | | 1.45 / 1.44 / 1.55 / 1.54 | 17.7 / 17.7 / 19.6 / 19.6 | | +| M5 | W2 | $0.65 | control | 781 | +146.07 | +26.42 | +119.65 | 52.9% | 1.52 | 10.1 | 49f8e602 | +| M5 | W2 | $0.65 | B2 | 1105 | +194.94 | +54.11 | +140.83 | 53.6% | 1.50 | 15.9 | 351f9eee | +| M5 | W2 | $0.30 | control / B2 | 781 / 1105 | +162.09 / +220.32 | +31.71 / +65.22 | +130.37 / +155.10 | | 1.60 / 1.58 | 8.9 / 13.7 | | +| M5 | OOS | $0.65 | control | 86 | +9.47 | +0.01 | +9.46 | 53.5% | 1.33 | 8.2 | da497d74 | +| M5 | OOS | $0.65 | B2 | 235 | +13.17 | −6.01 | +19.18 | 52.3% | 1.17 | 12.3 | 3cece33b | +| M5 | OOS | $0.30 | control / B2 | 86 / 235 | +12.42 / +20.66 | +1.65 / −2.04 | +10.77 / +22.69 | | 1.46 / 1.28 | 6.7 / 9.5 | | +| M5 | W3 | $0.65 | control | 870 | +153.64 | +16.73 | +136.91 | 53.3% | 1.50 | 10.1 | 408de695 | +| M5 | W3 | $0.65 | B2 | 1333 | +223.87 | +47.49 | +176.38 | 53.4% | 1.48 | 15.9 | f89176d6 | +| M5 | W3 | $0.30 | control / B2 | 870 / 1333 | +172.40 / +256.20 | | | | 1.57 / 1.56 | 8.9 / 13.7 | | + +Trade diffs on W2 at $0.65 (key = entry time + direction): +- B1: 0 added, 0 removed, 931 common. The 158 grade-A XAU_ATR setups become GENERIC (strategy + mix 915 / 16 vs 757 / 174); same bars, same direction, a swing-anchored stop instead of 2 × ATR. +- B3: 162 removed (−8.11R, all grade-A SELLs), 44 added (−0.11R, cooldown displacement), 769 common. + Direction mix 635 BUY / 178 SELL vs 635 / 296. +- B2: 392 added (+63.63R), 68 removed (+17.23R), 713 common. + +### Verdicts against the pre-registered rule + +| # | (a) ΔR ≥ +5 W2 | (b) halves | (c) PF | (d) OOS | (e) W3 DD ≤ +10% | (f) $0.30 sign | Verdict | +|---|---|---|---|---|---|---|---| +| B1 | −2.0 ✗ | −1.05 / −0.93 ✓ | 1.42 ✓ | 0 ✓ | 19.3 ✓ | −3.3 ✓ | **FAIL (a)**. Trade-identical; the GENERIC stop is not better than the 2 × ATR stop on the same grade-A signals (W3 −2.8R). Not shipped | +| B2 | +48.9 ✓ | +27.7 / +21.2 ✓ | 1.50 ✓ (−0.02) | +3.7 ✓ (but OOS PF 1.17, OOS H1 −6.0) | 15.9 vs 10.1 = +57% ✗ | +58.2 ✓ | **FAIL (e)**. Owner's call, see the ladder below | +| B3 | +8.0 ✓ | +4.0 / +4.0 ✓ | 1.53 ✓ | +7.8 ✓ | 20.9 vs 19.3 = +8% ✓ | +6.1 ✓ | **PASS a–f — SHIPPED** | +| B4 | +7.3 ✓ | ✓ | 1.53 ✓ | +7.8 ✓ | +8% ✓ | +4.6 ✓ | Passes, but 0.7R below B3 alone on W2 and 1.6R on W3; B1's part adds nothing. Not shipped | + +B3 per year on W3 ($0.65, control → B3): 2023 (Sep–Dec) +13.2 → +10.8; 2024 +52.9 → +53.3; +2025 +77.3 → +81.2; 2026 (to Sep 5) +43.2 → +50.5. Removed grade-A SELLs by year: 2023 15 / +1.4R, +2024 76 / −1.3R, 2025 79 / −8.6R, 2026 60 / −5.8R. The max-DD episode is the same one in both books +(2023-12-01 → 2024-02-23), 19.3R → 20.9R. M15 SELLs remaining under B3 on W3: A+ 19 / +2.6R, +B 217 / −8.6R. The 5-year fact on the other side of the scale stands (shorts paid in 2021–22). + +### M5 target-floor ladder (informational, run after B2's verdict; NOT pre-registered, NOT shipped) + +`--set signals.tp_floor_rr=` on M5, $0.65: + +| Floor | W2 trades / R / PF / DD | OOS trades / R / PF / DD | W3 trades / R / PF / DD | +|---|---|---|---| +| 0.5 (shipped) | 781 / +146.1 / 1.52 / 10.1 | 86 / +9.5 / 1.33 / 8.2 | 870 / +153.6 / 1.50 / 10.1 | +| 0.6 | 868 / +159.1 / 1.51 / 10.2 | 105 / +1.5 / 1.04 / 12.5 | 979 / +164.5 / 1.46 / 17.1 | +| 0.75 | 975 / +192.4 / 1.57 / 13.4 | 147 / +13.5 / 1.31 / 9.4 | 1133 / +211.6 / 1.54 / 13.4 | +| 0.9 | 1053 / +209.3 / 1.58 / 13.5 | 194 / +22.8 / 1.41 / 7.5 | 1252 / +238.7 / 1.55 / 13.5 | +| 1.0 (B2) | 1105 / +194.9 / 1.50 / 15.9 | 235 / +13.2 / 1.17 / 12.3 | 1333 / +223.9 / 1.48 / 15.9 | + +Reading: the 2026-07-11 verdict ("a 1R floor hurts M5") was made on Twelve Data bars at $0.30 with +the pre-relaxation gates and the pullback entries on; on Oanda bars at $0.65 with today's gates the +floor lifts small-ATR targets past the $9 spread gate and those trades earn (B2 added trades on W3: +554 / +90.8R, 53% winners, median stop $12 vs $20, 962 of 1,105 plans at exactly RR 1.0; positive +in every year; by volatility regime extreme +44.6R, low +32.0R, normal +14.2R). The cost is a deeper +drawdown in the same May–July 2026 episode (10.1R → 13.4–15.9R) and a non-monotone ladder (0.9 beats +1.0 on every window, 0.6 is worse than 0.5 out of sample), which is why nothing from it ships now. +If the owner wants M5 plans at RR ≥ 1 (the original request), the honest candidate is a fresh +pre-registered run at ONE value chosen before looking (0.75 or 1.0), with the DD clause waived +explicitly as on 2026-07-21, plus the cost ladder ($0.80, $1.00) because the added trades carry +$12 stops. Until then M5 plans keep 0.5R guides and the alert says so. + +## Shipped (2026-09-26) + +1. **A1 — alert cooldown** (`telegram_bot.py`): no new alert on a timeframe within 5 bars of the + last alerted setup's bar, `_ALERT_COOLDOWN_BARS = 5` = `backtest.cooldown_bars`. Book-neutral. +2. **A2 — alert wording** (`telegram_bot.py`, `main.py`, `docs/user_guide.html`): the trail is + printed first as the tested exit; TP1/TP2 are guides with their R; "Planned TP1 RR"; one + validated-book line per setup class from `agent_panel_base_rates.json`. Book-neutral. +3. **B3 — M15 grade-A SELL block** (`pair_signals/xauusd.py`, M15 override 6). Re-pins the M15 + books (below). +4. **XAU_ATR target floor** (`signal_aggregator._generate_xau_atr_trade_setup`, mirrored in + `cuda_signal_kernels.py` K2): the gold ATR generator now honours `tp_floor_rr` like the GENERIC + one, compared as multipliers so the non-binding case is bit-identical. Binds only on M15 (floor + 1R): grade-A plans print TP1 at 2 × ATR = 1R instead of 1.5 × ATR = 0.75R. TP1 is advisory in the + backtester, so the only book effect is through the $9 spread gate (parity MD5s below). + +## Addendum (same day): printed-target floor extended to every generator + +Owner rule: "make sure TP1 is at least 1:1". Shipped as `signals.tp_print_floor_rr = 1.0` +(effective floor `max(tp_floor_rr, tp_print_floor_rr)`), applied after the min-RR and spread gates in +the gold ATR, GENERIC and chart-pattern generators (Python and CUDA K2). Label-only by construction +and by measurement (twin books vs the previous pins, same code otherwise): + +| Book | Rows | R before → after | Columns that differ | Rows with a new `tp1` | min `rr_planned` before → after | +|---|---|---|---|---|---| +| M5 W2 | 781 | +146.07 → +146.07 | `tp1`, `rr_planned` | 544 (GENERIC 408, XAU_CP 105, XAU_ATR 31) | 0.5 → 1.0 | +| M15 W2 | 813 | +154.15 → +154.15 | none | 0 | 1.0 → 1.0 | +| H1 W3 | 253 | +70.02 → +70.02 | `tp1`, `rr_planned` | 252 | 0.5 → 1.0 | +| H4 W3 | 114 | +21.68 → +21.68 | `tp1`, `rr_planned` | 108 | 0.5 → 1.0 | + +New pins: M5 W2 `e599c8a8652f95b7fcdf5a7ef2b564f5`, H1 W3 `a44fd19b4067f46e5bd98d50cc3eff92`, +H4 W3 `f3587bae3c5dc3eda468ea3588b17dcb`; M15 W2 `5b65ecf6…` and OOS `09b8a8e7…` unchanged. Reference +script MD5s: see the parity lines appended below. The gated floor (`tp_floor_rr`, 0.5 on M5 / 1.0 on +M15) is untouched: raising it is the B2 question above, a book change, and remains the owner's call. + +## Addendum 2 (same day): consistency checks on `signals.db` — two delivery bugs fixed + +Read-only checks over the 8,453 rows (2026-09-04 → 09-25): scan latency (median 10–15 s after the +candle close on every timeframe; 2–3% of rows read the previous bar, all in 18–22 UTC around the +Labor Day gap and the settlement pause), `actionable` / setup consistency (clean), gate reasons for +actionable grades without a setup (all `SPREAD GATE`, small-ATR M5 bars), outcome states (97 resolved, +36 duplicates, 1 unresolvable for a candle gap), scan-outcome labelling (7,054 of 7,634 complete), +8 `failed` scans (Oanda "primary timeframe data unavailable", notably 06:11–06:17 UTC on both Sep 15 +and Sep 16), dashboard-vs-bot disagreement on the same bar (4 bars of 3,300). + +Two bugs, both fixed and tested, books untouched: + +1. **Panel probability never stored under `verdict_source: "macro"`** (the live setting): all 33 v2 + rows had `panel_probability` NULL although `panel_probs_json` carried the macro persona's `p_win`. + Cause: the macro-source `PanelVerdict` carried no `probability`, `_write_fields` wrote 0.0 and the + logger's `probability if probability else None` dropped it. Fix: the macro verdict's probability is + the macro take's `p_win` (`agent_panel.run_panel`). Without it the planned re-evaluation at 100 / + 300 outcomes would have had no live probabilities to score. +2. **Breach rescans re-log the last pre-pause M15 bar.** Every evening the 20:45 UTC bar was logged + again at 22:01, 22:06 and 22:11 (Sep 17–24) by the tick-driven level-breach rescan: the first + post-pause bar closes at 22:15, so the frame still ends at 20:45. The candle loop already skips + that window (`_closed_bar_in_settlement`); `_handle_breach` now applies the same guard. Cosmetic + for alerts (dedup + cooldown), but it polluted the log's per-bar dedup and the parity reports. + +Not bugs, recorded: 380 `blocked` weekend rows on Sep 20 carry no `bar_ts` by design; the 43× repeat of +the Sep 7 18:25 M5 bar is the known holiday gap (no holiday calendar); the H4 loop produced no setup +in three weeks (its two tradeable close hours). diff --git a/backtest/research/wrong_direction_2026-09-18.md b/backtest/research/wrong_direction_2026-09-18.md new file mode 100644 index 0000000..a3e4b69 --- /dev/null +++ b/backtest/research/wrong_direction_2026-09-18.md @@ -0,0 +1,614 @@ +# Wrong-direction losers: source, indicator values and timing — two-year M5 and M15 books, 2026-09-18 + +Follow-up to `loss_anatomy_2026-09-18.md`. Owner questions: (1) do the wrong-direction losers come +from chart patterns or from indicator-driven setups, (2) what did each indicator read on those +trades compared with the trades that got the direction right, (3) were there hours or days when +the direction was wrong more often. Same two-year books (2024-09-05 → 2026-09-05, $0.65), +regenerated on the CUDA twin with `--record --export-features`; the recorder's `indicators` +table holds every indicator's vote, value, confidence, effective weight and contribution on the +primary and both confirmation timeframes for every accepted trade. In-book statistics; no decision. + +## 5min: 781 trades — 153 wrong-direction losers (20%), 215 other losers, 413 winners + +Wrong direction = a losing trade whose best excursion never reached a quarter of the initial risk. + +### 1. Where the wrong-direction trades came from (5min) + +| Setup type | Trades | Wrong-direction | Rate | Share of all wrong-direction | Other losers | Winners | Net R | +|---|---|---|---|---|---|---|---| +| GENERIC | 411 | 73 | 18% | 48% | 115 | 223 | +80.9 | +| XAU_ATR | 32 | 6 | 19% | 4% | 9 | 17 | +8.9 | +| XAU_CP | 338 | 74 | 22% | 48% | 91 | 173 | +56.3 | + +Chart patterns (5min): + +| Pattern | Trades | Wrong-direction | Rate | Winners | Net R | +|---|---|---|---|---|---| +| bull_flag | 166 | 35 | 21% | 87 | +30.2 | +| double_bottom | 86 | 15 | 17% | 49 | +21.5 | +| inv_hs | 86 | 24 | 28% | 37 | +4.6 | + +By grade and by regime (5min): + +| Grade | Trades | Wrong-direction rate | | Regime | Trades | Wrong-direction rate | +|---|---|---|---|---|---|---| +| A | 187 | 18% | ranging | 169 | 22% | +| A+ | 1 | 0% | transitioning | 76 | 20% | +| B | 572 | 20% | trending | 61 | 21% | +| C | 21 | 29% | trending_down | 95 | 18% | +| | | | trending_up | 380 | 19% | + +By direction: BUY: 683 trades, 20% wrong-direction; SELL: 98 trades, 17% wrong-direction. + +### 2. The signal itself at entry (5min): wrong-direction losers vs winners + +Values signed in the trade's direction where a sign applies (positive = in the trade's favour). The separation column is the probability that a random winner shows a higher value than a random wrong-direction loser: 0.50 means the measure does not separate them at all. + +| Measure | Wrong-direction median | Winner median | Other-loser median | Separation | +|---|---|---|---|---| +| Composite score of the primary timeframe (in trade direction) | +31.75 | +33.07 | +32.77 | 0.53 | +| Final score after the multi-timeframe blend (in trade direction) | +29.05 | +30.08 | +29.63 | 0.53 | +| Final confidence | +53.97 | +54.66 | +53.65 | 0.54 | +| Composite score of confirmation 1 (in trade direction) | +20.56 | +20.60 | +18.22 | 0.54 | +| Composite score of confirmation 2 (in trade direction) | +16.99 | +16.50 | +17.34 | 0.51 | +| Indicators agreeing (count) | +4.00 | +5.00 | +4.00 | 0.54 | +| Indicators opposing (count) | +1.00 | +1.00 | +1.00 | 0.53 | +| Confidence penalty (points) | +16.94 | +16.62 | +16.62 | 0.50 | +| Exhaustion tags | +2.00 | +3.00 | +2.00 | 0.55 | +| Primary ATR as % of price | +0.12 | +0.12 | +0.13 | 0.51 | +| Prior 12-bar move in trade direction (ATR) | +2.91 | +2.92 | +2.90 | 0.53 | +| Hourly stretch (ATR) | +1.56 | +1.51 | +1.43 | 0.51 | +| Planned target 1 (R) | +0.73 | +0.50 | +0.53 | 0.46 | + +### 3a. Each indicator on the primary timeframe (5min) + +Agree = the indicator voted in the trade's direction; oppose = against. Value is signed in the trade's direction (RSI minus 50, Bollinger position minus 0.5, MACD and Keltner in ATR units, EMA alignment, rate of change in percent; ADX is unsigned strength). Contribution = the indicator's weighted score in the trade's favour. + +| Indicator | Present (wrong / win) | Agree % (wrong / win) | Oppose % (wrong / win) | Median value (wrong / win) | Median confidence (wrong / win) | Mean contribution (wrong / win) | Separation of value | +|---|---|---|---|---|---|---|---| +| EMA trend (alignment) | 100% / 100% | 78% / 82% | 0% / 0% | +1.00 / +1.00 | 90 / 90 | +2.38 / +2.57 | 0.52 | +| RSI | 100% / 100% | 6% / 8% | 20% / 18% | +18.33 / +20.61 | 30 / 30 | -0.14 / -0.11 | 0.56 | +| Bollinger position | 100% / 100% | 0% / 0% | 93% / 95% | +0.47 / +0.47 | 45 / 45 | -0.60 / -0.59 | 0.52 | +| ADX (trend strength) | 100% / 100% | 63% / 69% | 3% / 1% | +29.88 / +30.40 | 70 / 70 | +0.17 / +0.19 | 0.51 | +| MACD histogram | 100% / 100% | 84% / 86% | 0% / 0% | +0.19 / +0.18 | 67 / 66 | +3.22 / +3.35 | 0.50 | +| Rate of change | 100% / 100% | 76% / 77% | 1% / 1% | +0.29 / +0.32 | 52 / 52 | +0.83 / +0.83 | 0.55 | +| Keltner extension | 100% / 100% | 73% / 80% | 0% / 0% | +2.08 / +2.21 | 65 / 65 | +1.29 / +1.44 | 0.55 | +| Divergence | 24% / 34% | 36% / 28% | 64% / 70% | +1.00 / +1.00 | 70 / 70 | -0.10 / -0.39 | 0.54 | +| Candlestick | 35% / 30% | 93% / 90% | 0% / 0% | +1.00 / +1.00 | 50 / 50 | +0.40 / +0.39 | 0.48 | + +### 3b. Each indicator on the first confirmation timeframe + +Agree = the indicator voted in the trade's direction; oppose = against. Value is signed in the trade's direction (RSI minus 50, Bollinger position minus 0.5, MACD and Keltner in ATR units, EMA alignment, rate of change in percent; ADX is unsigned strength). Contribution = the indicator's weighted score in the trade's favour. + +| Indicator | Present (wrong / win) | Agree % (wrong / win) | Oppose % (wrong / win) | Median value (wrong / win) | Median confidence (wrong / win) | Mean contribution (wrong / win) | Separation of value | +|---|---|---|---|---|---|---|---| +| EMA trend (alignment) | 100% / 100% | 57% / 62% | 1% / 2% | +1.00 / +1.00 | 90 / 90 | +1.56 / +1.60 | 0.52 | +| RSI | 100% / 100% | 11% / 6% | 22% / 33% | +16.43 / +17.10 | 30 / 30 | -0.13 / -0.28 | 0.53 | +| Bollinger position | 100% / 100% | 1% / 0% | 71% / 79% | +0.43 / +0.46 | 45 / 45 | -0.46 / -0.58 | 0.55 | +| ADX (trend strength) | 100% / 100% | 51% / 47% | 10% / 6% | +27.18 / +25.70 | 55 / 55 | +0.12 / +0.11 | 0.46 | +| MACD histogram | 100% / 100% | 61% / 70% | 3% / 2% | +0.16 / +0.19 | 61 / 65 | +2.29 / +2.68 | 0.55 | +| Rate of change | 100% / 100% | 63% / 65% | 7% / 4% | +0.38 / +0.45 | 53 / 53 | +0.64 / +0.71 | 0.55 | +| Keltner extension | 100% / 100% | 60% / 67% | 0% / 1% | +1.78 / +1.92 | 65 / 65 | +1.17 / +1.33 | 0.53 | +| Divergence | 38% / 36% | 24% / 40% | 71% / 60% | +1.00 / +1.00 | 70 / 70 | -0.67 / -0.09 | 0.51 | +| Candlestick | 34% / 35% | 65% / 71% | 10% / 6% | +1.00 / +1.00 | 50 / 50 | +0.25 / +0.29 | 0.53 | + +### 3c. Each indicator on the second confirmation timeframe + +Agree = the indicator voted in the trade's direction; oppose = against. Value is signed in the trade's direction (RSI minus 50, Bollinger position minus 0.5, MACD and Keltner in ATR units, EMA alignment, rate of change in percent; ADX is unsigned strength). Contribution = the indicator's weighted score in the trade's favour. + +| Indicator | Present (wrong / win) | Agree % (wrong / win) | Oppose % (wrong / win) | Median value (wrong / win) | Median confidence (wrong / win) | Mean contribution (wrong / win) | Separation of value | +|---|---|---|---|---|---|---|---| +| EMA trend (alignment) | 100% / 100% | 41% / 45% | 5% / 6% | +0.50 / +0.50 | 50 / 90 | +0.94 / +1.04 | 0.51 | +| RSI | 100% / 100% | 7% / 6% | 23% / 23% | +11.38 / +11.44 | 30 / 30 | -0.17 / -0.17 | 0.51 | +| Bollinger position | 100% / 100% | 7% / 5% | 58% / 62% | +0.37 / +0.37 | 45 / 45 | -0.39 / -0.42 | 0.53 | +| ADX (trend strength) | 100% / 100% | 42% / 42% | 16% / 15% | +26.89 / +26.78 | 55 / 55 | +0.07 / +0.07 | 0.48 | +| MACD histogram | 100% / 100% | 58% / 62% | 9% / 7% | +0.16 / +0.14 | 63 / 61 | +1.74 / +1.98 | 0.50 | +| Rate of change | 100% / 100% | 60% / 67% | 10% / 10% | +0.59 / +0.66 | 55 / 56 | +0.69 / +0.80 | 0.52 | +| Keltner extension | 100% / 100% | 48% / 44% | 3% / 3% | +1.26 / +1.31 | 65 / 60 | +0.86 / +0.68 | 0.51 | +| Divergence | 31% / 29% | 34% / 44% | 66% / 54% | +1.00 / +1.00 | 70 / 70 | -0.49 / -0.06 | 0.49 | +| Candlestick | 27% / 30% | 73% / 70% | 10% / 10% | +1.00 / +1.00 | 50 / 50 | +0.29 / +0.27 | 0.48 | + +### 4. When the direction was wrong (5min) + +Base rate: 20% of all trades were wrong-direction losers. Cells under 20 trades are noise. + +| Entry hour (Coordinated Universal Time) | Trades | Wrong-direction | Rate | | Weekday | Trades | Wrong-direction | Rate | +|---|---|---|---|---|---|---|---|---| +| 03:00 | 50 | 8 | 16% | Mon | 153 | 27 | 18% | +| 04:00 | 30 | 7 | 23% | Tue | 159 | 35 | 22% | +| 05:00 | 52 | 17 | 33% | Wed | 176 | 33 | 19% | +| 06:00 | 60 | 9 | 15% | Thu | 135 | 28 | 21% | +| 07:00 | 51 | 8 | 16% | Fri | 151 | 28 | 19% | +| 08:00 | 62 | 15 | 24% | Sun | 7 | 2 | 29% | +| 12:00 | 63 | 11 | 17% | | | | | +| 14:00 | 117 | 22 | 19% | | | | | +| 15:00 | 90 | 15 | 17% | | | | | +| 17:00 | 35 | 8 | 23% | | | | | +| 18:00 | 41 | 8 | 20% | | | | | +| 19:00 | 45 | 7 | 16% | | | | | +| 20:00 | 42 | 9 | 21% | | | | | +| 21:00 | 12 | 3 | 25% | | | | | +| 22:00 | 31 | 6 | 19% | | | | | + +Weekday-hour cells with at least 10 trades and the highest wrong-direction rate (5min): + +| Day | Hour | Trades | Wrong-direction | Rate | +|---|---|---|---|---| +| Thu | 20:00 | 11 | 6 | 55% | +| Tue | 05:00 | 16 | 8 | 50% | +| Wed | 18:00 | 11 | 4 | 36% | +| Wed | 08:00 | 17 | 6 | 35% | +| Wed | 06:00 | 14 | 4 | 29% | +| Thu | 12:00 | 14 | 4 | 29% | + +Months with the highest wrong-direction rate: 2024-12 60% (3 of 5); 2025-08 33% (5 of 15); 2026-09 31% (4 of 13); 2025-09 27% (9 of 33). + +### Appendix 5min: every wrong-direction loser with its primary-timeframe indicator values + +Columns: entry time, setup (pattern), grade, direction, regime, final score in trade direction / confidence, then per indicator `signed value (vote)` where the vote is −2 strong sell … +2 strong buy; blank = the indicator did not vote. Values as defined in section 3. R = realised result. + +| Entry | Setup | Grade | Dir | Regime | Score/Conf | EMA | RSI | Bollinger | ADX | MACD | ROC | Keltner | Div | Candle | R | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 2024-09-13 17:05 | XAU_CP (bull_flag) | B | BUY | ranging | +17/57 | +1.00 (+2) | +15.26 (-1) | +0.41 (-1) | +22.50 (+0) | +0.05 (+1) | +0.08 (+1) | +1.66 (+1) | | | -1.08 | +| 2024-09-18 18:35 | GENERIC | A | BUY | trending_up | +34/60 | +1.00 (+2) | +31.29 (+0) | +0.51 (-1) | +31.03 (+1) | +0.64 (+2) | +1.11 (+1) | +3.99 (+1) | | +1.00 (+1) | -1.02 | +| 2024-10-22 17:45 | XAU_CP (bull_flag) | A | BUY | trending_up | +35/58 | +1.00 (+2) | +18.50 (+0) | +0.35 (-1) | +27.02 (+1) | +0.13 (+2) | +0.12 (+1) | +2.01 (+1) | | | -1.08 | +| 2024-11-06 04:50 | XAU_CP (inv_hs) | B | BUY | ranging | +24/40 | +0.00 (+0) | +8.85 (+0) | +0.46 (-1) | +17.57 (+0) | +0.28 (+2) | +0.31 (+1) | +1.46 (+1) | +1.00 (+1) | | -1.08 | +| 2024-11-08 14:10 | XAU_CP (bull_flag) | A | BUY | transitioning | +32/60 | +1.00 (+2) | +19.99 (+0) | +0.59 (-1) | +21.16 (+0) | +0.33 (+2) | +0.43 (+1) | +2.73 (+1) | | | -1.08 | +| 2024-12-02 14:15 | XAU_CP (bull_flag) | B | BUY | ranging | +14/36 | +1.00 (+2) | +17.74 (-1) | +0.53 (-1) | +18.08 (+0) | +0.15 (+2) | +0.14 (+0) | +1.97 (+1) | | | -1.08 | +| 2024-12-09 15:15 | XAU_CP (bull_flag) | B | BUY | trending_up | +28/47 | +1.00 (+2) | +31.38 (+0) | +0.42 (-1) | +43.81 (+1) | +0.21 (+0) | +0.38 (+1) | +2.69 (+1) | | | -1.08 | +| 2024-12-26 15:50 | XAU_CP (bull_flag) | B | BUY | trending_up | +35/55 | +1.00 (+2) | +29.64 (+0) | +0.42 (-1) | +35.94 (+1) | +0.41 (+2) | +0.30 (+1) | +3.30 (+1) | | | -1.08 | +| 2025-03-25 14:05 | XAU_CP (bull_flag) | B | BUY | trending_up | +43/41 | +1.00 (+2) | +28.08 (+0) | +0.58 (-1) | +26.66 (+1) | +0.40 (+2) | +0.31 (+1) | +3.17 (+1) | | | -1.07 | +| 2025-04-09 19:45 | XAU_ATR | A | BUY | transitioning | +31/65 | +1.00 (+2) | +17.01 (+0) | +0.41 (-1) | +24.65 (+0) | +0.37 (+2) | +1.01 (+1) | +2.11 (+1) | +1.00 (+1) | +2.00 (+1) | -1.04 | +| 2025-04-10 05:05 | XAU_CP (double_bottom) | B | BUY | trending_up | +18/60 | +1.00 (+2) | +14.04 (+1) | +0.59 (-1) | +41.41 (+1) | -0.15 (+0) | +0.14 (+1) | +1.47 (+0) | +1.00 (-1) | | -1.05 | +| 2025-04-16 22:10 | GENERIC | A | BUY | trending_up | +37/60 | +1.00 (+2) | +27.80 (+0) | +0.79 (-1) | +26.61 (+1) | +0.41 (+2) | +0.50 (+1) | +4.20 (+1) | | +1.00 (+1) | -1.03 | +| 2025-04-22 05:00 | GENERIC | C | BUY | trending_up | +25/51 | +1.00 (+2) | +12.19 (+1) | -0.06 (+0) | +41.38 (+1) | -0.38 (+0) | +0.02 (+1) | +0.73 (+0) | | | -1.07 | +| 2025-04-23 06:55 | GENERIC | B | SELL | trending_down | +32/54 | +1.00 (-2) | +22.24 (+0) | +0.51 (+1) | +41.78 (-1) | +0.13 (-1) | +0.35 (-1) | +3.09 (-1) | | -2.00 (-1) | -1.02 | +| 2025-04-24 22:30 | GENERIC | A | BUY | trending_up | +37/59 | +1.00 (+2) | +33.05 (+0) | +0.70 (-1) | +46.34 (+1) | +0.39 (+2) | +0.53 (+1) | +4.25 (+1) | | +1.00 (+1) | -1.02 | +| 2025-04-27 22:15 | GENERIC | A | BUY | trending_up | +36/56 | +1.00 (+2) | +28.40 (+0) | +0.55 (-1) | +54.48 (+1) | +0.33 (+2) | +0.53 (+1) | +3.82 (+1) | | +1.00 (+1) | -1.02 | +| 2025-04-28 15:20 | XAU_CP (bull_flag) | B | BUY | trending_up | +25/48 | +1.00 (+2) | +25.67 (+0) | +0.32 (-1) | +39.47 (+1) | +0.24 (+0) | +0.68 (+1) | +3.01 (+1) | | | -1.05 | +| 2025-04-28 18:50 | GENERIC | B | BUY | trending_up | +31/58 | +1.00 (+2) | +21.76 (+0) | +0.43 (-1) | +34.69 (+1) | +0.20 (+2) | +0.41 (+1) | +2.28 (+1) | +1.00 (-1) | +1.00 (+1) | -1.04 | +| 2025-05-06 20:55 | GENERIC | A | BUY | trending_up | +41/60 | +1.00 (+2) | +26.50 (+0) | +0.57 (-1) | +31.62 (+1) | +0.33 (+2) | +0.34 (+1) | +3.85 (+1) | | +1.00 (+1) | -1.04 | +| 2025-05-13 05:15 | XAU_CP (bull_flag) | B | BUY | trending | +25/55 | +0.50 (+0) | +33.63 (-1) | +0.57 (-1) | +38.02 (+1) | +0.54 (+2) | +0.74 (+1) | +4.77 (+1) | | +1.00 (+1) | -1.05 | +| 2025-05-16 12:10 | GENERIC | B | SELL | trending_down | +30/54 | +1.00 (-2) | +20.02 (+0) | +0.35 (+1) | +49.72 (-1) | +0.13 (-2) | +0.51 (-1) | +2.45 (-1) | -1.00 (+1) | | -1.02 | +| 2025-05-21 05:55 | GENERIC | A | BUY | transitioning | +32/69 | +1.00 (+2) | +23.49 (-1) | +0.69 (-1) | +23.44 (+0) | +0.39 (+2) | +0.48 (+1) | +3.10 (+1) | | | -1.03 | +| 2025-05-21 19:30 | XAU_CP (bull_flag) | B | BUY | transitioning | +30/67 | +1.00 (+2) | +24.56 (-1) | +0.50 (-1) | +20.86 (+0) | +0.31 (+2) | +0.28 (+1) | +2.56 (+1) | | | -1.08 | +| 2025-05-28 08:15 | XAU_CP (bull_flag) | A | BUY | trending_up | +38/56 | +1.00 (+2) | +22.27 (+0) | +0.46 (-1) | +47.44 (+1) | +0.26 (+2) | +0.39 (+1) | +2.66 (+1) | | | -1.08 | +| 2025-06-02 12:25 | XAU_CP (bull_flag) | A | BUY | trending_up | +38/55 | +1.00 (+2) | +18.99 (+0) | +0.47 (-1) | +33.76 (+1) | +0.21 (+2) | +0.20 (+1) | +2.23 (+1) | | | -1.06 | +| 2025-06-03 14:10 | GENERIC | B | SELL | trending_down | +35/54 | +1.00 (-2) | +20.43 (+0) | +0.48 (+1) | +32.72 (-1) | +0.47 (-2) | +0.64 (+0) | +2.54 (-1) | | | -1.03 | +| 2025-06-06 04:50 | XAU_CP (bull_flag) | B | BUY | trending_up | +40/39 | +1.00 (+2) | +20.30 (+0) | +0.31 (-1) | +27.03 (+1) | +0.07 (+2) | +0.12 (+1) | +1.83 (+1) | | | -1.08 | +| 2025-06-13 06:40 | XAU_CP (inv_hs) | B | BUY | ranging | +13/61 | +0.50 (+0) | +6.92 (+0) | +0.44 (-1) | +18.37 (+0) | +0.14 (+2) | +0.13 (+1) | +0.85 (+0) | | | -1.07 | +| 2025-07-14 07:30 | XAU_CP (bull_flag) | A | BUY | trending_up | +36/55 | +1.00 (+2) | +23.25 (+0) | +0.48 (-1) | +27.82 (+1) | +0.34 (+2) | +0.39 (+1) | +3.23 (+1) | | +1.00 (+1) | -1.07 | +| 2025-07-14 14:55 | GENERIC | B | SELL | trending_down | +34/52 | +1.00 (-2) | +17.02 (+0) | +0.45 (+1) | +54.01 (-1) | +0.24 (-2) | +0.30 (+0) | +2.27 (-1) | | | -1.03 | +| 2025-07-17 12:45 | GENERIC | B | SELL | trending_down | +30/59 | +1.00 (-2) | +24.27 (+0) | +0.75 (+1) | +31.17 (-1) | +0.29 (-2) | +0.35 (-1) | +3.08 (-1) | -1.00 (+1) | -1.00 (-1) | -1.03 | +| 2025-08-05 15:50 | XAU_CP (bull_flag) | B | BUY | trending_up | +20/53 | +1.00 (+2) | +17.80 (+0) | +0.35 (-1) | +42.84 (+1) | -0.15 (+0) | +0.11 (+1) | +1.97 (+1) | +1.00 (-1) | +1.00 (+1) | -1.08 | +| 2025-08-07 07:35 | GENERIC | B | BUY | trending_up | +33/51 | +1.00 (+2) | +32.53 (+0) | +0.48 (-1) | +43.71 (+1) | +0.45 (+2) | +0.27 (+0) | +3.98 (+1) | | | -1.03 | +| 2025-08-07 20:30 | XAU_CP (bull_flag) | B | BUY | trending_up | +25/50 | +1.00 (+2) | +27.04 (+0) | +0.29 (+0) | +30.42 (+1) | +0.22 (+0) | +0.12 (+1) | +2.27 (+1) | | | -1.08 | +| 2025-08-22 14:35 | GENERIC | A | BUY | trending_up | +37/60 | +1.00 (+2) | +36.01 (+0) | +0.51 (-1) | +50.07 (+1) | +0.75 (+2) | +1.18 (+1) | +5.04 (+1) | +1.00 (+1) | +1.00 (+1) | -0.01 | +| 2025-08-28 12:10 | XAU_CP (bull_flag) | B | BUY | trending_up | +38/55 | +1.00 (+2) | +21.43 (+0) | +0.42 (-1) | +35.24 (+1) | +0.31 (+2) | +0.26 (+1) | +2.80 (+1) | | | -1.08 | +| 2025-09-03 18:10 | GENERIC | B | BUY | trending_up | +31/55 | +1.00 (+2) | +24.49 (+0) | +0.48 (-1) | +39.40 (+1) | +0.04 (+1) | +0.15 (+1) | +2.59 (+1) | | +1.00 (+1) | -1.04 | +| 2025-09-04 05:00 | XAU_CP (inv_hs) | B | BUY | trending | +9/0 | -0.50 (+0) | +1.00 (+0) | +0.49 (-1) | +40.71 (-1) | +0.33 (+2) | -0.01 (-1) | +0.52 (+0) | | | -1.08 | +| 2025-09-09 05:40 | XAU_CP (bull_flag) | A | BUY | trending_up | +37/60 | +1.00 (+2) | +14.70 (+1) | +0.38 (-1) | +30.75 (+1) | +0.05 (+2) | +0.08 (+1) | +1.87 (+1) | | | -1.08 | +| 2025-09-12 14:05 | XAU_CP (inv_hs) | B | BUY | ranging | +9/25 | +1.00 (+2) | +13.89 (-1) | +0.31 (-1) | +15.85 (+0) | +0.19 (+2) | +0.09 (+1) | +1.22 (+0) | | +1.00 (+1) | -1.08 | +| 2025-09-17 18:10 | GENERIC | B | BUY | trending_up | +32/59 | +1.00 (+2) | +33.30 (+0) | +0.97 (-1) | +25.59 (-1) | +0.26 (+2) | +0.63 (+1) | +2.93 (+0) | | | -1.02 | +| 2025-09-22 18:25 | XAU_CP (bull_flag) | B | BUY | trending_up | +23/48 | +1.00 (+2) | +23.92 (+0) | +0.32 (-1) | +45.99 (+1) | -0.07 (+0) | +0.12 (+0) | +2.29 (+1) | | +1.00 (+1) | -1.08 | +| 2025-09-25 12:25 | XAU_CP (inv_hs) | B | BUY | ranging | +15/21 | +1.00 (+2) | +5.24 (+0) | +0.20 (+0) | +16.32 (+0) | +0.16 (+2) | +0.08 (+0) | +0.67 (+0) | | | -1.08 | +| 2025-09-30 14:55 | XAU_CP (bull_flag) | B | BUY | trending | +21/49 | +0.50 (+0) | +22.91 (-1) | +0.37 (-1) | +32.40 (+1) | +0.25 (+2) | +0.68 (+1) | +2.82 (+1) | | +1.00 (+1) | -1.04 | +| 2025-09-30 17:25 | XAU_CP (inv_hs) | B | BUY | ranging | -7/24 | +1.00 (+2) | +12.12 (-1) | +0.34 (-1) | +18.10 (+0) | -0.01 (+0) | +0.09 (+0) | +1.04 (+0) | | +0.00 (+0) | -1.08 | +| 2025-10-01 08:20 | GENERIC | A | BUY | trending_up | +39/58 | +1.00 (+2) | +29.73 (+0) | +0.55 (-1) | +31.76 (+1) | +0.57 (+2) | +0.57 (+1) | +4.37 (+1) | | +1.00 (+1) | -1.02 | +| 2025-10-06 05:10 | GENERIC | A | BUY | trending_up | +40/61 | +1.00 (+2) | +25.52 (+0) | +0.55 (-1) | +57.26 (+1) | +0.15 (+2) | +0.39 (+1) | +3.07 (+1) | | +1.00 (+1) | -1.03 | +| 2025-10-09 05:35 | XAU_CP (bull_flag) | B | BUY | ranging | +3/34 | +1.00 (+2) | +19.07 (-1) | +0.34 (-1) | +22.72 (+0) | -0.07 (+0) | +0.08 (+1) | +1.65 (+1) | | | -1.07 | +| 2025-10-14 05:20 | GENERIC | B | BUY | trending_up | +38/54 | +1.00 (+2) | +26.11 (+0) | +0.58 (-1) | +25.38 (+1) | +0.15 (+2) | +0.34 (+0) | +2.61 (+1) | | | -1.02 | +| 2025-10-15 08:10 | GENERIC | A | BUY | trending_up | +40/59 | +1.00 (+2) | +32.15 (+0) | +0.55 (-1) | +27.48 (+1) | +0.23 (+2) | +0.34 (+1) | +3.33 (+1) | | +1.00 (+1) | -1.02 | +| 2025-10-17 04:05 | GENERIC | B | BUY | transitioning | +29/72 | +1.00 (+2) | +20.77 (-1) | +0.49 (-1) | +23.22 (+0) | +0.26 (+2) | +0.34 (+1) | +2.45 (+1) | | | -1.02 | +| 2025-10-17 05:00 | XAU_CP (double_bottom) | B | BUY | ranging | -4/48 | +1.00 (+2) | +15.91 (-1) | +0.52 (-1) | +18.94 (+0) | +0.01 (+1) | +0.05 (+1) | +2.03 (+1) | +1.00 (-1) | | -1.03 | +| 2025-10-21 03:40 | XAU_CP (inv_hs) | B | BUY | ranging | +22/33 | +0.00 (+0) | +2.55 (+0) | +0.26 (+0) | +21.77 (+0) | +0.25 (+2) | +0.28 (+1) | +0.53 (+0) | +1.00 (+1) | | -1.05 | +| 2025-10-23 14:10 | GENERIC | B | BUY | trending_up | +35/53 | +1.00 (+2) | +15.86 (+0) | +0.39 (-1) | +27.39 (+1) | +0.10 (+2) | +0.31 (+1) | +1.83 (+1) | | | -1.01 | +| 2025-10-23 18:15 | XAU_CP (double_bottom) | B | BUY | ranging | +18/34 | +0.50 (+0) | +4.92 (+0) | +0.42 (-1) | +23.85 (+0) | +0.15 (+2) | +0.33 (+1) | +0.80 (+0) | +1.00 (+1) | +1.00 (+1) | -1.04 | +| 2025-10-24 14:15 | XAU_CP (bull_flag) | B | BUY | trending | +15/30 | +0.50 (+0) | +24.15 (-1) | +0.45 (-1) | +43.08 (+1) | +0.04 (+1) | +0.41 (+1) | +2.69 (+1) | | | -1.18 | +| 2025-10-30 20:05 | XAU_CP (bull_flag) | B | BUY | trending_up | +21/26 | +1.00 (+2) | +15.88 (+0) | +0.29 (+0) | +25.73 (+1) | -0.05 (+0) | +0.02 (+1) | +1.40 (+0) | | +1.00 (+1) | -1.07 | +| 2025-10-31 12:50 | GENERIC | A | BUY | trending_up | +36/60 | +1.00 (+2) | +22.69 (+0) | +0.55 (-1) | +27.66 (+1) | +0.47 (+2) | +0.54 (+1) | +2.98 (+1) | | | -1.03 | +| 2025-11-03 15:15 | GENERIC | B | BUY | trending_up | +32/52 | +1.00 (+2) | +16.98 (+0) | +0.43 (-1) | +27.07 (+1) | +0.20 (+2) | +0.26 (+1) | +1.47 (+0) | | | -1.04 | +| 2025-11-05 08:35 | GENERIC | C | BUY | trending_up | +27/53 | +1.00 (+2) | +14.30 (+1) | +0.32 (-1) | +33.40 (+1) | +0.25 (+0) | +0.29 (+1) | +1.58 (+1) | | | -1.03 | +| 2025-11-11 03:20 | GENERIC | A | BUY | trending_up | +40/59 | +1.00 (+2) | +16.16 (+0) | +0.62 (-1) | +34.85 (+1) | +0.15 (+2) | +0.27 (+1) | +2.21 (+1) | | | -1.03 | +| 2025-11-11 04:00 | GENERIC | C | BUY | trending_up | +26/54 | +1.00 (+2) | +13.25 (+1) | +0.30 (-1) | +40.00 (+1) | +0.05 (+0) | +0.14 (+0) | +1.57 (+1) | | +1.00 (+1) | -1.03 | +| 2025-11-12 18:30 | XAU_CP (double_bottom) | B | BUY | trending_up | +19/49 | +1.00 (+2) | +15.06 (+0) | +0.49 (-1) | +45.47 (+1) | -0.16 (+0) | +0.06 (+0) | +1.61 (+1) | +1.00 (-1) | | -1.03 | +| 2025-11-14 03:25 | XAU_CP (bull_flag) | B | BUY | trending | +17/29 | +0.50 (+0) | +21.20 (-1) | +0.37 (-1) | +39.69 (+1) | +0.09 (+2) | +0.18 (+0) | +2.10 (+1) | | | -1.07 | +| 2025-11-19 15:00 | GENERIC | C | BUY | ranging | +28/66 | +1.00 (+2) | +15.01 (-1) | +0.77 (-1) | +14.50 (+0) | +0.20 (+2) | +0.40 (+1) | +2.17 (+2) | | +1.00 (+1) | -1.03 | +| 2025-11-24 21:45 | XAU_CP (bull_flag) | B | BUY | trending_up | +23/52 | +1.00 (+2) | +29.57 (+0) | +0.53 (-1) | +51.36 (+1) | -0.05 (+0) | +0.22 (+1) | +3.03 (+1) | +1.00 (-1) | +1.00 (+1) | -1.06 | +| 2025-12-03 14:40 | GENERIC | B | BUY | trending_up | +28/51 | +1.00 (+2) | +11.96 (+1) | +0.31 (-1) | +37.04 (+1) | +0.03 (+2) | +0.18 (+0) | +1.35 (+0) | | | -1.03 | +| 2025-12-08 03:20 | XAU_CP (bull_flag) | B | BUY | ranging | +16/37 | +0.50 (+0) | +15.10 (-1) | +0.46 (-1) | +20.01 (+0) | +0.24 (+2) | +0.22 (+1) | +1.64 (+1) | | +1.00 (+1) | -1.08 | +| 2025-12-12 14:55 | XAU_CP (double_bottom) | B | BUY | ranging | -2/40 | +1.00 (+2) | +14.50 (-1) | +0.67 (-1) | +16.54 (+0) | +0.04 (+1) | +0.14 (+1) | +1.54 (+0) | | | -1.03 | +| 2025-12-12 20:10 | XAU_CP (inv_hs) | B | BUY | ranging | +5/42 | +0.00 (+0) | +10.85 (-1) | +0.58 (-1) | +10.27 (+0) | +0.12 (+2) | +0.13 (+1) | +1.29 (+0) | | +1.00 (+1) | -1.06 | +| 2025-12-19 14:40 | XAU_CP (double_bottom) | B | BUY | ranging | -1/37 | +1.00 (+2) | +10.55 (-1) | +0.52 (-1) | +14.44 (+0) | +0.06 (+1) | +0.10 (+1) | +1.30 (+0) | | | -1.05 | +| 2025-12-22 14:20 | XAU_CP (bull_flag) | B | BUY | trending_up | +35/50 | +1.00 (+2) | +16.35 (+0) | +0.36 (-1) | +27.17 (+1) | +0.11 (+2) | +0.20 (+0) | +1.70 (+1) | | | -1.07 | +| 2025-12-24 15:45 | GENERIC | B | SELL | trending_down | +32/54 | +1.00 (-2) | +18.47 (+0) | +0.52 (+1) | +44.48 (-1) | +0.18 (-2) | +0.27 (+0) | +2.24 (-1) | -1.00 (-1) | | -1.02 | +| 2026-01-05 19:00 | XAU_CP (double_bottom) | B | BUY | ranging | +3/36 | +1.00 (+2) | +7.10 (+0) | +0.48 (-1) | +9.67 (+0) | +0.04 (+1) | +0.07 (+0) | +0.85 (+0) | | | -1.05 | +| 2026-01-08 21:00 | GENERIC | B | BUY | trending_up | +30/51 | +1.00 (+2) | +22.66 (+0) | +0.36 (-1) | +37.50 (+1) | +0.46 (+0) | +0.43 (+1) | +2.73 (+1) | +1.00 (+1) | | -1.03 | +| 2026-01-13 14:50 | GENERIC | B | BUY | trending_up | +29/55 | +1.00 (+2) | +23.19 (+0) | +0.61 (-1) | +39.89 (+1) | +0.12 (+2) | +0.48 (+1) | +2.96 (+1) | +1.00 (-1) | | -1.02 | +| 2026-01-15 03:30 | GENERIC | B | SELL | trending_down | +36/54 | +1.00 (-2) | +18.63 (+0) | +0.51 (+1) | +35.24 (-1) | +0.28 (-2) | +0.45 (+0) | +2.10 (-1) | | | -1.02 | +| 2026-01-21 06:30 | GENERIC | B | BUY | trending_up | +30/51 | +1.00 (+2) | +22.05 (+0) | +0.60 (-1) | +36.19 (+1) | -0.03 (+0) | +0.20 (+1) | +2.14 (+1) | | | -1.02 | +| 2026-01-26 06:55 | GENERIC | B | BUY | transitioning | +29/59 | +1.00 (+2) | +17.05 (+0) | +0.48 (-1) | +23.57 (+0) | +0.42 (+2) | +0.39 (+0) | +2.24 (+1) | | | -1.02 | +| 2026-01-27 04:05 | GENERIC | B | BUY | ranging | +29/59 | +1.00 (+2) | +8.36 (+0) | +0.26 (+0) | +7.19 (+0) | +0.01 (+2) | +0.21 (+1) | +0.80 (+2) | | | -1.05 | +| 2026-01-28 08:35 | GENERIC | B | BUY | trending_up | +40/54 | +1.00 (+2) | +30.26 (+0) | +0.49 (-1) | +40.92 (+1) | +0.10 (+2) | +0.45 (+1) | +2.45 (+1) | | | -1.02 | +| 2026-01-29 21:30 | XAU_CP (bull_flag) | B | BUY | trending | +4/22 | +0.50 (+0) | +20.47 (-1) | +0.35 (-1) | +28.64 (+1) | +0.34 (+0) | +1.19 (+0) | +2.27 (+1) | | +1.00 (+1) | -1.02 | +| 2026-02-02 06:50 | GENERIC | B | SELL | trending_down | +36/52 | +1.00 (-2) | +19.15 (+0) | +0.35 (+1) | +57.06 (-1) | +0.24 (+0) | +2.41 (-2) | +2.15 (-1) | | | -1.00 | +| 2026-02-03 07:40 | XAU_ATR | A | BUY | trending_up | +39/59 | +1.00 (+2) | +27.24 (+0) | +0.52 (-1) | +42.97 (+1) | +0.24 (+2) | +1.17 (+1) | +3.55 (+1) | | +1.00 (+1) | -1.02 | +| 2026-02-04 12:35 | XAU_CP (inv_hs) | B | BUY | trending | +29/40 | +0.50 (+0) | +11.30 (+0) | +0.59 (-1) | +26.88 (+1) | +0.30 (+2) | +0.60 (+1) | +1.67 (+1) | | +1.00 (+1) | -1.02 | +| 2026-02-10 04:50 | XAU_CP (double_bottom) | B | BUY | ranging | +7/38 | -0.50 (+0) | +4.07 (+0) | +0.59 (-1) | +22.19 (+0) | +0.10 (+2) | +0.06 (+1) | +0.62 (+0) | | | -1.03 | +| 2026-02-10 14:35 | XAU_CP (bull_flag) | B | BUY | transitioning | +38/40 | +1.00 (+2) | +18.48 (+0) | +0.47 (-1) | +24.35 (+0) | +0.35 (+2) | +0.44 (+1) | +2.33 (+1) | | | -1.05 | +| 2026-02-12 19:50 | XAU_CP (inv_hs) | B | BUY | ranging | +17/33 | +0.00 (+0) | +4.15 (+0) | +0.42 (-1) | +21.09 (+0) | +0.30 (+2) | +0.37 (+1) | +0.82 (+0) | +1.00 (+1) | +1.00 (+1) | -1.02 | +| 2026-02-13 04:20 | XAU_CP (double_bottom) | B | BUY | trending | +4/25 | +0.50 (+0) | +15.70 (+0) | +0.65 (-1) | +28.32 (+1) | -0.01 (+0) | +0.29 (+1) | +1.65 (+0) | | | -1.03 | +| 2026-02-13 19:25 | GENERIC | B | BUY | trending_up | +28/59 | +1.00 (+2) | +16.04 (+0) | +0.47 (-1) | +35.73 (+1) | +0.13 (+2) | +0.30 (+0) | +1.88 (+1) | +1.00 (-1) | | -1.12 | +| 2026-02-16 15:10 | GENERIC | B | SELL | trending_down | +30/56 | +1.00 (-2) | +19.21 (+0) | +0.56 (+1) | +38.66 (-1) | +0.26 (-2) | +0.35 (+0) | +2.08 (-1) | -1.00 (+1) | | -1.04 | +| 2026-02-20 14:55 | GENERIC | B | BUY | transitioning | +27/58 | +1.00 (+2) | +24.61 (-1) | +0.47 (-1) | +21.69 (+0) | +0.23 (+2) | +0.37 (+1) | +2.21 (+1) | | | -1.02 | +| 2026-02-24 05:55 | XAU_CP (double_bottom) | B | BUY | trending | +23/38 | +0.00 (+0) | +9.09 (+0) | +0.75 (-1) | +30.00 (+1) | +0.25 (+2) | +0.19 (+1) | +1.27 (+0) | | | -1.03 | +| 2026-02-25 17:10 | GENERIC | B | BUY | transitioning | +28/58 | +1.00 (+2) | +17.72 (+0) | +0.40 (-1) | +18.15 (+0) | +0.18 (+2) | +0.34 (+0) | +1.94 (+1) | | | -1.02 | +| 2026-02-26 20:15 | GENERIC | A | BUY | transitioning | +31/59 | +1.00 (+2) | +13.98 (+0) | +0.49 (-1) | +18.72 (+0) | +0.16 (+2) | +0.21 (+1) | +1.79 (+1) | | | -1.03 | +| 2026-03-02 07:35 | GENERIC | B | BUY | trending_up | +29/51 | +1.00 (+2) | +23.33 (+0) | +0.34 (-1) | +44.16 (+1) | +0.30 (+0) | +0.62 (+1) | +2.49 (+1) | | | -1.02 | +| 2026-03-02 08:35 | GENERIC | C | BUY | trending_up | +28/45 | +1.00 (+2) | +14.65 (+1) | +0.25 (+0) | +42.91 (+1) | -0.13 (+0) | +0.21 (+1) | +1.34 (+0) | | | -1.03 | +| 2026-03-03 15:20 | GENERIC | B | SELL | trending_down | +35/50 | +1.00 (-2) | +24.35 (+0) | +0.39 (+1) | +47.11 (-1) | +0.25 (+0) | +2.36 (-2) | +2.28 (-1) | | | -1.00 | +| 2026-03-06 15:25 | XAU_ATR | A | BUY | transitioning | +33/64 | +1.00 (+2) | +24.51 (-1) | +0.52 (-1) | +23.88 (+0) | +0.41 (+2) | +1.15 (+1) | +2.89 (+1) | | +1.00 (+1) | -1.02 | +| 2026-03-10 06:00 | GENERIC | A | BUY | trending_up | +37/61 | +1.00 (+2) | +16.30 (+0) | +0.54 (-1) | +29.88 (+1) | +0.22 (+2) | +0.20 (+1) | +1.87 (+1) | | | -1.04 | +| 2026-03-12 20:35 | GENERIC | B | SELL | trending_down | +32/54 | +1.00 (-2) | +23.40 (+0) | +0.62 (+1) | +59.86 (-1) | +0.14 (-2) | +0.40 (+0) | +2.23 (-1) | -1.00 (+1) | +0.00 (+0) | -1.02 | +| 2026-03-13 08:05 | GENERIC | B | SELL | trending_down | +30/54 | +1.00 (-2) | +22.31 (+0) | +0.75 (+1) | +43.85 (-1) | +0.09 (-1) | +0.43 (-1) | +2.83 (-1) | | -1.00 (-1) | -1.02 | +| 2026-03-17 05:15 | GENERIC | A | BUY | trending_up | +36/56 | +1.00 (+2) | +16.67 (+0) | +0.45 (-1) | +33.11 (+1) | +0.05 (+2) | +0.22 (+1) | +1.73 (+1) | | | -1.03 | +| 2026-03-25 07:20 | XAU_CP (inv_hs) | B | BUY | trending | +22/30 | +0.00 (+0) | +7.12 (+0) | +0.37 (-1) | +34.57 (-1) | +0.29 (+2) | +0.25 (+1) | +1.23 (+0) | | +1.00 (+1) | -1.03 | +| 2026-03-27 06:15 | GENERIC | A | BUY | trending_up | +37/58 | +1.00 (+2) | +18.33 (+0) | +0.45 (-1) | +41.33 (+1) | +0.10 (+2) | +0.61 (+1) | +2.31 (+1) | | | -1.02 | +| 2026-03-27 19:30 | XAU_CP (inv_hs) | B | BUY | ranging | +13/55 | +1.00 (+2) | +10.69 (-1) | +0.30 (-1) | +23.51 (+0) | +0.13 (+2) | +0.10 (+0) | +1.21 (+0) | | +1.00 (+1) | -1.03 | +| 2026-03-31 05:05 | XAU_CP (inv_hs) | B | BUY | ranging | +12/30 | +1.00 (+2) | +5.79 (+0) | +0.49 (-1) | +18.60 (+0) | +0.16 (+2) | +0.26 (+1) | +0.78 (+0) | | +2.00 (+1) | -1.04 | +| 2026-03-31 05:40 | GENERIC | C | BUY | ranging | +26/78 | +1.00 (+2) | +10.36 (-1) | +0.62 (-1) | +17.59 (+0) | +0.21 (+2) | +0.35 (+1) | +1.58 (+2) | +1.00 (+1) | | -1.03 | +| 2026-04-02 20:05 | GENERIC | B | BUY | trending_up | +32/59 | +1.00 (+2) | +9.57 (+1) | +0.28 (+0) | +25.77 (+1) | +0.05 (+2) | +0.02 (+1) | +1.15 (+0) | | | -1.84 | +| 2026-04-13 17:40 | XAU_ATR | A | BUY | trending_up | +35/63 | +1.00 (+2) | +15.45 (+0) | +0.34 (-1) | +31.06 (+1) | +0.13 (+2) | +0.18 (+1) | +1.61 (+1) | | | -1.04 | +| 2026-04-20 22:20 | GENERIC | A | BUY | trending_up | +36/61 | +1.00 (+2) | +27.62 (+0) | +0.57 (-1) | +26.15 (+1) | +0.47 (+2) | +0.33 (+1) | +3.27 (+1) | | +1.00 (+1) | -1.03 | +| 2026-04-21 19:25 | XAU_CP (inv_hs) | B | BUY | ranging | +18/35 | -0.50 (+0) | +5.37 (+0) | +0.39 (-1) | +19.15 (+0) | +0.31 (+2) | +0.26 (+1) | +0.91 (+0) | +1.00 (+1) | +2.00 (+1) | -1.04 | +| 2026-05-03 22:45 | XAU_CP (inv_hs) | B | BUY | ranging | +14/37 | +0.50 (+0) | +11.54 (-1) | +0.47 (-1) | +16.73 (+0) | +0.31 (+2) | +0.25 (+0) | +1.67 (+1) | | | -1.04 | +| 2026-05-06 07:00 | XAU_CP (bull_flag) | B | BUY | trending_up | +26/51 | +1.00 (+2) | +16.40 (+0) | +0.38 (-1) | +42.22 (+1) | +0.07 (+2) | +0.13 (+0) | +1.78 (+1) | +1.00 (-1) | +1.00 (+1) | -1.05 | +| 2026-05-06 14:45 | GENERIC | B | BUY | trending_up | +32/58 | +1.00 (+2) | +14.48 (+1) | +0.31 (-1) | +25.32 (+1) | +0.15 (+2) | +0.20 (+1) | +1.40 (+0) | | | -1.03 | +| 2026-05-12 12:55 | XAU_CP (double_bottom) | B | BUY | ranging | +10/29 | +0.50 (+0) | +9.80 (+0) | +0.41 (-1) | +14.24 (+0) | +0.10 (+2) | +0.11 (+1) | +1.23 (+0) | | | -1.03 | +| 2026-05-18 08:55 | GENERIC | A | BUY | transitioning | +31/65 | +0.50 (+0) | +11.76 (+0) | +0.47 (-1) | +17.94 (+0) | +0.09 (+2) | +0.21 (+1) | +1.36 (+2) | | | -1.03 | +| 2026-05-19 03:40 | XAU_CP (inv_hs) | B | BUY | trending | +22/31 | +0.00 (+0) | -1.77 (+0) | +0.10 (+0) | +26.13 (-1) | +0.12 (+2) | +0.05 (+1) | +0.15 (+0) | | +1.00 (+1) | -1.06 | +| 2026-05-19 08:00 | XAU_CP (double_bottom) | B | BUY | ranging | -6/38 | +0.50 (+0) | +12.94 (-1) | +0.58 (-1) | +15.75 (+0) | +0.10 (+2) | +0.10 (+1) | +1.51 (+0) | +1.00 (-1) | | -1.03 | +| 2026-05-26 12:40 | GENERIC | B | SELL | trending_down | +36/53 | +1.00 (-2) | +17.48 (+0) | +0.35 (+1) | +34.84 (-1) | +0.14 (-2) | +0.23 (-1) | +1.95 (-1) | | -1.00 (-1) | -1.03 | +| 2026-06-01 05:20 | XAU_CP (double_bottom) | B | BUY | ranging | +16/38 | -0.50 (+0) | +7.13 (+0) | +0.64 (-1) | +23.13 (+0) | +0.31 (+2) | +0.13 (+1) | +1.25 (+0) | +1.00 (+1) | | -1.04 | +| 2026-06-02 07:00 | GENERIC | B | BUY | trending_up | +29/57 | +1.00 (+2) | +22.04 (+0) | +0.38 (-1) | +55.11 (+1) | +0.05 (+2) | +0.33 (+1) | +2.17 (+1) | +1.00 (-1) | +1.00 (+1) | -1.03 | +| 2026-06-02 22:10 | XAU_CP (inv_hs) | B | BUY | ranging | -6/34 | +0.00 (+0) | +10.43 (-1) | +0.52 (-1) | +12.77 (+0) | +0.11 (+2) | +0.04 (+1) | +1.34 (+0) | +1.00 (-1) | | -1.07 | +| 2026-06-11 17:45 | XAU_ATR | A | BUY | trending_up | +40/63 | +1.00 (+2) | +34.65 (+0) | +0.67 (-1) | +26.06 (+1) | +0.75 (+2) | +2.13 (+2) | +5.33 (+1) | | | -1.03 | +| 2026-06-18 17:55 | XAU_CP (inv_hs) | B | BUY | ranging | +9/30 | +0.00 (+0) | +5.05 (+0) | +0.49 (-1) | +17.20 (+0) | +0.32 (+2) | +0.20 (+0) | +0.94 (+0) | | | -1.05 | +| 2026-06-25 08:40 | XAU_CP (bull_flag) | B | BUY | transitioning | +20/59 | +0.50 (+0) | +15.26 (+0) | +0.49 (-1) | +18.75 (+0) | +0.33 (+2) | +0.60 (+0) | +1.85 (+1) | | +1.00 (+1) | -1.05 | +| 2026-06-26 15:50 | GENERIC | B | BUY | trending_up | +28/57 | +1.00 (+2) | +18.75 (+0) | +0.51 (-1) | +31.65 (+1) | +0.02 (+1) | +0.35 (+1) | +1.79 (+1) | | +0.00 (+0) | -1.03 | +| 2026-06-30 08:05 | XAU_CP (bull_flag) | B | BUY | trending_up | +22/51 | +1.00 (+2) | +20.40 (+0) | +0.41 (-1) | +48.76 (+1) | -0.17 (+0) | +0.25 (+1) | +2.06 (+1) | | | -1.05 | +| 2026-07-01 14:35 | XAU_CP (bull_flag) | B | BUY | trending_up | +24/43 | +1.00 (+2) | +28.07 (+0) | +0.33 (-1) | +61.44 (+1) | +0.21 (+0) | +0.31 (+0) | +3.08 (+1) | | +1.00 (+1) | -1.02 | +| 2026-07-06 07:45 | XAU_CP (inv_hs) | B | BUY | ranging | +16/40 | +0.00 (+0) | +13.57 (-1) | +0.69 (-1) | +23.57 (+0) | +0.30 (+2) | +0.29 (+1) | +1.89 (+1) | | +1.00 (+1) | -1.03 | +| 2026-07-13 18:55 | XAU_CP (double_bottom) | B | BUY | trending | +25/34 | +0.00 (+0) | +7.33 (+0) | +0.46 (-1) | +30.23 (+1) | +0.41 (+2) | +0.33 (+1) | +1.22 (+0) | +1.00 (+1) | | -1.03 | +| 2026-07-22 03:15 | GENERIC | B | BUY | trending_up | +29/54 | +1.00 (+2) | +25.11 (+0) | +0.52 (-1) | +61.73 (+1) | +0.04 (+2) | +0.30 (+0) | +2.48 (+1) | +1.00 (-1) | +0.00 (+0) | -1.03 | +| 2026-07-24 15:40 | GENERIC | B | BUY | trending_up | +28/57 | +1.00 (+2) | +17.48 (+0) | +0.46 (-1) | +36.38 (+1) | +0.20 (+2) | +0.37 (+1) | +1.78 (+1) | +1.00 (-1) | | -1.03 | +| 2026-07-29 06:35 | GENERIC | B | BUY | trending_up | +33/55 | +1.00 (+2) | +18.79 (+0) | +0.51 (-1) | +43.50 (+1) | +0.25 (+2) | +0.11 (+0) | +2.41 (+1) | | +1.00 (+1) | -1.03 | +| 2026-07-30 05:50 | GENERIC | B | SELL | trending_down | +31/55 | +1.00 (-2) | +23.06 (+0) | +0.53 (+1) | +49.98 (-1) | +0.14 (-2) | +0.34 (+0) | +2.33 (-1) | | | -1.03 | +| 2026-07-30 14:05 | XAU_ATR | A | BUY | trending_up | +40/56 | +1.00 (+2) | +19.24 (+0) | +0.50 (-1) | +39.62 (+1) | +0.39 (+2) | +0.76 (+0) | +2.18 (+1) | | | -1.03 | +| 2026-07-30 20:05 | GENERIC | A | BUY | transitioning | +31/64 | +1.00 (+2) | +15.51 (+0) | +0.46 (-1) | +22.45 (+0) | +0.21 (+2) | +0.31 (+1) | +2.10 (+1) | +1.00 (+1) | | -1.03 | +| 2026-08-04 08:05 | XAU_CP (inv_hs) | B | BUY | ranging | +17/39 | +1.00 (+2) | +14.09 (-1) | +0.60 (-1) | +11.51 (+0) | +0.13 (+2) | +0.22 (+1) | +1.70 (+1) | | | -1.06 | +| 2026-08-04 17:05 | GENERIC | A | BUY | trending_up | +42/61 | +1.00 (+2) | +21.70 (+0) | +0.73 (-1) | +25.53 (+1) | +0.26 (+2) | +0.42 (+1) | +3.09 (+1) | | +1.00 (+1) | -1.03 | +| 2026-08-05 06:40 | XAU_CP (bull_flag) | B | BUY | trending_up | +35/50 | +1.00 (+2) | +24.58 (+0) | +0.34 (-1) | +52.31 (+1) | +0.13 (+2) | +0.26 (+0) | +2.69 (+1) | | | -1.04 | +| 2026-08-07 12:40 | GENERIC | B | BUY | trending_up | +29/61 | +1.00 (+2) | +29.17 (+0) | +0.75 (-1) | +28.67 (+1) | +0.34 (+2) | +0.95 (+1) | +4.06 (+1) | +1.00 (-1) | | -0.28 | +| 2026-08-07 15:15 | XAU_CP (inv_hs) | B | BUY | ranging | +10/26 | +1.00 (+2) | +8.65 (+0) | +0.27 (+0) | +11.18 (+0) | +0.00 (+1) | +0.09 (+1) | +1.01 (+0) | | | -1.03 | +| 2026-08-12 03:10 | GENERIC | B | BUY | trending_up | +33/55 | +1.00 (+2) | +24.59 (+0) | +0.42 (-1) | +41.86 (+1) | +0.14 (+2) | +0.27 (+1) | +2.51 (+1) | +1.00 (-1) | +1.00 (+1) | -1.03 | +| 2026-08-12 14:20 | XAU_CP (inv_hs) | B | BUY | ranging | +8/32 | +1.00 (+2) | +11.94 (-1) | +0.38 (-1) | +11.54 (+0) | +0.12 (+2) | +0.40 (+1) | +1.33 (+0) | | | -1.02 | +| 2026-08-19 20:55 | GENERIC | A | BUY | trending_up | +35/56 | +1.00 (+2) | +23.02 (+0) | +0.47 (-1) | +37.89 (+1) | +0.12 (+2) | +0.32 (+1) | +2.48 (+1) | | +1.00 (+1) | -1.03 | +| 2026-08-20 12:35 | GENERIC | B | SELL | trending_down | +33/51 | +1.00 (-2) | +22.78 (+0) | +0.43 (+1) | +34.84 (-1) | +0.25 (-2) | +0.48 (+0) | +2.76 (-1) | | | -1.02 | +| 2026-08-26 15:20 | GENERIC | B | SELL | trending_down | +40/55 | +1.00 (-2) | +20.64 (+0) | +0.49 (+1) | +26.53 (-1) | +0.37 (-2) | +0.56 (+0) | +2.13 (-1) | | | -1.02 | +| 2026-08-31 08:05 | XAU_CP (bull_flag) | B | BUY | trending | +20/37 | +0.50 (+0) | +13.73 (+0) | +0.48 (-1) | +30.30 (+1) | +0.03 (+1) | +0.28 (+1) | +1.38 (+2) | +1.00 (-1) | | -1.06 | +| 2026-08-31 14:25 | GENERIC | B | SELL | trending_down | +30/55 | +1.00 (-2) | +18.49 (+0) | +0.41 (+1) | +36.73 (-1) | +0.13 (-2) | +0.44 (-1) | +1.96 (-1) | -1.00 (+1) | | -1.02 | +| 2026-09-02 08:10 | XAU_CP (inv_hs) | B | BUY | ranging | -14/35 | +0.50 (+0) | +12.23 (-1) | +0.39 (-1) | +19.76 (+0) | -0.05 (+0) | +0.04 (+1) | +1.13 (+0) | +1.00 (-1) | | -1.05 | +| 2026-09-03 08:30 | GENERIC | B | BUY | transitioning | +25/54 | +1.00 (+2) | +10.52 (+0) | +0.42 (-1) | +19.46 (+0) | +0.22 (+2) | +0.24 (+1) | +1.22 (+0) | | +1.00 (+1) | -1.04 | +| 2026-09-04 05:40 | XAU_CP (inv_hs) | B | BUY | ranging | +9/37 | +0.50 (+0) | +7.39 (+0) | +0.51 (-1) | +13.54 (+0) | +0.10 (+2) | +0.07 (+1) | +1.12 (+0) | | | -1.06 | +| 2026-09-04 15:15 | XAU_CP (double_bottom) | B | BUY | ranging | +9/23 | +0.00 (+0) | +9.02 (+0) | +0.48 (-1) | +14.40 (+0) | +0.20 (+2) | +0.28 (+1) | +1.34 (+0) | | | -0.39 | + +## 15min: 931 trades — 162 wrong-direction losers (17%), 206 other losers, 563 winners + +Wrong direction = a losing trade whose best excursion never reached a quarter of the initial risk. + +### 1. Where the wrong-direction trades came from (15min) + +| Setup type | Trades | Wrong-direction | Rate | Share of all wrong-direction | Other losers | Winners | Net R | +|---|---|---|---|---|---|---|---| +| GENERIC | 757 | 120 | 16% | 74% | 169 | 468 | +140.5 | +| XAU_ATR | 174 | 42 | 24% | 26% | 37 | 95 | +5.7 | + +By grade and by regime (15min): + +| Grade | Trades | Wrong-direction rate | | Regime | Trades | Wrong-direction rate | +|---|---|---|---|---|---|---| +| A | 450 | 18% | ranging | 120 | 15% | +| A+ | 19 | 32% | transitioning | 213 | 15% | +| B | 402 | 15% | trending_down | 219 | 21% | +| C | 60 | 20% | trending_up | 379 | 18% | + +By direction: BUY: 635 trades, 16% wrong-direction; SELL: 296 trades, 20% wrong-direction. + +### 2. The signal itself at entry (15min): wrong-direction losers vs winners + +Values signed in the trade's direction where a sign applies (positive = in the trade's favour). The separation column is the probability that a random winner shows a higher value than a random wrong-direction loser: 0.50 means the measure does not separate them at all. + +| Measure | Wrong-direction median | Winner median | Other-loser median | Separation | +|---|---|---|---|---| +| Composite score of the primary timeframe (in trade direction) | +36.20 | +35.85 | +36.41 | 0.48 | +| Final score after the multi-timeframe blend (in trade direction) | +33.38 | +32.89 | +33.27 | 0.47 | +| Final confidence | +60.01 | +60.41 | +60.10 | 0.52 | +| Composite score of confirmation 1 (in trade direction) | +28.13 | +28.12 | +26.84 | 0.50 | +| Composite score of confirmation 2 (in trade direction) | +19.83 | +20.82 | +19.74 | 0.51 | +| Indicators agreeing (count) | +5.00 | +5.00 | +5.00 | 0.48 | +| Indicators opposing (count) | +1.00 | +1.00 | +1.00 | 0.54 | +| Confidence penalty (points) | +14.41 | +14.00 | +14.00 | 0.50 | +| Exhaustion tags | +3.00 | +2.00 | +2.00 | 0.49 | +| Primary ATR as % of price | +0.19 | +0.18 | +0.16 | 0.46 | +| Prior 12-bar move in trade direction (ATR) | +2.69 | +2.92 | +2.95 | 0.54 | +| Hourly stretch (ATR) | +1.64 | +1.75 | +1.53 | 0.53 | +| Planned target 1 (R) | +1.00 | +1.00 | +1.00 | 0.54 | + +### 3a. Each indicator on the primary timeframe (15min) + +Agree = the indicator voted in the trade's direction; oppose = against. Value is signed in the trade's direction (RSI minus 50, Bollinger position minus 0.5, MACD and Keltner in ATR units, EMA alignment, rate of change in percent; ADX is unsigned strength). Contribution = the indicator's weighted score in the trade's favour. + +| Indicator | Present (wrong / win) | Agree % (wrong / win) | Oppose % (wrong / win) | Median value (wrong / win) | Median confidence (wrong / win) | Mean contribution (wrong / win) | Separation of value | +|---|---|---|---|---|---|---|---| +| EMA trend (alignment) | 100% / 100% | 95% / 95% | 0% / 1% | +1.00 / +1.00 | 90 / 90 | +2.49 / +2.62 | 0.55 | +| RSI | 100% / 100% | 12% / 9% | 12% / 20% | +18.78 / +20.21 | 30 / 30 | +0.00 / -0.05 | 0.52 | +| Bollinger position | 100% / 100% | 0% / 0% | 94% / 94% | +0.52 / +0.51 | 65 / 65 | -0.59 / -0.63 | 0.51 | +| ADX (trend strength) | 100% / 100% | 68% / 63% | 2% / 1% | +30.09 / +27.91 | 70 / 70 | +0.18 / +0.18 | 0.48 | +| MACD histogram | 100% / 100% | 95% / 92% | 0% / 0% | +0.19 / +0.22 | 70 / 70 | +3.96 / +3.86 | 0.53 | +| Rate of change | 100% / 100% | 83% / 87% | 0% / 0% | +0.47 / +0.47 | 54 / 54 | +1.07 / +1.12 | 0.52 | +| Keltner extension | 100% / 100% | 93% / 93% | 0% / 0% | +2.22 / +2.28 | 65 / 65 | +2.15 / +2.18 | 0.52 | +| Divergence | 31% / 30% | 41% / 34% | 55% / 64% | +1.00 / +1.00 | 70 / 70 | +0.12 / -0.06 | 0.53 | +| Candlestick | 27% / 32% | 81% / 80% | 2% / 4% | +1.00 / +1.00 | 50 / 50 | +0.33 / +0.33 | 0.52 | + +### 3b. Each indicator on the first confirmation timeframe + +Agree = the indicator voted in the trade's direction; oppose = against. Value is signed in the trade's direction (RSI minus 50, Bollinger position minus 0.5, MACD and Keltner in ATR units, EMA alignment, rate of change in percent; ADX is unsigned strength). Contribution = the indicator's weighted score in the trade's favour. + +| Indicator | Present (wrong / win) | Agree % (wrong / win) | Oppose % (wrong / win) | Median value (wrong / win) | Median confidence (wrong / win) | Mean contribution (wrong / win) | Separation of value | +|---|---|---|---|---|---|---|---| +| EMA trend (alignment) | 100% / 100% | 100% / 100% | 0% / 0% | +1.00 / +1.00 | 90 / 90 | +2.97 / +2.99 | 0.51 | +| RSI | 100% / 100% | 15% / 14% | 14% / 13% | +19.07 / +19.39 | 30 / 30 | -0.01 / -0.01 | 0.51 | +| Bollinger position | 100% / 100% | 0% / 0% | 85% / 87% | +0.44 / +0.44 | 45 / 45 | -0.48 / -0.48 | 0.49 | +| ADX (trend strength) | 100% / 100% | 75% / 76% | 1% / 0% | +31.67 / +31.41 | 70 / 70 | +0.21 / +0.21 | 0.50 | +| MACD histogram | 100% / 100% | 59% / 60% | 0% / 1% | +0.22 / +0.19 | 61 / 60 | +2.47 / +2.41 | 0.47 | +| Rate of change | 100% / 100% | 56% / 61% | 1% / 2% | +0.28 / +0.26 | 51 / 51 | +0.68 / +0.69 | 0.47 | +| Keltner extension | 100% / 100% | 79% / 81% | 0% / 0% | +2.06 / +2.08 | 65 / 65 | +1.36 / +1.38 | 0.51 | +| Divergence | 27% / 31% | 19% / 16% | 79% / 83% | +1.00 / +1.00 | 70 / 70 | -0.76 / -0.82 | 0.57 | +| Candlestick | 30% / 28% | 39% / 52% | 29% / 10% | +0.00 / +0.00 | 50 / 50 | +0.04 / +0.18 | 0.56 | + +### 3c. Each indicator on the second confirmation timeframe + +Agree = the indicator voted in the trade's direction; oppose = against. Value is signed in the trade's direction (RSI minus 50, Bollinger position minus 0.5, MACD and Keltner in ATR units, EMA alignment, rate of change in percent; ADX is unsigned strength). Contribution = the indicator's weighted score in the trade's favour. + +| Indicator | Present (wrong / win) | Agree % (wrong / win) | Oppose % (wrong / win) | Median value (wrong / win) | Median confidence (wrong / win) | Mean contribution (wrong / win) | Separation of value | +|---|---|---|---|---|---|---|---| +| EMA trend (alignment) | 100% / 100% | 66% / 76% | 12% / 10% | +0.50 / +1.00 | 65 / 90 | +1.28 / +1.49 | 0.57 | +| RSI | 100% / 100% | 12% / 10% | 23% / 26% | +12.73 / +13.85 | 30 / 30 | -0.12 / -0.10 | 0.54 | +| Bollinger position | 100% / 100% | 0% / 1% | 71% / 72% | +0.42 / +0.43 | 45 / 45 | -0.50 / -0.53 | 0.52 | +| ADX (trend strength) | 100% / 100% | 48% / 49% | 9% / 7% | +26.30 / +26.57 | 55 / 55 | +0.11 / +0.12 | 0.47 | +| MACD histogram | 100% / 100% | 75% / 69% | 2% / 2% | +0.15 / +0.16 | 63 / 63 | +2.69 / +2.49 | 0.50 | +| Rate of change | 100% / 100% | 76% / 73% | 2% / 5% | +0.56 / +0.57 | 55 / 55 | +0.87 / +0.84 | 0.49 | +| Keltner extension | 100% / 100% | 48% / 53% | 0% / 0% | +1.45 / +1.60 | 60 / 65 | +0.87 / +0.99 | 0.54 | +| Divergence | 27% / 30% | 48% / 40% | 52% / 57% | +1.00 / +1.00 | 70 / 70 | +0.06 / -0.18 | 0.57 | +| Candlestick | 31% / 31% | 78% / 75% | 8% / 6% | +1.00 / +1.00 | 50 / 50 | +0.33 / +0.32 | 0.52 | + +### 4. When the direction was wrong (15min) + +Base rate: 17% of all trades were wrong-direction losers. Cells under 20 trades are noise. + +| Entry hour (Coordinated Universal Time) | Trades | Wrong-direction | Rate | | Weekday | Trades | Wrong-direction | Rate | +|---|---|---|---|---|---|---|---|---| +| 03:00 | 74 | 8 | 11% | Mon | 169 | 29 | 17% | +| 04:00 | 28 | 5 | 18% | Tue | 212 | 33 | 16% | +| 05:00 | 61 | 12 | 20% | Wed | 189 | 42 | 22% | +| 06:00 | 79 | 16 | 20% | Thu | 179 | 27 | 15% | +| 07:00 | 68 | 13 | 19% | Fri | 169 | 29 | 17% | +| 08:00 | 77 | 13 | 17% | Sun | 13 | 2 | 15% | +| 09:00 | 75 | 12 | 16% | | | | | +| 12:00 | 68 | 11 | 16% | | | | | +| 14:00 | 154 | 25 | 16% | | | | | +| 15:00 | 65 | 9 | 14% | | | | | +| 17:00 | 34 | 5 | 15% | | | | | +| 18:00 | 45 | 12 | 27% | | | | | +| 19:00 | 38 | 6 | 16% | | | | | +| 20:00 | 34 | 8 | 24% | | | | | +| 22:00 | 31 | 7 | 23% | | | | | + +Weekday-hour cells with at least 10 trades and the highest wrong-direction rate (15min): + +| Day | Hour | Trades | Wrong-direction | Rate | +|---|---|---|---|---| +| Wed | 18:00 | 13 | 5 | 38% | +| Tue | 05:00 | 19 | 7 | 37% | +| Tue | 06:00 | 13 | 4 | 31% | +| Wed | 09:00 | 20 | 6 | 30% | +| Wed | 19:00 | 10 | 3 | 30% | +| Fri | 15:00 | 18 | 5 | 28% | + +Months with the highest wrong-direction rate: 2026-07 29% (11 of 38); 2026-02 28% (8 of 29); 2025-07 24% (11 of 46); 2025-04 24% (10 of 42). + +### Appendix 15min: every wrong-direction loser with its primary-timeframe indicator values + +Columns: entry time, setup (pattern), grade, direction, regime, final score in trade direction / confidence, then per indicator `signed value (vote)` where the vote is −2 strong sell … +2 strong buy; blank = the indicator did not vote. Values as defined in section 3. R = realised result. + +| Entry | Setup | Grade | Dir | Regime | Score/Conf | EMA | RSI | Bollinger | ADX | MACD | ROC | Keltner | Div | Candle | R | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 2024-09-11 07:00 | GENERIC | A | BUY | trending_up | +44/65 | +1.00 (+2) | +26.75 (+0) | +0.59 (-1) | +32.23 (+1) | +0.27 (+2) | +0.29 (+1) | +2.40 (+1) | | | -1.06 | +| 2024-09-19 09:30 | GENERIC | A | BUY | trending_up | +37/60 | +1.00 (+2) | +29.79 (+0) | +0.46 (-1) | +39.62 (+1) | +0.26 (+2) | +0.58 (+1) | +3.55 (+1) | +1.00 (-1) | | -1.03 | +| 2024-09-24 05:00 | GENERIC | A | BUY | trending_up | +40/64 | +1.00 (+2) | +25.13 (+0) | +0.51 (-1) | +26.94 (+1) | +0.43 (+2) | +0.40 (+1) | +3.08 (+1) | | | -1.05 | +| 2024-09-25 14:30 | GENERIC | B | BUY | transitioning | +30/66 | +1.00 (+2) | +16.25 (+0) | +0.50 (-1) | +17.01 (+0) | +0.23 (+2) | +0.32 (+1) | +1.58 (+1) | | | -1.07 | +| 2024-09-30 19:15 | GENERIC | B | SELL | trending_down | +32/57 | +1.00 (-2) | +11.98 (-1) | +0.38 (+1) | +27.91 (-1) | +0.09 (-2) | +0.35 (-1) | +1.44 (+0) | -1.00 (+1) | | -1.06 | +| 2024-10-01 17:15 | GENERIC | A | BUY | trending_up | +39/56 | +1.00 (+2) | +16.01 (+0) | +0.40 (-1) | +31.70 (+1) | +0.06 (+2) | +0.31 (+1) | +1.69 (+1) | | | -1.03 | +| 2024-10-02 14:15 | GENERIC | C | BUY | ranging | +35/48 | +0.50 (+1) | +12.27 (-1) | +0.54 (-1) | +21.45 (+0) | +0.29 (+2) | +0.35 (+1) | +1.60 (+2) | +1.00 (+1) | | -1.06 | +| 2024-10-14 06:30 | GENERIC | A | BUY | trending_up | +41/59 | +1.00 (+2) | +24.92 (+0) | +0.50 (-1) | +30.45 (+1) | +0.27 (+2) | +0.38 (+1) | +3.19 (+1) | | | -1.05 | +| 2024-10-23 18:15 | GENERIC | B | SELL | trending_down | +30/53 | +1.00 (-2) | +18.28 (+0) | +0.23 (+0) | +48.61 (-1) | -0.08 (+0) | +0.18 (-1) | +1.91 (-1) | | | -1.05 | +| 2024-10-29 05:15 | GENERIC | A | BUY | trending_up | +35/59 | +1.00 (+2) | +16.86 (+0) | +0.34 (-1) | +36.62 (+1) | +0.04 (+1) | +0.11 (+1) | +2.05 (+1) | | +1.00 (+1) | -1.06 | +| 2024-11-05 15:00 | GENERIC | B | BUY | transitioning | +27/49 | +0.50 (+1) | +12.55 (+0) | +0.44 (-1) | +15.51 (+0) | +0.16 (+2) | +0.24 (+1) | +1.33 (+0) | | +1.00 (+1) | -1.05 | +| 2024-12-03 14:15 | GENERIC | B | BUY | transitioning | +28/57 | +1.00 (+2) | +17.30 (+0) | +0.58 (-1) | +18.00 (+0) | +0.29 (+2) | +0.30 (+0) | +2.12 (+1) | | +0.00 (+0) | -1.05 | +| 2024-12-18 20:45 | GENERIC | A | SELL | trending_down | +36/64 | +1.00 (-2) | +34.41 (+0) | +0.60 (+1) | +46.37 (-1) | +0.70 (-2) | +1.81 (-1) | +5.26 (-1) | -1.00 (+1) | | -0.51 | +| 2024-12-23 06:45 | GENERIC | C | BUY | ranging | +26/69 | +1.00 (+2) | +21.04 (-1) | +0.73 (-1) | +13.63 (+0) | +0.20 (+2) | +0.28 (+1) | +2.33 (+1) | +1.00 (+1) | +1.00 (+1) | -1.06 | +| 2025-01-06 07:45 | GENERIC | A | SELL | trending_down | +40/57 | +1.00 (-2) | +28.81 (+0) | +0.53 (+1) | +38.88 (-1) | +0.26 (-2) | +0.35 (-1) | +3.23 (-1) | | -1.00 (-1) | -1.03 | +| 2025-01-16 17:15 | GENERIC | A | BUY | trending_up | +33/57 | +1.00 (+2) | +19.76 (+0) | +0.32 (-1) | +28.49 (+1) | +0.06 (+2) | +0.18 (+1) | +1.71 (+1) | +1.00 (-1) | | -1.05 | +| 2025-01-22 09:30 | GENERIC | C | BUY | ranging | +25/61 | +1.00 (+2) | +20.25 (-1) | +0.68 (-1) | +19.66 (+0) | +0.21 (+2) | +0.54 (+1) | +2.89 (+1) | | +1.00 (+1) | -1.04 | +| 2025-01-24 15:30 | GENERIC | A | BUY | trending_up | +41/60 | +1.00 (+2) | +14.43 (+1) | +0.49 (-1) | +29.77 (+1) | +0.05 (+1) | +0.23 (+1) | +1.55 (+2) | | | -1.36 | +| 2025-01-29 19:30 | GENERIC | A | SELL | trending_down | +38/60 | +1.00 (-2) | +20.97 (+0) | +0.57 (+1) | +35.32 (-1) | +0.13 (-2) | +0.21 (-1) | +2.05 (-1) | | | -1.05 | +| 2025-02-14 18:15 | GENERIC | B | SELL | trending_down | +29/52 | +1.00 (-2) | +28.07 (+0) | +0.37 (+1) | +58.15 (-1) | +0.27 (+0) | +0.72 (-1) | +3.07 (-1) | | | -1.01 | +| 2025-02-19 18:15 | GENERIC | A | SELL | trending_down | +33/57 | +0.50 (-1) | +17.52 (+0) | +0.46 (+1) | +40.68 (-1) | +0.23 (-2) | +0.51 (-1) | +2.04 (-1) | | -1.00 (-1) | -1.05 | +| 2025-02-20 08:30 | GENERIC | B | BUY | trending_up | +31/59 | +1.00 (+2) | +20.59 (+0) | +0.46 (-1) | +36.49 (+1) | +0.19 (+2) | +0.21 (+0) | +2.18 (+1) | +1.00 (-1) | | -1.04 | +| 2025-02-20 14:15 | GENERIC | A | SELL | trending_down | +34/60 | +0.50 (-1) | +29.24 (+0) | +0.75 (+1) | +33.74 (-1) | +0.53 (-2) | +0.82 (-1) | +3.90 (-1) | -1.00 (-1) | | -1.03 | +| 2025-02-21 08:45 | GENERIC | A | SELL | trending_down | +39/60 | +1.00 (-2) | +15.23 (+0) | +0.52 (+1) | +28.31 (-1) | +0.14 (-2) | +0.24 (+0) | +1.62 (-2) | | | -1.04 | +| 2025-02-26 14:15 | GENERIC | A+ | SELL | transitioning | +46/69 | +1.00 (-2) | +24.52 (+1) | +0.95 (+1) | +15.41 (+0) | +0.31 (-2) | +0.64 (-1) | +3.45 (-2) | | | -1.03 | +| 2025-03-10 18:00 | GENERIC | A | SELL | trending_down | +41/62 | +1.00 (-2) | +20.95 (+0) | +0.59 (+1) | +30.43 (-1) | +0.36 (-2) | +0.76 (-1) | +2.44 (-1) | | +0.00 (+0) | -1.02 | +| 2025-03-13 03:15 | GENERIC | B | BUY | trending_up | +33/62 | +1.00 (+2) | +18.58 (+0) | +0.54 (-1) | +38.42 (+1) | +0.12 (+2) | +0.22 (+1) | +1.96 (+1) | +1.00 (-1) | +0.00 (+0) | -1.06 | +| 2025-03-14 04:30 | GENERIC | A | BUY | trending_up | +36/64 | +1.00 (+2) | +10.74 (+1) | +0.38 (-1) | +28.65 (+1) | +0.03 (+2) | +0.11 (+0) | +1.10 (+2) | +1.00 (+0) | | -1.07 | +| 2025-03-21 14:15 | GENERIC | A | SELL | transitioning | +32/66 | +1.00 (-2) | +31.05 (+1) | +0.69 (+1) | +24.00 (+0) | +0.60 (-2) | +1.02 (-1) | +4.30 (-1) | -1.00 (-1) | | -0.48 | +| 2025-03-24 18:15 | GENERIC | A | SELL | trending_down | +43/59 | +1.00 (-2) | +22.03 (+0) | +0.41 (+1) | +36.90 (-1) | +0.15 (-2) | +0.37 (-1) | +2.30 (-1) | | | -1.04 | +| 2025-03-26 07:45 | GENERIC | A | BUY | trending_up | +38/61 | +1.00 (+2) | +20.83 (+0) | +0.43 (-1) | +25.98 (+1) | +0.46 (+2) | +0.46 (+1) | +2.52 (+1) | | +2.00 (+1) | -1.05 | +| 2025-03-28 07:00 | GENERIC | A | BUY | trending_up | +42/60 | +1.00 (+2) | +27.74 (+0) | +0.58 (-1) | +58.16 (+1) | +0.13 (+2) | +0.35 (+1) | +2.86 (+1) | | +1.00 (+1) | -1.04 | +| 2025-03-31 06:15 | GENERIC | A | BUY | trending_up | +45/58 | +1.00 (+2) | +31.08 (+0) | +0.55 (-1) | +38.63 (+1) | +0.15 (+2) | +0.56 (+1) | +3.07 (+1) | | | -1.03 | +| 2025-04-02 08:45 | GENERIC | A | BUY | trending_up | +36/59 | +1.00 (+2) | +16.39 (+0) | +0.60 (-1) | +28.22 (+1) | +0.35 (+2) | +0.56 (+1) | +2.31 (+1) | | | -1.03 | +| 2025-04-04 08:30 | GENERIC | A | SELL | transitioning | +35/70 | +1.00 (-2) | +20.71 (+1) | +0.63 (+1) | +23.21 (+0) | +0.26 (-2) | +0.78 (-1) | +2.31 (-1) | | | -1.02 | +| 2025-04-08 06:45 | XAU_ATR | A | BUY | transitioning | +33/61 | +0.50 (+1) | +17.12 (+0) | +0.62 (-1) | +19.34 (+0) | +0.17 (+2) | +0.50 (+0) | +2.00 (+2) | | | -1.05 | +| 2025-04-11 15:30 | GENERIC | C | BUY | trending_up | +26/54 | +1.00 (+2) | +10.11 (+1) | +0.38 (-1) | +25.55 (+1) | +0.04 (+2) | +0.17 (+0) | +1.13 (+0) | +1.00 (-1) | | -1.02 | +| 2025-04-15 05:00 | GENERIC | A | BUY | trending_up | +40/60 | +1.00 (+2) | +21.62 (+0) | +0.41 (-1) | +38.85 (+1) | +0.18 (+2) | +0.33 (+1) | +2.21 (+1) | | | -1.05 | +| 2025-04-16 22:15 | GENERIC | B | BUY | trending_up | +29/60 | +1.00 (+2) | +26.69 (+0) | +0.69 (-1) | +47.37 (+1) | +0.06 (+1) | +0.48 (+1) | +3.24 (+1) | +1.00 (-1) | | -1.03 | +| 2025-04-22 06:00 | GENERIC | B | BUY | trending_up | +33/53 | +1.00 (+2) | +22.70 (+0) | +0.31 (-1) | +33.83 (+1) | +0.00 (+1) | +0.16 (+0) | +2.49 (+1) | | | -1.01 | +| 2025-04-23 14:15 | XAU_ATR | A | SELL | trending_down | +41/64 | +1.00 (-2) | +23.94 (+0) | +0.63 (+1) | +29.93 (-1) | +0.41 (-2) | +1.94 (-1) | +3.07 (-1) | | | -1.02 | +| 2025-04-27 22:15 | GENERIC | A | BUY | trending_up | +34/60 | +0.50 (+1) | +26.61 (+0) | +0.59 (-1) | +32.73 (+1) | +0.54 (+2) | +1.07 (+1) | +3.67 (+1) | | +0.00 (+0) | -1.02 | +| 2025-04-28 19:00 | GENERIC | B | BUY | trending_up | +31/57 | +1.00 (+2) | +25.89 (+0) | +0.36 (-1) | +33.20 (+1) | +0.07 (+2) | +0.44 (+1) | +2.60 (+1) | +1.00 (-1) | +1.00 (+1) | -1.02 | +| 2025-05-06 20:00 | GENERIC | A | BUY | trending_up | +34/56 | +1.00 (+2) | +22.74 (+0) | +0.39 (-1) | +33.89 (+1) | +0.01 (+1) | +0.24 (+1) | +2.47 (+1) | | | -1.01 | +| 2025-05-13 07:00 | GENERIC | B | BUY | trending_up | +32/53 | +0.50 (+1) | +15.52 (+0) | +0.38 (-1) | +29.53 (+1) | +0.23 (+2) | +0.73 (+1) | +2.14 (+1) | | | -1.03 | +| 2025-05-15 06:00 | GENERIC | A | SELL | trending_down | +42/57 | +1.00 (-2) | +30.34 (+0) | +0.40 (+1) | +46.37 (-1) | +0.28 (-2) | +0.79 (-1) | +3.35 (-1) | | | -1.01 | +| 2025-05-15 22:15 | GENERIC | B | BUY | trending_up | +32/60 | +1.00 (+2) | +28.60 (+0) | +0.58 (-1) | +52.87 (+1) | +0.04 (+1) | +0.61 (+1) | +3.44 (+1) | | | -1.02 | +| 2025-05-21 06:00 | GENERIC | B | BUY | trending_up | +31/57 | +1.00 (+2) | +18.38 (+0) | +0.55 (-1) | +33.47 (+1) | +0.07 (+1) | +0.26 (+0) | +2.05 (+1) | +1.00 (+0) | | -1.03 | +| 2025-05-21 14:15 | GENERIC | B | SELL | ranging | +30/59 | -0.00 (+0) | +8.56 (+0) | +0.41 (+1) | +19.48 (+0) | +0.13 (-2) | +0.27 (-1) | +0.95 (-2) | | | -1.04 | +| 2025-05-23 15:00 | GENERIC | B | BUY | trending_up | +34/54 | +1.00 (+2) | +17.55 (+0) | +0.36 (-1) | +32.24 (+1) | +0.02 (+1) | +0.35 (+1) | +1.67 (+1) | | | -1.02 | +| 2025-05-28 08:15 | GENERIC | A | BUY | transitioning | +33/59 | +0.50 (+1) | +18.88 (+0) | +0.55 (-1) | +18.80 (+0) | +0.40 (+2) | +0.43 (+1) | +2.69 (+1) | | | -1.02 | +| 2025-06-03 14:15 | GENERIC | A | SELL | trending_down | +42/62 | +0.50 (-1) | +22.91 (+0) | +0.83 (+1) | +30.59 (-1) | +0.21 (-2) | +0.57 (-1) | +2.56 (-2) | | | -1.02 | +| 2025-06-04 09:00 | GENERIC | B | BUY | ranging | +30/58 | +1.00 (+2) | +7.95 (+0) | +0.48 (-1) | +19.78 (+0) | +0.12 (+2) | +0.21 (+0) | +0.99 (+2) | | | -1.04 | +| 2025-06-05 12:15 | GENERIC | B | BUY | trending_up | +31/53 | +1.00 (+2) | +14.88 (+1) | +0.23 (+0) | +37.61 (+1) | +0.11 (+0) | +0.31 (+1) | +1.55 (+1) | +1.00 (+1) | +1.00 (+1) | -1.02 | +| 2025-06-11 12:45 | GENERIC | B | BUY | trending_up | +30/61 | +1.00 (+2) | +22.31 (+0) | +0.60 (-1) | +28.25 (+1) | +0.21 (+2) | +0.60 (+1) | +2.69 (+1) | +1.00 (-1) | | -1.02 | +| 2025-06-19 06:30 | XAU_ATR | A+ | SELL | trending_down | +44/66 | +1.00 (-2) | +29.47 (+0) | +0.77 (+1) | +25.08 (-1) | +0.43 (-2) | +0.69 (-1) | +3.67 (-1) | | | -1.06 | +| 2025-06-19 17:30 | GENERIC | B | BUY | ranging | +28/71 | +0.00 (+0) | +8.99 (+0) | +0.50 (-1) | +13.98 (+0) | +0.19 (+2) | +0.27 (+1) | +1.00 (+2) | +1.00 (+1) | | -1.05 | +| 2025-06-20 03:15 | GENERIC | A | SELL | trending_down | +44/65 | +1.00 (-2) | +23.87 (+0) | +0.64 (+1) | +25.94 (-1) | +0.32 (-2) | +0.58 (-1) | +2.77 (-1) | | | -1.31 | +| 2025-07-02 14:15 | GENERIC | B | SELL | ranging | +28/65 | -0.00 (+0) | +11.39 (+1) | +0.60 (+1) | +18.62 (+0) | +0.20 (-2) | +0.23 (-1) | +1.39 (-2) | -1.00 (-1) | | -1.05 | +| 2025-07-02 22:30 | GENERIC | A | BUY | trending_up | +42/60 | +1.00 (+2) | +27.17 (+0) | +0.56 (-1) | +33.53 (+1) | +0.17 (+2) | +0.29 (+1) | +3.19 (+1) | | | -1.05 | +| 2025-07-03 07:00 | GENERIC | A | BUY | transitioning | +33/68 | +1.00 (+2) | +18.01 (+0) | +0.53 (-1) | +22.58 (+0) | +0.36 (+2) | +0.37 (+1) | +2.31 (+1) | | +2.00 (+1) | -1.07 | +| 2025-07-09 09:00 | GENERIC | A | SELL | trending_down | +42/61 | +1.00 (-2) | +14.97 (-1) | +0.46 (+1) | +42.70 (-1) | +0.05 (-2) | +0.07 (-1) | +1.32 (-2) | -1.00 (+1) | | -1.04 | +| 2025-07-13 22:45 | GENERIC | B | BUY | transitioning | +25/63 | +1.00 (+2) | +18.21 (+0) | +0.57 (-1) | +23.10 (+0) | +0.28 (+2) | +0.37 (+0) | +2.40 (+1) | | -1.00 (-1) | -1.03 | +| 2025-07-14 14:45 | GENERIC | A | SELL | trending_down | +34/56 | +0.50 (-1) | +22.15 (+0) | +0.51 (+1) | +30.45 (-1) | +0.34 (-2) | +0.60 (-1) | +3.02 (-1) | | -1.00 (-1) | -1.03 | +| 2025-07-16 15:45 | GENERIC | B | BUY | trending_up | +28/54 | +0.50 (+1) | +23.53 (+0) | +0.59 (-1) | +26.48 (+1) | +0.40 (+2) | +0.78 (+0) | +2.46 (+1) | | | -1.02 | +| 2025-07-17 12:45 | GENERIC | B | SELL | trending_down | +31/62 | +1.00 (-2) | +21.53 (+0) | +0.92 (+1) | +32.72 (-1) | +0.13 (-1) | +0.43 (-1) | +3.02 (-2) | -1.00 (+1) | -1.00 (-1) | -1.03 | +| 2025-07-28 04:00 | GENERIC | B | BUY | ranging | +31/66 | +0.00 (+0) | +13.35 (-1) | +0.72 (-1) | +15.96 (+0) | +0.25 (+2) | +0.24 (+1) | +1.83 (+2) | | +1.00 (+1) | -1.04 | +| 2025-07-30 09:00 | GENERIC | B | BUY | transitioning | +44/46 | +0.50 (+1) | +9.33 (+0) | +0.50 (-1) | +20.43 (+0) | +0.16 (+2) | +0.26 (+1) | +1.12 (+2) | | +1.00 (+1) | -1.07 | +| 2025-07-31 09:15 | GENERIC | B | BUY | trending_up | +31/58 | +0.50 (+1) | +24.94 (+0) | +0.48 (-1) | +53.19 (+1) | +0.18 (+2) | +0.36 (+1) | +2.05 (+1) | | | -1.03 | +| 2025-08-01 08:00 | GENERIC | B | SELL | ranging | +35/51 | +1.00 (-2) | +12.21 (+1) | +0.59 (+1) | +16.74 (+0) | +0.19 (-2) | +0.19 (-1) | +1.30 (-2) | -1.00 (-1) | | -1.06 | +| 2025-08-12 12:30 | GENERIC | B | SELL | transitioning | +26/56 | +1.00 (-2) | +12.67 (+0) | +0.52 (+1) | +22.61 (+0) | +0.11 (-2) | +0.18 (-1) | +1.49 (+0) | | | -1.04 | +| 2025-08-25 15:00 | GENERIC | B | BUY | transitioning | +28/56 | +1.00 (+2) | +13.30 (+0) | +0.47 (-1) | +15.91 (+0) | +0.28 (+2) | +0.16 (+0) | +1.81 (+1) | | +0.00 (+0) | -1.05 | +| 2025-09-02 04:45 | GENERIC | C | BUY | trending_up | +26/51 | +1.00 (+2) | +14.00 (+1) | +0.23 (+0) | +45.04 (+1) | -0.05 (+0) | +0.11 (+1) | +1.35 (+0) | | | -1.05 | +| 2025-09-03 18:15 | GENERIC | A | BUY | trending_up | +42/57 | +1.00 (+2) | +27.13 (+0) | +0.45 (-1) | +40.58 (+1) | +0.11 (+2) | +0.40 (+1) | +2.74 (+1) | | +1.00 (+1) | -1.02 | +| 2025-09-04 03:15 | GENERIC | A | SELL | trending_down | +37/61 | +0.50 (-1) | +31.86 (+0) | +0.75 (+1) | +41.07 (-1) | +0.51 (-2) | +1.20 (-1) | +4.49 (-1) | | | -1.01 | +| 2025-09-09 05:45 | GENERIC | B | BUY | trending_up | +30/59 | +1.00 (+2) | +20.85 (+0) | +0.38 (-1) | +31.37 (+1) | +0.10 (+2) | +0.24 (+1) | +1.90 (+1) | +1.00 (-1) | +1.00 (+1) | -1.04 | +| 2025-09-12 05:15 | GENERIC | B | BUY | trending_up | +29/52 | +1.00 (+2) | +27.41 (+0) | +0.30 (-1) | +42.45 (+1) | +0.15 (+0) | +0.23 (+1) | +2.42 (+1) | | | -1.03 | +| 2025-09-12 14:30 | GENERIC | B | BUY | ranging | +31/62 | +1.00 (+2) | +9.10 (+0) | +0.35 (-1) | +18.60 (+0) | +0.17 (+2) | +0.16 (+1) | +0.97 (+2) | +1.00 (+1) | | -1.03 | +| 2025-09-17 19:00 | XAU_ATR | A | SELL | trending_down | +40/62 | +0.50 (-1) | +27.64 (+0) | +0.90 (+1) | +35.59 (-1) | +0.36 (-2) | +1.02 (-1) | +2.84 (-2) | -1.00 (-1) | | -1.03 | +| 2025-09-23 09:15 | GENERIC | A | BUY | trending_up | +35/61 | +1.00 (+2) | +30.56 (+0) | +0.74 (-1) | +26.20 (+1) | +0.50 (+2) | +0.84 (+1) | +4.62 (+1) | +1.00 (-1) | +1.00 (+1) | -1.02 | +| 2025-09-24 19:00 | GENERIC | B | SELL | trending_down | +32/55 | +0.50 (-1) | +33.01 (+0) | +0.41 (+1) | +60.16 (-1) | +0.20 (-2) | +0.48 (-1) | +2.97 (-1) | | | -1.01 | +| 2025-09-25 08:15 | GENERIC | A | BUY | transitioning | +33/59 | +0.50 (+1) | +19.02 (+0) | +0.63 (-1) | +21.43 (+0) | +0.35 (+2) | +0.53 (+1) | +2.49 (+1) | | | -1.03 | +| 2025-09-30 05:00 | GENERIC | B | BUY | trending_up | +28/52 | +1.00 (+2) | +31.25 (+0) | +0.40 (-1) | +46.47 (+1) | +0.26 (+0) | +0.55 (+1) | +2.97 (+1) | | | -1.02 | +| 2025-10-01 17:30 | GENERIC | C | BUY | trending_up | +26/46 | +0.50 (+1) | -1.86 (+2) | +0.04 (+0) | +30.25 (-1) | +0.04 (+2) | -0.06 (+0) | -0.09 (+0) | +1.00 (+1) | | -1.05 | +| 2025-10-14 05:00 | GENERIC | B | BUY | trending_up | +28/51 | +1.00 (+2) | +24.63 (+0) | +0.36 (-1) | +46.45 (+1) | +0.12 (+0) | +0.62 (+1) | +2.48 (+1) | | +1.00 (+1) | -1.01 | +| 2025-10-15 08:15 | GENERIC | A | BUY | trending_up | +36/65 | +1.00 (+2) | +23.55 (+0) | +0.56 (-1) | +33.49 (+1) | +0.17 (+2) | +0.58 (+1) | +2.59 (+1) | +1.00 (-1) | | -1.02 | +| 2025-10-17 04:15 | XAU_ATR | A | BUY | trending_up | +33/65 | +1.00 (+2) | +13.10 (+1) | +0.27 (+0) | +26.90 (+1) | +0.08 (+2) | +1.87 (+1) | +1.54 (+1) | +1.00 (-1) | | -1.02 | +| 2025-10-17 14:15 | XAU_ATR | A | SELL | trending_down | +44/62 | +0.50 (-1) | +26.73 (+0) | +0.82 (+1) | +57.50 (-1) | +0.32 (-2) | +2.09 (-2) | +3.86 (-1) | | -1.00 (-1) | -1.01 | +| 2025-10-20 20:00 | GENERIC | B | BUY | trending_up | +31/60 | +1.00 (+2) | +31.72 (+0) | +0.63 (-1) | +39.31 (+1) | +0.02 (+1) | +0.82 (+1) | +3.12 (+1) | +1.00 (-1) | | -1.01 | +| 2025-11-03 08:00 | GENERIC | B | BUY | ranging | +31/68 | +1.00 (+2) | +18.98 (-1) | +0.55 (-1) | +19.00 (+0) | +0.12 (+2) | +0.33 (+0) | +1.64 (+2) | | | -1.02 | +| 2025-11-11 03:15 | GENERIC | A | BUY | trending_up | +36/59 | +1.00 (+2) | +29.76 (+0) | +0.41 (-1) | +42.80 (+1) | +0.07 (+2) | +0.34 (+1) | +2.39 (+1) | +1.00 (-1) | +1.00 (+1) | -1.02 | +| 2025-11-14 03:30 | GENERIC | B | BUY | transitioning | +28/61 | +0.50 (+1) | +17.19 (+0) | +0.49 (-1) | +22.45 (+0) | +0.41 (+2) | +0.64 (+1) | +2.15 (+1) | | +1.00 (+1) | -1.02 | +| 2025-11-20 08:30 | XAU_ATR | A+ | SELL | trending_down | +49/73 | +1.00 (-2) | +17.81 (+0) | +0.61 (+1) | +25.46 (-1) | +0.18 (-2) | +0.51 (-1) | +1.83 (-2) | | | -1.03 | +| 2025-12-02 09:45 | GENERIC | B | SELL | trending_down | +30/59 | +0.50 (-1) | +23.37 (+0) | +0.66 (+1) | +35.83 (-1) | +0.25 (-2) | +0.55 (-1) | +2.63 (-1) | | -1.00 (-1) | -1.02 | +| 2025-12-08 06:00 | GENERIC | C | BUY | ranging | +27/64 | +0.00 (+0) | +13.04 (-1) | +0.58 (-1) | +17.25 (+0) | +0.19 (+2) | +0.17 (+1) | +1.53 (+2) | | | -1.04 | +| 2025-12-30 12:45 | GENERIC | B | BUY | trending_up | +32/56 | +0.50 (+1) | +18.68 (+0) | +0.45 (-1) | +28.35 (+1) | +0.20 (+2) | +0.54 (+1) | +2.12 (+1) | | +1.00 (+1) | -1.02 | +| 2025-12-31 06:15 | XAU_ATR | A | SELL | trending_down | +40/59 | +1.00 (-2) | +17.08 (+0) | +0.54 (+1) | +29.28 (-1) | +0.38 (-2) | +1.21 (+0) | +2.39 (-1) | -1.00 (-1) | | -1.02 | +| 2025-12-31 09:00 | XAU_ATR | A | SELL | trending_down | +37/63 | +1.00 (-2) | +11.79 (-1) | +0.28 (+0) | +27.94 (-1) | +0.01 (-1) | +0.00 (-1) | +1.54 (-1) | | | -1.03 | +| 2026-01-02 18:15 | XAU_ATR | A | SELL | trending_down | +40/59 | +1.00 (-2) | +24.54 (+0) | +0.36 (+1) | +49.77 (-1) | +0.13 (-2) | +0.73 (-1) | +2.49 (-1) | -1.00 (-1) | | -2.02 | +| 2026-01-21 06:30 | XAU_ATR | A | BUY | trending_up | +42/59 | +1.00 (+2) | +35.63 (+0) | +0.46 (-1) | +60.02 (+1) | +0.05 (+2) | +0.80 (+1) | +2.99 (+1) | | | -1.03 | +| 2026-01-21 09:30 | GENERIC | C | BUY | trending_up | +26/55 | +1.00 (+2) | +13.96 (+1) | +0.32 (-1) | +36.84 (+1) | -0.14 (+0) | +0.58 (+1) | +1.55 (+1) | | | -1.01 | +| 2026-01-21 20:15 | GENERIC | B | SELL | trending_down | +29/53 | +0.50 (-1) | +13.16 (-1) | +0.41 (+1) | +33.57 (-1) | +0.24 (-2) | +0.47 (+0) | +1.71 (-1) | | | -1.01 | +| 2026-01-22 07:30 | XAU_ATR | A | BUY | trending_up | +42/63 | +1.00 (+2) | +17.98 (+0) | +0.48 (-1) | +25.34 (+1) | +0.47 (+2) | +0.83 (+1) | +2.47 (+1) | | | -1.03 | +| 2026-01-27 07:45 | GENERIC | C | BUY | ranging | +28/74 | +1.00 (+2) | +13.95 (-1) | +0.47 (-1) | +14.32 (+0) | +0.11 (+2) | +0.58 (+1) | +1.25 (+2) | | | -1.01 | +| 2026-02-02 06:45 | XAU_ATR | A+ | SELL | trending_down | +50/69 | +1.00 (-2) | +27.60 (+0) | +0.59 (+1) | +41.91 (-1) | +0.25 (-2) | +4.50 (-2) | +2.71 (-1) | | | -1.00 | +| 2026-02-12 05:30 | GENERIC | C | BUY | trending_up | +30/46 | +0.50 (+1) | +4.16 (+2) | +0.37 (-1) | +30.27 (+1) | +0.16 (+2) | +0.11 (+0) | +0.57 (+0) | +1.00 (+1) | | -1.03 | +| 2026-02-13 19:15 | GENERIC | B | BUY | transitioning | +26/60 | +0.50 (+1) | +15.67 (+0) | +0.43 (-1) | +17.03 (+0) | +0.09 (+2) | +0.45 (+1) | +1.65 (+1) | | | -1.01 | +| 2026-02-19 07:15 | GENERIC | A | BUY | trending_up | +35/58 | +1.00 (+2) | +31.23 (+0) | +0.49 (-1) | +31.11 (+1) | +0.54 (+2) | +0.74 (+0) | +3.54 (+1) | | | -1.01 | +| 2026-02-20 09:30 | XAU_ATR | A | BUY | transitioning | +31/64 | +1.00 (+2) | +15.12 (+0) | +0.40 (-1) | +23.97 (+0) | +0.19 (+2) | +0.27 (+1) | +1.77 (+1) | | | -1.03 | +| 2026-02-24 14:15 | XAU_ATR | A | SELL | trending_down | +38/61 | +0.50 (-1) | +30.33 (+0) | +0.73 (+1) | +36.23 (-1) | +0.42 (-2) | +1.41 (-1) | +3.53 (-1) | | | -1.02 | +| 2026-02-24 20:45 | GENERIC | C | BUY | ranging | +26/77 | +0.50 (+1) | +11.52 (-1) | +0.52 (-1) | +19.92 (+0) | +0.15 (+2) | +0.15 (+1) | +1.40 (+2) | | | -1.03 | +| 2026-02-26 20:15 | XAU_ATR | A | BUY | trending_up | +34/58 | +1.00 (+2) | +13.52 (+1) | +0.43 (-1) | +26.99 (-1) | +0.18 (+2) | +0.43 (+1) | +1.44 (+0) | | +1.00 (+1) | -1.03 | +| 2026-03-03 06:15 | XAU_ATR | A | SELL | transitioning | +31/71 | -0.00 (+0) | +23.82 (+1) | +0.84 (+1) | +20.07 (+0) | +0.45 (-2) | +1.48 (-1) | +3.29 (-1) | -1.00 (-1) | | -1.02 | +| 2026-03-04 18:15 | GENERIC | B | SELL | transitioning | +25/54 | +1.00 (-2) | +14.30 (+0) | +0.41 (+1) | +18.17 (+0) | +0.11 (-2) | +0.37 (-1) | +1.38 (+0) | | | -1.01 | +| 2026-03-06 15:30 | GENERIC | B | BUY | trending_up | +32/60 | +0.50 (+1) | +15.62 (+0) | +0.68 (-1) | +28.45 (+1) | +0.37 (+2) | +1.55 (+1) | +2.38 (+1) | +1.00 (+1) | | -1.01 | +| 2026-03-06 18:45 | GENERIC | B | BUY | trending_up | +29/58 | +0.50 (+1) | +15.43 (+0) | +0.34 (-1) | +28.83 (+1) | +0.07 (+2) | +0.41 (+1) | +1.70 (+1) | +1.00 (+1) | +1.00 (+1) | -1.01 | +| 2026-03-11 20:00 | XAU_ATR | A | BUY | trending_up | +38/70 | +0.50 (+1) | +5.36 (+1) | +0.55 (-1) | +36.85 (-1) | +0.19 (+2) | +0.22 (+1) | +0.80 (+2) | +1.00 (+1) | | -1.04 | +| 2026-03-17 04:45 | GENERIC | A | BUY | trending_up | +33/57 | +0.50 (+1) | +17.67 (+0) | +0.44 (-1) | +29.28 (+1) | +0.17 (+2) | +0.36 (+1) | +2.05 (+1) | +1.00 (+1) | | -1.02 | +| 2026-03-23 07:30 | XAU_ATR | A | SELL | trending_down | +34/57 | +1.00 (-2) | +28.60 (+0) | +0.55 (+1) | +55.46 (-1) | +0.42 (-2) | +5.31 (+0) | +3.62 (-1) | -1.00 (+1) | +0.00 (+0) | -1.01 | +| 2026-03-23 14:30 | GENERIC | B | BUY | trending_up | +36/51 | +0.50 (+1) | +12.86 (+1) | +0.32 (-1) | +33.75 (+1) | +0.20 (+2) | +2.45 (+2) | +1.76 (+1) | +1.00 (-1) | | -1.00 | +| 2026-03-26 08:45 | XAU_ATR | A | SELL | trending_down | +37/57 | +1.00 (-2) | +20.38 (+0) | +0.35 (+1) | +33.75 (-1) | +0.14 (-2) | +0.02 (+0) | +2.42 (-1) | | | -1.02 | +| 2026-03-27 05:45 | GENERIC | B | BUY | transitioning | +28/61 | +0.50 (+1) | +21.60 (-1) | +0.48 (-1) | +24.67 (+0) | +0.17 (+2) | +0.81 (+1) | +2.43 (+1) | | | -1.01 | +| 2026-03-30 12:30 | GENERIC | B | BUY | trending_up | +30/64 | +1.00 (+2) | +18.40 (+0) | +0.54 (-1) | +38.35 (+1) | +0.13 (+2) | +0.73 (+1) | +1.81 (+1) | +1.00 (-1) | +1.00 (+1) | -1.01 | +| 2026-04-01 15:45 | XAU_ATR | A+ | BUY | trending_up | +51/66 | +1.00 (+2) | +20.06 (+0) | +0.64 (-1) | +25.46 (+1) | +0.19 (+2) | +1.14 (+1) | +2.23 (+2) | | | -1.02 | +| 2026-04-06 08:45 | GENERIC | B | BUY | transitioning | +26/59 | +0.50 (+1) | +15.79 (+0) | +0.48 (-1) | +21.95 (+0) | +0.25 (+2) | +0.69 (+0) | +1.95 (+1) | | | -1.01 | +| 2026-04-07 06:30 | XAU_ATR | A | SELL | trending_down | +33/66 | +1.00 (-2) | +4.02 (-2) | +0.08 (+0) | +27.89 (-1) | +0.02 (-2) | +0.19 (-1) | +0.45 (+0) | | -1.00 (-1) | -1.03 | +| 2026-04-20 22:30 | XAU_ATR | A | BUY | transitioning | +31/64 | +1.00 (+2) | +16.74 (+0) | +0.54 (-1) | +18.60 (+0) | +0.20 (+2) | +0.31 (+1) | +1.90 (+1) | | +0.00 (+0) | -1.05 | +| 2026-04-21 20:00 | GENERIC | B | SELL | trending_down | +33/52 | +1.00 (-2) | +18.16 (+0) | +0.44 (+1) | +40.09 (-1) | +0.08 (-2) | +0.44 (+0) | +2.27 (-1) | -1.00 (+1) | | -0.31 | +| 2026-04-24 14:30 | GENERIC | B | BUY | trending_up | +30/53 | +0.50 (+1) | +14.74 (+1) | +0.42 (-1) | +26.53 (+1) | +0.20 (+2) | +0.40 (+0) | +1.70 (+1) | | | -1.01 | +| 2026-04-27 03:45 | GENERIC | B | BUY | trending_up | +31/58 | +0.50 (+1) | +20.30 (+0) | +0.51 (-1) | +32.62 (+1) | +0.51 (+2) | +0.76 (+1) | +2.62 (+1) | | +1.00 (+1) | -1.03 | +| 2026-05-04 18:30 | XAU_ATR | A | SELL | trending_down | +34/55 | +1.00 (-2) | +17.75 (+0) | +0.27 (+0) | +35.48 (-1) | +0.01 (-2) | +0.22 (+0) | +1.57 (-1) | | | -1.03 | +| 2026-05-07 22:45 | XAU_ATR | A | SELL | trending_down | +39/62 | +0.50 (-1) | +13.87 (-1) | +0.51 (+1) | +38.20 (-1) | +0.10 (-2) | +0.48 (-1) | +1.54 (-2) | | -1.00 (-1) | -1.03 | +| 2026-05-11 06:00 | XAU_ATR | A | SELL | trending_down | +37/65 | +1.00 (-2) | +25.44 (+0) | +0.64 (+1) | +38.34 (-1) | +0.17 (-2) | +0.79 (-1) | +2.47 (-1) | -1.00 (+1) | | -1.03 | +| 2026-05-13 06:15 | GENERIC | C | BUY | ranging | +31/47 | +0.00 (+0) | +7.24 (+0) | +0.52 (-1) | +17.80 (+0) | +0.11 (+2) | +0.14 (+1) | +1.04 (+2) | | | -1.02 | +| 2026-05-20 03:15 | XAU_ATR | A | SELL | trending_down | +41/64 | +1.00 (-2) | +15.51 (+0) | +0.49 (+1) | +30.96 (-1) | +0.20 (-2) | +1.05 (-1) | +1.78 (-1) | | | -1.03 | +| 2026-05-21 18:00 | GENERIC | B | BUY | transitioning | +27/63 | +0.50 (+1) | +23.63 (-1) | +0.60 (-1) | +22.98 (+0) | +0.49 (+2) | +1.06 (+1) | +2.60 (+1) | | | -1.01 | +| 2026-05-22 14:15 | GENERIC | B | SELL | ranging | +32/70 | +1.00 (-2) | +15.71 (+1) | +0.70 (+1) | +15.74 (+0) | +0.16 (-2) | +0.53 (-1) | +2.12 (-2) | | | -1.29 | +| 2026-06-02 07:15 | GENERIC | A | BUY | trending_up | +36/55 | +1.00 (+2) | +26.76 (+0) | +0.41 (-1) | +30.49 (+1) | +0.33 (+2) | +0.66 (+0) | +3.07 (+1) | | | -1.01 | +| 2026-06-08 05:45 | XAU_ATR | A | SELL | trending_down | +35/57 | +1.00 (-2) | +16.14 (+0) | +0.63 (+1) | +36.04 (-1) | +0.09 (-1) | +0.40 (-1) | +2.05 (-1) | | -1.00 (-1) | -1.03 | +| 2026-06-10 12:15 | XAU_ATR | A | SELL | trending_down | +34/63 | +1.00 (-2) | +23.68 (+0) | +0.62 (+1) | +37.31 (-1) | +0.15 (-2) | +0.84 (-1) | +2.36 (-1) | -1.00 (+1) | | -1.02 | +| 2026-06-12 09:15 | XAU_ATR | A | BUY | transitioning | +36/70 | +1.00 (+2) | +18.55 (+0) | +0.52 (-1) | +23.45 (+0) | +0.41 (+2) | +1.14 (+1) | +2.25 (+1) | +1.00 (+1) | | -1.03 | +| 2026-06-15 14:30 | XAU_ATR | A | BUY | trending_up | +39/64 | +1.00 (+2) | +20.56 (+0) | +0.51 (-1) | +37.03 (+1) | +0.14 (+2) | +0.65 (+1) | +1.96 (+1) | | | -1.04 | +| 2026-06-19 05:00 | XAU_ATR | A+ | SELL | trending_down | +49/66 | +1.00 (-2) | +37.80 (+0) | +0.59 (+1) | +49.95 (-1) | +0.41 (-2) | +1.29 (-1) | +4.55 (-1) | | | -1.04 | +| 2026-06-26 15:45 | XAU_ATR | A | BUY | trending_up | +33/60 | +1.00 (+2) | +24.05 (+0) | +0.47 (-1) | +41.85 (+1) | +0.19 (+2) | +1.10 (+1) | +2.37 (+1) | +1.00 (-1) | | -1.03 | +| 2026-06-30 14:30 | GENERIC | B | BUY | trending_up | +32/64 | +0.50 (+1) | +20.61 (+0) | +0.70 (-1) | +28.39 (+1) | +0.13 (+2) | +0.50 (+1) | +2.30 (+2) | +1.00 (-1) | | -1.01 | +| 2026-07-06 14:45 | GENERIC | B | SELL | transitioning | +28/67 | +0.50 (-1) | +15.58 (+0) | +0.52 (+1) | +21.20 (+0) | +0.11 (-2) | +0.62 (-1) | +1.61 (-1) | | | -1.02 | +| 2026-07-07 05:15 | GENERIC | B | SELL | trending_down | +28/55 | +0.50 (-1) | +21.44 (+0) | +0.45 (+1) | +40.22 (-1) | +0.14 (-2) | +0.42 (-1) | +2.05 (-1) | -1.00 (+1) | | -1.02 | +| 2026-07-07 12:15 | GENERIC | A | BUY | trending_up | +35/58 | +0.50 (+1) | +24.63 (+0) | +0.79 (-1) | +27.96 (+1) | +0.50 (+2) | +0.97 (+1) | +3.81 (+1) | | | -1.01 | +| 2026-07-14 12:45 | XAU_ATR | A | BUY | transitioning | +33/65 | +0.50 (+1) | +34.78 (-1) | +1.05 (-1) | +20.21 (+0) | +0.42 (+2) | +1.86 (+1) | +5.42 (+1) | | | -1.03 | +| 2026-07-20 03:45 | XAU_ATR | A | BUY | transitioning | +32/61 | +0.50 (+1) | +16.30 (+0) | +0.49 (-1) | +20.82 (+0) | +0.34 (+2) | +0.42 (+1) | +1.92 (+1) | | | -1.04 | +| 2026-07-22 12:30 | GENERIC | B | BUY | ranging | +31/69 | +1.00 (+2) | +15.32 (-1) | +0.74 (-1) | +14.12 (+0) | +0.20 (+2) | +0.43 (+1) | +1.92 (+2) | | | -1.03 | +| 2026-07-27 07:30 | GENERIC | B | BUY | trending_up | +33/60 | +1.00 (+2) | +10.74 (+1) | +0.55 (-1) | +29.55 (+1) | +0.09 (+2) | +0.31 (+1) | +1.35 (+0) | | | -1.03 | +| 2026-07-27 14:30 | XAU_ATR | A | SELL | transitioning | +32/59 | +0.50 (-1) | +15.83 (+0) | +0.56 (+1) | +24.69 (+0) | +0.27 (-2) | +0.63 (-1) | +2.04 (-1) | | -1.00 (-1) | -1.04 | +| 2026-07-29 14:15 | GENERIC | A | SELL | trending_down | +39/61 | +1.00 (-2) | +24.24 (+0) | +0.62 (+1) | +30.53 (-1) | +0.35 (-2) | +0.78 (-1) | +2.89 (-1) | | | -1.01 | +| 2026-07-29 18:15 | XAU_ATR | A | BUY | trending_up | +36/62 | +0.50 (+1) | +30.49 (+0) | +0.76 (-1) | +37.37 (+1) | +0.59 (+2) | +1.76 (+1) | +4.27 (+1) | | | -1.03 | +| 2026-07-30 14:45 | XAU_ATR | A | BUY | trending_up | +42/59 | +1.00 (+2) | +18.68 (+0) | +0.57 (-1) | +33.18 (+1) | +0.20 (+2) | +0.78 (+1) | +2.07 (+1) | | +1.00 (+1) | -1.02 | +| 2026-08-04 17:15 | GENERIC | A | BUY | trending_up | +42/61 | +1.00 (+2) | +18.27 (+0) | +0.53 (-1) | +31.75 (+1) | +0.10 (+2) | +0.50 (+1) | +1.96 (+1) | | | -1.02 | +| 2026-08-06 14:45 | XAU_ATR | A | BUY | ranging | +37/67 | +1.00 (+2) | +5.66 (+0) | +0.28 (+0) | +13.07 (+0) | +0.12 (+2) | +0.40 (+1) | +0.85 (+2) | | +1.00 (+1) | -1.03 | +| 2026-08-20 12:30 | GENERIC | B | SELL | transitioning | +26/61 | +0.50 (-1) | +23.58 (+1) | +0.62 (+1) | +22.33 (+0) | +0.37 (-2) | +0.51 (+0) | +2.59 (-1) | | | -1.01 | +| 2026-08-27 14:15 | XAU_ATR | A | SELL | trending_down | +37/61 | +1.00 (-2) | +16.18 (+0) | +0.44 (+1) | +32.76 (-1) | +0.11 (-2) | +0.52 (-1) | +1.43 (-2) | -1.00 (+1) | | -1.03 | +| 2026-08-28 07:00 | XAU_ATR | A | BUY | transitioning | +36/61 | +0.00 (+0) | +16.94 (+0) | +0.93 (-1) | +12.29 (+0) | +0.37 (+2) | +0.61 (+1) | +3.10 (+2) | +1.00 (+1) | +1.00 (+1) | -1.04 | +| 2026-09-04 08:00 | XAU_ATR | A | BUY | transitioning | +39/76 | +1.00 (+2) | +10.83 (+0) | +0.57 (-1) | +14.15 (+0) | +0.23 (+2) | +0.35 (+0) | +1.43 (+2) | | | -1.04 | + +## Summary for the owner + +| Question | Five-minute book | Fifteen-minute book | +|---|---|---| +| Where do the wrong-direction trades come from? | Half from chart patterns (74 of 153) and half from indicator setups (73 generic, 6 gold volatility-sized). Chart patterns are wrong a little more often (22% of their trades against 18%); the inverse head-and-shoulders is the weak one (28% wrong, only +4.6R on 86 trades), the double bottom the strong one (17%, +21.5R). | No chart patterns on this timeframe. The gold volatility-sized setup (the grade-A setup) is wrong 24% of the time against 16% for the generic swing setup, and nets only +5.7R on 174 trades; three quarters of the wrong-direction count is generic simply because generic is four fifths of the book. | +| What did the indicators read on them? | Practically the same as on the winners. Every separation value is between 0.46 and 0.57 (0.50 = no separation): composite score +31.8 against +33.1, confidence 54 against 55, five of nine indicators agreeing in both groups. The only mild differences: the Keltner vote agreed 73% against 80%, RSI sat at +18 against +21 above the midline, a divergence was present on 24% against 34%. | Same picture, if anything slightly inverted: wrong-direction trades had a marginally higher composite (+36.2 against +35.9) and more exhaustion tags (3 against 2). The only differences above noise sit on the confirmation frames: the first confirmation's candlestick opposed 29% of the wrong-direction trades against 10% of winners (present on 30%), and the second confirmation's EMA alignment agreed 66% against 76%. | +| Hours or days? | 05:00 (33% wrong on 52 trades against a 20% base) and 08:00 (24%); the cells Tuesday 05:00 (8 of 16) and Thursday 20:00 (6 of 11). December 2024 and August 2025 were the worst months. | 18:00 (27% on 45 trades against a 17% base) and 22:00 (23%); Wednesday is the only weekday above the base (22% on 189 trades), with Wednesday 18:00 (5 of 13) and 09:00 (6 of 20) the worst cells. July was the worst month in both years (29% and 24%). | + +Reading: the wrong-direction losers were not visibly wrong at entry. The indicator set, the +composite, the confidence, the confirmation frames and the stretch measures all read the same as +on the winners, which means the direction errors are the noise floor of this signal set, not a +systematic misread that another threshold would remove. The two structural readings, the inverse +head-and-shoulders on M5 and the gold volatility-sized setup on M15, and the Wednesday / July +weakness on M15, are candidates for a rule-1 campaign at most, not changes. The per-trade +appendix above lists every wrong-direction loser with its nine primary-timeframe indicator values. diff --git a/config.py b/config.py index 0c7bf65..c007cca 100755 --- a/config.py +++ b/config.py @@ -126,7 +126,17 @@ class SignalConfig: # floors at ATR × v2_sl_atr_multiplier only. min_sl_pips: float = 80.0 # TP1 floor as a fraction of SL distance (0.5 = historical RR 0.5). + # Part of the setup geometry the gates judge (min-RR, spread gate). tp_floor_rr: float = 0.5 + # Printed-target floor (2026-09-26, owner rule "TP1 at least 1:1"): + # every generator raises the PRINTED TP1 to at least this fraction + # of the stop AFTER the min-RR and spread gates have judged the + # generator's own target, so which generator serves a signal (and + # the stop) never changes and the books stay trade-identical; only + # the exported tp1 / rr_planned columns move. TP1 is advisory in the + # tested exit (the trail), so this is a guide, not a book change. + # The effective floor is max(tp_floor_rr, tp_print_floor_rr). + tp_print_floor_rr: float = 1.0 # ── Trailing-stop exit (2026-07) ────────────────────────────── # Once favorable excursion reaches trail_arm_r × risk, the stop diff --git a/docs/beginner_guide.html b/docs/beginner_guide.html index c5b4dfd..f81c420 100644 --- a/docs/beginner_guide.html +++ b/docs/beginner_guide.html @@ -79,9 +79,14 @@ 📈 Direction: BUY ▶ Entry: 4,132.50 ← open the trade near this price 🛑 SL: 4,088.20 ← your safety brake (stop loss) -🎯 TP1: 4,176.80 ← rough profit target (a guide, not a rule) -🔄 Trail: once price hits 4,159.08, - trail SL $33.23 behind best price ← how to lock in profit (see section 6) +🔄 Exit rule (tested): once price reaches + 4,159.08 (+0.6R), trail the SL $33.23 + behind the best price, moved at + candle close only. ← how to lock in profit (see section 6) +🎯 TP1 guide 4,176.80 (1.0R) ← rough profit target (a guide, not a rule) +⚖️ Planned TP1 RR 1:1.0 (guide only) +📚 Book (this setup class): 312 trades, + 58% winners, avg +0.19R per trade ← what this kind of signal did in the backtests
  • Grade (A+, A, B, C): the bot's own mark for how clean the setup is. A+ is best. It never sends anything worse than C.
  • Confidence and the indicator list: extra detail for curious people. You don't need to understand it to trade the signal.
  • diff --git a/docs/user_guide.html b/docs/user_guide.html index 70518c3..fb78516 100644 --- a/docs/user_guide.html +++ b/docs/user_guide.html @@ -56,12 +56,15 @@ 🚀 TRADE SETUP 📈 Direction: BUY ▶ Entry: 4,132.50 -🛑 SL: 4,103.10 ($29.40 / 294 pips) -🎯 TP1: 4,161.90 ($29.40 / 294 pips) -⚖️ RR: 1:1.0 -🔄 Trail: once price hits 4,150.14 (+0.6R), - trail SL $22.05 behind best price — - TP1 is a guide, let it run +🛑 SL: 4,103.10 ($29.40 / 294 pips = 1R) +🔄 Exit rule (tested): once price reaches + 4,150.14 (+0.6R), trail the SL $22.05 + behind the best price, moving it at + candle close only. Otherwise the SL is the exit. +🎯 TP1 guide 4,161.90 ($29.40 / 294 pips = 1.0R) +🎯 TP2 guide 4,176.60 (1.5R) +⚖️ Planned TP1 RR 1:1.0 (guide only — the trail is the exit) +📚 Book (this setup class): 312 trades, 58% winners, avg +0.19R per trade @@ -70,10 +73,12 @@ - + - - + + + +
    FieldWhat it means
    MTF alignedThe two confirmation timeframes agree with the trade direction. Signals without alignment are filtered out on M5.
    Warnings ⚠️Context, not veto. Backtesting shows warned trades perform about the same — strong momentum often triggers warnings.
    Violent-move watch 🌪An occasional extra line saying the next hour looks unusually likely to produce a very large candle — because a high-impact US release is due (or just landed), because the clock is in a historically wild slot, or because price is stretched outside its bands. It is a heads-up about volatility, not a direction call, and it never changes the signal, the grade or the trade setup — the same alert would be sent without it. Do not close or tighten an open trade because of it: every such protective action was tested over three years and lost money. Your stop is the protection.
    AI estimate 🤖An advisory line such as "AI estimate: about a 45% chance this trade pays off (usual for this kind of setup: 60%). Biggest worry: interest-rate decision in about 2 hours. Still being tested." One AI reviewer (the news reader) receives the recent headlines together with facts computed by code — how far the move has already run, whether a very large candle just printed, how the stop and target compare with the usual, how trades of this exact kind fared in the two-year test, and whether major scheduled news lands before the trade would normally finish — and gives a probability that the trade ends in profit plus one reason for and one against. (Three further reviewers, chart, risk and sceptic, were switched off on 2026-09-17: in two evaluations they added nothing beyond the base rate and mostly voted the same way on every setup.) It is not a recommendation and it never changes the signal, the grade or the setup. Until about 300 of these estimates have been graded against real outcomes, treat it as context, not as a filter: the earlier AGREE/OPPOSE version of this line opposed target-reaching trades as often as it agreed.
    AI check 🤖An advisory block written for a new trader, such as "AI check: the odds look worse than usual. New data just came out against this buy. Chance it pays off: about 45 in 100 (usual for this kind of setup: 60 in 100). For: Before the news, the price was climbing. Against: Fresh data just came out against gold. This check is an experiment. Do not trade on it alone." The first sentence compares the estimate with the usual odds for this kind of setup (8 points either way counts as a difference); there is no verdict word and no confidence percentage. One AI reviewer (the news reader) receives the recent headlines together with facts computed by code — how far the move has already run, whether a very large candle just printed, how the stop and target compare with the usual, how trades of this exact kind fared in the two-year test, and whether major scheduled news lands before the trade would normally finish — and gives a probability that the trade ends in profit plus one reason for and one against. (Three further reviewers, chart, risk and sceptic, were switched off on 2026-09-17: in two evaluations they added nothing beyond the base rate and mostly voted the same way on every setup.) It is not a recommendation and it never changes the signal, the grade or the setup. Until about 300 of these estimates have been graded against real outcomes, treat it as context, not as a filter: the earlier AGREE/OPPOSE version of this line opposed target-reaching trades as often as it agreed.
    Market context 📰An AI-written note in plain English on what is moving the gold price right now, why it matters, and the next report to watch. It is built only from headlines published in the last 4 hours that lean one way (neutral items are dropped) plus today's high-impact releases. Informational only — it never blocks or grades a signal.
    SL / TP1 / RRStop loss sits behind the nearest swing level with an ATR buffer. TP1 is the nearest opposing level. 1R = the entry-to-SL distance = the amount you risk.
    Trail plan 🔄The real exit. Once price moves +0.6R in your favor, move the stop to trail 0.75R behind the best price and update it at each candle close. TP1 is advisory — let winners run.
    SL / TP1 guide / planned RRStop loss sits behind the nearest swing level with an ATR buffer (grade A+/A setups use a 2 × ATR stop). 1R = the entry-to-SL distance = the amount you risk. TP1 is a guide (the nearest opposing level or a multiple of ATR, never printed closer than 1R since 2026-09-26); the "planned TP1 RR" is TP1 ÷ 1R and is always at least 1:1. It is not the expected payoff: the tested exit is the trail, and the validated books earn their profit from trailed runners (M5 average win about +1.0R against an average loss of −0.76R; M15 +0.87R against −0.93R).
    Exit rule 🔄The real, backtested exit. Once price moves the stated R in your favor (0.6R on M5, 0.7R on M15), move the stop to trail the stated $ distance behind the best price and update it at each candle close only. Until then the original SL is the exit. TP1 is advisory — let winners run.
    Book line 📚What this class of setup (timeframe, setup type, grade, direction) actually did in the validated two-year backtests: number of trades, share of winners and average R per trade. When the exact class has fewer than 30 trades the line falls back to all setups of that direction on the timeframe, then to the whole timeframe.
    One alert per moveAfter an alert, the bot sends no new alert on that timeframe for the next 5 candles (25 min on M5, 75 min on M15), the same entry cooldown the backtester applies. Consecutive-bar re-entries into the same move were the largest live-only loss in September 2026.

    3 · Acting on a signal — step by step

    diff --git a/main.py b/main.py index fed58d4..52a1fa9 100755 --- a/main.py +++ b/main.py @@ -126,10 +126,23 @@ def print_signal_report(report: SignalReport, detail: bool = False): print(f"\n {Colors.BOLD}Setup{Colors.RESET} " f"{dc}{ts.direction}{Colors.RESET} @ {Colors.BOLD}{_fmt_price(ts.entry_price)}{Colors.RESET} " f"→ SL {Colors.RED}{_fmt_price(ts.stop_loss)}{Colors.RESET} " - f"({ts.risk_pips:.1f}p) " - f"→ TP {Colors.GREEN}{_fmt_price(ts.take_profit_1)}{Colors.RESET} " + f"({ts.risk_pips:.1f}p = 1R) " + f"→ TP1 guide {Colors.GREEN}{_fmt_price(ts.take_profit_1)}{Colors.RESET} " f"({ts.reward_pips:.1f}p) " - f"RR 1:{ts.risk_reward:.1f}") + f"planned TP1 RR 1:{ts.risk_reward:.1f}") + # The trail is the tested exit (the backtester never exits at + # TP1); print it with the setup so the CLI says the same thing + # as the Telegram alert (2026-09-26). + risk_dist = abs(ts.entry_price - ts.stop_loss) + arm_r = getattr(ts, "trail_arm_r", 0.0) or 0.0 + dist_r = getattr(ts, "trail_dist_r", 0.0) or 0.0 + if arm_r > 0 and risk_dist > 0: + arm_price = (ts.entry_price + arm_r * risk_dist if ts.direction == "BUY" + else ts.entry_price - arm_r * risk_dist) + print(f" {Colors.GRAY}Exit rule (tested): once price reaches " + f"{_fmt_price(arm_price)} (+{arm_r:.1f}R), trail the SL " + f"${dist_r * risk_dist:.2f} behind the best price at candle close; " + f"TP1 is a guide only.{Colors.RESET}") # ── 3. CONTEXT (price · regime · vol · MTF breakdown) ────────── print(f"\n {Colors.WHITE}{report.display_name}{Colors.RESET} " diff --git a/pair_signals/xauusd.py b/pair_signals/xauusd.py index bc79318..36d25d0 100644 --- a/pair_signals/xauusd.py +++ b/pair_signals/xauusd.py @@ -107,6 +107,29 @@ screen's M5 picks (MACD-down) all FAILED the replay, and ema_trend x 2 / divergence -> 0 on M15 failed too. Canonical M15 book re-pinned to backtest/oanda_m15_2yr_065_20260909.csv. +M15 override 6 — grade-A SELLs blocked (validated 2026-09-26, +backtest/research/rr_and_sell_campaign_2026-09-26.md, pre-registered +rule, CUDA twin runs paired with gpu_backtest.py MD5s, $0.65): +sell_min_quality=["A+", "B"] on M15, so gate 6e downgrades every +grade-A short to D (A+ and B shorts still trade). The bucket was the +one cycle-2 candidate from the 2026-09-10 screen: M15 grade-A SELLs +230 / -14.4R on the 3-year window, negative in every 12-month slice +and in the OOS window. Candidate vs control: 2-year W2 2024-09-05 -> +2026-09-05 813/+154.15R PF 1.53 DD 15.6R (halves +70.1/+84.0) vs +931/+146.14R PF 1.43 DD 16.1R (+66.1/+80.0); OOS 2024-06-01 -> +2025-03-30 313/+68.50R PF 1.67 DD 12.4R vs 355/+60.67R PF 1.49 DD +17.1R; 3-year W3 2023-09-05 -> 2026-09-05 1105/+195.79R PF 1.49 DD +20.9R vs 1273/+186.61R PF 1.39 DD 19.3R (+8%, inside the 10% clause), +better in 2024, 2025 and 2026, -2.4R in the 4 months of 2023; at $0.30 ++167.53R vs +161.44R (W2). Trade diff on W2: 162 removed (-8.1R, all +grade-A SELLs), 44 added (-0.1R, cooldown displacement). Counter-fact +on record: M15 A-grade shorts paid in the 2021-22 bear phase +(m15_5yr_cuda_2026-09-18.md), outside the owner's 3-year window. +Tested and NOT shipped in the same campaign: signals.min_rr_ratio 1.0 +on M15 (trade-identical, -2.0R W2, -2.8R W3: the swing-anchored +GENERIC stop is not better than the 2 x ATR stop on the same grade-A +signals); the combination with this block (-0.7R vs the block alone). + M5 override (validated 2026-07-11, full-history replay 2020-03-17 -> 2026-07-11 + 12mo book, $0.30 spread): block hour 9 UTC (post-London- open whipsaw). Hour-9 entries lost -28.9R/143 over 6 years, negative @@ -305,6 +328,10 @@ def make_config( # RSI regime multiplier × 0.5 in every regime on M15 only — see # module docstring (M15 override 5, validated 2026-09-09). config.technical.regime_weights_override = _m15_regime_weights() + # Grade-A SELLs blocked on M15 only (gate 6e downgrades an + # actionable-grade short whose grade is not listed) — see module + # docstring (M15 override 6, validated 2026-09-26). + config.xau_strategy.sell_min_quality = ["A+", "B"] if primary_tf == Timeframe.H1: # Block every H1 SELL — the SELL quality gate (6e in # signal_aggregator.py) downgrades any actionable-grade short diff --git a/signal_aggregator.py b/signal_aggregator.py index d229fc5..fddd6f8 100755 --- a/signal_aggregator.py +++ b/signal_aggregator.py @@ -903,6 +903,22 @@ class SignalAggregator: if height < min_tp: return + # Printed-target floor AFTER the gates (2026-09-26, see + # config.SignalConfig.tp_print_floor_rr): the measured move stays + # the gated target; the printed guide is at least the floor. + # Mirrored in cuda_signal_kernels.py K2. + tp1_dist = height + floor_dist = sl_dist * self._printed_target_floor() + if tp1_dist < floor_dist: + tp1_dist = floor_dist + if is_buy: + tp1 = entry + floor_dist + tp2 = entry + 1.5 * floor_dist + else: + tp1 = entry - floor_dist + tp2 = entry - 1.5 * floor_dist + rr = tp1_dist / sl_dist + report.trade_setup = TradeSetup( pair=report.symbol, display_name=report.display_name, @@ -913,7 +929,7 @@ class SignalAggregator: take_profit_2=tp2, risk_reward=round(rr, 2), risk_pips=sl_dist / pip_value, - reward_pips=height / pip_value, + reward_pips=tp1_dist / pip_value, pip_value=pip_value, strategy="XAU_CP", setup_pattern=pat["pattern"], @@ -1394,6 +1410,13 @@ class SignalAggregator: report.warnings[:] = kept report.cleaned_warnings.extend(stale) + def _printed_target_floor(self) -> float: + """The floor every generator applies to the PRINTED target 1, as a + fraction of the stop: max(tp_floor_rr, tp_print_floor_rr). Applied + after the gates (see config.SignalConfig.tp_print_floor_rr).""" + sig = self.config.signals + return max(sig.tp_floor_rr, getattr(sig, "tp_print_floor_rr", 0.0)) + def _generate_xau_atr_trade_setup( self, report: SignalReport, atr: float ) -> Optional[TradeSetup]: @@ -1446,6 +1469,31 @@ class SignalAggregator: ) return None + # Printed-target floor (2026-09-26): the guide target is at least + # max(tp_floor_rr, tp_print_floor_rr) x the stop (1R by default). + # With a 2 x ATR stop the grade-A target of 1.5 x ATR printed + # "RR 1:0.8" (1 x ATR with two exhaustion tags, "1:0.5"); on M15 + # the factory floor is 1R, on M5 (0.5) nothing binds. Applied + # AFTER the gates on purpose: the min-RR and spread checks above + # keep judging the generator's own 1.5 x ATR target, so which + # generator serves the signal (and therefore the stop) is + # unchanged and the books are trade-identical (a floor applied + # before the spread gate moved 94 grade-A M15 setups onto the + # 2 x ATR stop and cost -24.6R on W2: rr_and_sell_campaign_ + # 2026-09-26.md). TP1 is advisory in the backtester (the trail is + # the exit), so only the exported tp1/tp2/rr_planned columns + # move. Mirrored in cuda_signal_kernels.py K2. + floor_dist = sl_distance * self._printed_target_floor() + if tp_distance < floor_dist: + tp_distance = floor_dist + if is_buy: + tp1 = price + floor_dist + tp2 = price + floor_dist * 1.5 + else: + tp1 = price - floor_dist + tp2 = price - floor_dist * 1.5 + rr = tp_distance / sl_distance + return TradeSetup( pair=report.symbol, display_name=report.display_name, @@ -1865,6 +1913,22 @@ class SignalAggregator: ) return None + # Printed-target floor AFTER the gates (2026-09-26, see + # config.SignalConfig.tp_print_floor_rr): the gates above judged + # the swing/ATR target, so the trade is unchanged; only the guide + # moves. TP2 keeps its "at least 1 ATR beyond TP1" rule. + # Mirrored in cuda_signal_kernels.py K2. + floor_dist = sl_distance * self._printed_target_floor() + if tp1_distance < floor_dist: + tp1_distance = floor_dist + if is_buy: + tp1 = entry + floor_dist + tp2 = max(tp2, tp1 + atr) + else: + tp1 = entry - floor_dist + tp2 = min(tp2, tp1 - atr) + rr = tp1_distance / sl_distance + risk_pips = sl_distance / pip_value reward_pips = tp1_distance / pip_value diff --git a/static/dashboard.js b/static/dashboard.js index e4d6ff3..87b14d7 100644 --- a/static/dashboard.js +++ b/static/dashboard.js @@ -595,22 +595,24 @@ function planHTML(ts) { No trade plan. The signal did not clear the gate that releases an entry, stop and target.`; } const buy = ts.direction === "BUY"; + // The trail is the tested exit (the backtester never exits at target 1); + // the risk-reward shown is the PLANNED target-1 ratio, a guide only. const trail = (ts.trail_arm_r != null && ts.trail_dist_r != null) - ? `Stop starts trailing at ${ts.trail_arm_r}R of profit and follows ${ts.trail_dist_r}R behind.` - : "Trailing uses the platform default."; + ? `Exit rule (tested): once the trade is ${ts.trail_arm_r}R in profit, the stop trails ${ts.trail_dist_r}R behind the best price, moved at candle close. Until then the stop is the exit; targets are guides.` + : "Exit rule: the trailing stop uses the platform default; targets are guides."; return `
    ${buy ? "Buy plan" : "Sell plan"} ${esc(ts.strategy || "")} - ${(+ts.risk_reward).toFixed(1)} to 1 + planned ${(+ts.risk_reward).toFixed(1)} to 1
    Entry
    ${fmt$(ts.entry)}
    Stop
    ${fmt$(ts.stop_loss)}
    -
    Target 1
    +
    Target 1 (guide)
    ${fmt$(ts.take_profit_1)}
    Target 2
    ${fmt$(ts.take_profit_2)}
    @@ -873,21 +875,22 @@ function detailHTML(tf) { ${esc(r.news_summary || "")}
    `); } - if (r.panel_verdict && r.panel_verdict_source === "mean") { + if (r.panel_verdict && (r.panel_probability || 0) > 0) { // v2 panel: a probability anchored to the base rate of this kind of // setup. Advisory only — no gate reads it, so it gets no verdict colour. + // Same plain wording as the Telegram alert (2026-09-26). const base = (r.panel_context && r.panel_context.base_rate) || {}; const usual = base.win_rate != null - ? ` Setups like this one have paid off about ${Math.round(base.win_rate)}% of the time.` : ""; + ? ` Usual for this kind of setup: about ${Math.round(base.win_rate)} in 100.` : ""; const takes = (r.panel_takes || []).map(t => `${esc(t.persona)} ${t.p_win != null ? Math.round(t.p_win) + "%" : "—"} ${esc(t.reason_for || "")} ${esc(t.reason_against || "")}`).join(""); out += sec("AI estimate", `
    - About a ${Math.round(r.panel_probability || 0)}% chance this trade pays off.${esc(usual)} - It is advisory and still being tested — nothing in the signal reads it.
    - ${esc(r.panel_rationale || "")}
    + Chance it pays off: about ${Math.round(r.panel_probability || 0)} in 100.${esc(usual)} + ${esc(r.panel_rationale || "")}
    + This check is an experiment. Do not trade on it alone.
    ${takes ? `
    ${takes}
    PersonaChanceForAgainst
    ` : ""}`); diff --git a/telegram_bot.py b/telegram_bot.py index 648b90a..8338a5e 100755 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -275,7 +275,6 @@ def _mtf_line(report: SignalReport) -> str: # Informational fixed-pip target shown on every trade setup (display-only, # not part of the backtested exit model). -_INFO_TP_PIPS = 100 _IND_SHORT = { "ema_trend": "EMA", "rsi": "RSI", "macd": "MACD", "bollinger": "BB", @@ -324,36 +323,81 @@ _DISCLAIMER = ( ) -def _panel_estimate_lines(report: SignalReport) -> list[str]: - """v2 panel (verdict_source="mean"): a plain-language probability, the - class base rate it is anchored to, the biggest worry, and each - persona's own estimate. Advisory only — no colour, no verdict word, - never a recommendation (2026-09-17 review: the old AGREE/OPPOSE line - opposed target-reaching trades as often as it agreed).""" +def _panel_odds_word(p: float, usual: Optional[float]) -> str: + """Plain-language headline: the estimate against the usual odds for + this kind of setup (8 points either way), or against 60 / 40 when no + base rate is known. Never a verdict word.""" + if usual is not None: + d = p - usual + if d >= 8: + return "the odds look better than usual" + if d <= -8: + return "the odds look worse than usual" + return "the odds look about usual" + if p >= 60: + return "the odds look good" + if p <= 40: + return "the odds look poor" + return "the odds look about even" + + +def _panel_lines(report: SignalReport) -> list[str]: + """The AI review for a new trader (owner rule 2026-09-26: simple and + clear). One headline in everyday words, the chance "in 100" against + the usual odds for this kind of setup, one reason for and one + against, and a plain warning. Used whenever the verdict carries a + probability (the "mean" source, and "macro" since 2026-09-26); no + verdict word, no percentages that are not probabilities, no persona + names unless several personas ran (research configurations).""" + p = float(report.panel_probability or 0.0) base = (report.panel_context or {}).get("base_rate") or {} usual = base.get("win_rate") - line = (f"🤖 AI estimate: about a *{report.panel_probability:.0f}%* " - f"chance this trade pays off") - if usual is not None: - line += f" (usual for this kind of setup: {usual:.0f}%)" - line += "." + head = f"🤖 AI check: {_panel_odds_word(p, usual)}." if report.panel_rationale: - line += f" {_escape_md(report.panel_rationale)}" - worst = min((t for t in report.panel_takes - if t.get("p_win") is not None and t.get("reason_against")), - key=lambda t: t["p_win"], default=None) - if worst is not None: - line += f" Biggest worry: {_escape_md(worst['reason_against'])}" - line += " Still being tested." - out = [line] - if len(report.panel_takes) > 1: # one persona: the headline says it all - votes = " · ".join( - f"{t['persona']} {t['p_win']:.0f}%" if t.get("p_win") is not None - else f"{t['persona']} -" for t in report.panel_takes) + head += f" {_escape_md(report.panel_rationale)}" + chance = f" Chance it pays off: about *{p:.0f} in 100*" + if usual is not None: + chance += f" (usual for this kind of setup: {usual:.0f} in 100)" + out = [head, chance + "."] + takes = [t for t in report.panel_takes if t.get("p_win") is not None] + lead = max(takes, key=lambda t: abs(float(t["p_win"]) - 50.0), default=None) + if lead is not None: + if lead.get("reason_for"): + out.append(f" For: {_escape_md(lead['reason_for'])}") + if lead.get("reason_against"): + out.append(f" Against: {_escape_md(lead['reason_against'])}") + if len(takes) > 1: # one persona: the headline says it all + votes = " · ".join(f"{t['persona']} {float(t['p_win']):.0f}%" for t in takes) out.append(f" _{_escape_md(votes)}_") + out.append(" This check is an experiment. Do not trade on it alone.") return out +def _book_rate_line(report: SignalReport) -> Optional[str]: + """One line of validated-book statistics for the setup's class + (timeframe × strategy × grade × direction, falling back to wider + classes exactly as the agent panel does). None when no table or no + primary analysis is available; never raises.""" + ts = report.trade_setup + if ts is None or report.primary_analysis is None: + return None + try: + from agent_panel import _base_rate + rate = _base_rate(report.primary_analysis.timeframe, + getattr(ts, "strategy", None) or "GENERIC", + report.signal_quality, ts.direction) + except Exception: + logger.debug("book rate line unavailable", exc_info=True) + return None + if not rate or "win_rate" not in rate or "mean_r" not in rate: + return None + scope = {"class": "this setup class", + "timeframe_direction": f"{ts.direction} setups on this timeframe", + "timeframe": "this timeframe"}.get(rate.get("level"), "this class") + return (f"📚 Book ({scope}): {rate['n']} trades, " + f"{rate['win_rate']:.0f}% winners, avg {rate['mean_r']:+.2f}R per trade") + + def format_signal_message(report: SignalReport) -> str: sig = report.final_signal emoji = _SIGNAL_EMOJI.get(sig, "⬜") @@ -388,30 +432,21 @@ def format_signal_message(report: SignalReport) -> str: dir_emoji = "📈" if ts.direction == "BUY" else "📉" risk_usd = ts.risk_pips * ts.pip_value reward_usd = ts.reward_pips * ts.pip_value + # Order and wording (2026-09-26): the trail is the tested exit + # (the backtester never exits at TP1 — `_simulate_trailing` reads + # only the stop and the trailing rule), so it is printed first + # and TP1/TP2 are labelled guides with their R. The RR line is + # the PLANNED TP1 ratio, not the expected payoff: the validated + # books earn their R from the trailed runners (M5 average win + # +1.03R vs loss −0.76R; M15 +0.87R vs −0.93R). + risk_dist = abs(ts.entry_price - ts.stop_loss) lines += [ "", "🚀 *TRADE SETUP*", f"{dir_emoji} Direction: *{ts.direction}*", f"▶ Entry `{_fmt_price(ts.entry_price)}`", - f"🛑 SL `{_fmt_price(ts.stop_loss)}` (${risk_usd:.2f} / {ts.risk_pips:.0f}p)", - f"🎯 TP1 `{_fmt_price(ts.take_profit_1)}` (${reward_usd:.2f} / {ts.reward_pips:.0f}p)", + f"🛑 SL `{_fmt_price(ts.stop_loss)}` (${risk_usd:.2f} / {ts.risk_pips:.0f}p = 1R)", ] - if ts.take_profit_2: - lines.append(f"🎯 TP2 `{_fmt_price(ts.take_profit_2)}`") - - # Informational fixed-pip target: where a 100-pip TP would sit for - # this setup (display-only — not part of the backtested exit model). - info_dist = _INFO_TP_PIPS * ts.pip_value - if ts.direction == "BUY": - info_price = ts.entry_price + info_dist - else: - info_price = ts.entry_price - info_dist - risk_dist = abs(ts.entry_price - ts.stop_loss) - info_r = f" · ≈{info_dist / risk_dist:.1f}R" if risk_dist > 0 else "" - lines.append( - f"🎯 TP {_INFO_TP_PIPS}p: `{_fmt_price(info_price)}` (${info_dist:.2f}{info_r})" - ) - lines.append(f"⚖️ RR 1:{ts.risk_reward:.1f}") # Trailing-stop management plan (matches backtested exit model: # TP1 is advisory, the trail is the exit). Params are stamped on @@ -433,11 +468,33 @@ def format_signal_message(report: SignalReport) -> str: trail_dist = dist_r * risk_dist tag = " (pullback)" if is_pb else "" lines.append( - f"🔄 Trail{tag}: arm `{_fmt_price(arm_price)}` " - f"(+{arm_r:.1f}R), trail SL ${trail_dist:.2f} " - f"behind best price — TP1 is a guide, let it run" + f"🔄 Exit rule{tag} (tested): once price reaches " + f"`{_fmt_price(arm_price)}` (+{arm_r:.1f}R), trail the SL " + f"${trail_dist:.2f} behind the best price, moving it at " + f"candle close only. Otherwise the SL is the exit." ) + tp1_r = (abs(ts.take_profit_1 - ts.entry_price) / risk_dist + if risk_dist > 0 else 0.0) + lines.append( + f"🎯 TP1 guide `{_fmt_price(ts.take_profit_1)}` " + f"(${reward_usd:.2f} / {ts.reward_pips:.0f}p = {tp1_r:.1f}R)" + ) + if ts.take_profit_2: + tp2_r = (abs(ts.take_profit_2 - ts.entry_price) / risk_dist + if risk_dist > 0 else 0.0) + lines.append( + f"🎯 TP2 guide `{_fmt_price(ts.take_profit_2)}` ({tp2_r:.1f}R)") + + lines.append(f"⚖️ Planned TP1 RR 1:{ts.risk_reward:.1f} (guide only — the trail is the exit)") + + # What this setup class actually paid in the validated books + # (agent_panel_base_rates.json, the table the AI estimate is + # anchored to): the honest expectation for a discretionary reader. + rate_line = _book_rate_line(report) + if rate_line: + lines.append(rate_line) + extra: list[str] = [] mtf = _mtf_line(report) if mtf: @@ -453,8 +510,8 @@ def format_signal_message(report: SignalReport) -> str: extra.append(_escape_md(report.watch_line)) if report.news_summary: extra.append(f"📰 {_escape_md(report.news_summary)}") - if report.panel_verdict and report.panel_verdict_source == "mean": - extra.extend(_panel_estimate_lines(report)) + if report.panel_verdict and (report.panel_probability or 0) > 0: + extra.extend(_panel_lines(report)) elif report.panel_verdict: mark = {"AGREE": "✅", "OPPOSE": "⛔", "NEUTRAL": "➖"}.get( report.panel_verdict, "") @@ -670,6 +727,55 @@ def _alert_sent_line(report: SignalReport, tf_label: str, delivered_to: int, _last_alert_quality: Dict[str, str] = {} # key → grade of last ALERTED signal +_last_alert_bar: Dict[str, pd.Timestamp] = {} # key → bar label of last ALERTED setup +# Entry cooldown, in primary bars, after an ALERTED setup (2026-09-26): +# the backtester's `_apply_cooldown` (cooldown_bars=5) never enters within +# 5 bars of an accepted entry, so every validated book counts one trade per +# move. Live had no such rule: the entry-moved re-alert below re-sent the +# same idea on consecutive bars while price ran (2026-09-15 08:00–08:45, +# 2026-09-18 06:00–06:30, 2026-09-22 18:00–19:40 …); over 2026-09-04 → +# 2026-09-25 those re-entries were 42 of 99 unique setups and −25.1R of +# the −37.8R raw advisory book (backtest/research/rr_and_sell_campaign_ +# 2026-09-26.md). Must equal backtest.cooldown_bars in gpu_backtest.py. +_ALERT_COOLDOWN_BARS = 5 + + +def _key_bar_seconds(key: str) -> Optional[int]: + """Candle length for an alert key ("XAU/USD_15min" → 900). None when + the key carries no known timeframe (test keys), which disables the + bar cooldown for that key.""" + tf_value = key.rsplit("_", 1)[-1] + for tf, secs in BaseDataProvider.CANDLE_SECONDS.items(): + if tf.value == tf_value: + return secs + return None + + +def _report_bar_ts(report: SignalReport) -> Optional[pd.Timestamp]: + """The report's primary bar label as a UTC timestamp; None when it + cannot be parsed (the cooldown then does not apply).""" + try: + ts = pd.Timestamp(report.timestamp) + except Exception: + return None + if pd.isna(ts): + return None + return ts.tz_localize("UTC") if ts.tzinfo is None else ts.tz_convert("UTC") + + +def _inside_alert_cooldown(key: str, report: SignalReport) -> bool: + """True while this key sits within _ALERT_COOLDOWN_BARS primary bars + of its last ALERTED setup's bar (the backtester's entry cooldown).""" + last = _last_alert_bar.get(key) + if last is None or report.trade_setup is None: + return False + secs = _key_bar_seconds(key) + now_bar = _report_bar_ts(report) + if secs is None or now_bar is None: + return False + elapsed = (now_bar - last).total_seconds() + return 0 <= elapsed < _ALERT_COOLDOWN_BARS * secs + def _alert_fingerprint(report: SignalReport) -> tuple: @@ -699,6 +805,14 @@ def _should_alert(key: str, report: SignalReport) -> bool: Pure check — call _commit_alert only after the message actually went out, so a failed Telegram send doesn't consume the alert for an hour. """ + if _inside_alert_cooldown(key, report): + # Same rule as the backtester: no second entry within 5 bars of + # the last one, whatever the grade or the entry drift did. + logger.info( + f"alert skipped: {key} {report.final_signal.name} grade " + f"{report.signal_quality} bar {report.timestamp} is inside the " + f"{_ALERT_COOLDOWN_BARS}-bar entry cooldown (backtester rule)") + return False fp = _alert_fingerprint(report) if _last_alert_fp.get(key) != fp: return True @@ -728,6 +842,9 @@ def _commit_alert(key: str, report: SignalReport) -> None: _last_alert_setup[key] = ( setup.entry_price, abs(setup.entry_price - setup.stop_loss) ) + bar = _report_bar_ts(report) + if bar is not None: + _last_alert_bar[key] = bar else: _last_alert_setup.pop(key, None) @@ -1031,6 +1148,17 @@ def _cleanup_cooldowns() -> None: async def _handle_breach(bot, pair_symbol: str, price: float, level_name: str) -> None: + # No M15 bar exists to read while the bar that "just closed" falls in + # gold's daily settlement pause (21:00–22:00 UTC in summer) — the + # first post-pause bar closes 15 min after the pause ends. A rescan + # here only re-reads the last pre-pause bar: the 2026-09-26 review of + # signals.db found the 20:45 UTC bar logged again at 22:01, 22:06 and + # 22:11 every evening from these tick-driven rescans (the candle loop + # already skips the same window, `_closed_bar_in_settlement`). + if _closed_bar_in_settlement(15): + logger.info(f"Level breach: {pair_symbol} crossed {level_name} at {price} — " + f"skipped, the M15 bar to scan is inside the settlement pause") + return now = time.time() if now - _scan_cooldowns.get(pair_symbol, 0) < _WS_COOLDOWN: # Park the crossing instead of dropping it — price holding beyond diff --git a/test_all.py b/test_all.py index da871a7..67516aa 100644 --- a/test_all.py +++ b/test_all.py @@ -1475,6 +1475,56 @@ def _(): # MODULE 6: main.py # ══════════════════════════════════════════════════════════════════════ +@test("Printed-target floor: generic setup prints TP1 at >= 1R after the gates judged the raw target") +def _(): + from engine import TechnicalAnalysis + from signal_aggregator import SignalReport + from pair_signals.xauusd import make_config + + def build(print_floor): + cfg = make_config(Timeframe.M5) # tp_floor_rr 0.5 on M5 + cfg.signals.tp_print_floor_rr = print_floor + agg = SignalAggregator(cfg) + r = SignalReport(symbol="XAU/USD", display_name="XAU/USD", timestamp="2026-09-15 08:00:00+00:00") + r.final_signal = SignalStrength.BUY + r.signal_quality = "B" + r.current_price = 3300.0 + r.final_score = 30.0 + # ATR 8: stop = swing low 3290 - 1 ATR = 3282 (18 = 2.25 ATR, above the + # 2 ATR / $8 floors); the swing high at 3304 fails the 0.5R floor, so + # TP1 = max(1.5 ATR, 0.5 x 18) = 12 -> 3312 (passes the $9 spread gate). + r.primary_analysis = TechnicalAnalysis( + symbol="XAU/USD", timeframe="5min", timestamp="t", + key_levels={"atr": 8.0, "swing_low_1": 3290.0, "swing_high_1": 3304.0}) + return agg._generate_trade_setup(r, 0.10) + + raw = build(0.0) # floor off: the historical geometry + assert raw is not None and raw.strategy in (None, "GENERIC") + assert abs(raw.stop_loss - 3282.0) < 1e-9 and abs(raw.take_profit_1 - 3312.0) < 1e-9 + assert raw.risk_reward == 0.67 + floored = build(1.0) # the shipped default + assert floored is not None, "the floor must never veto a setup the gates accepted" + assert abs(floored.stop_loss - raw.stop_loss) < 1e-9, "the stop (the trade) is unchanged" + assert abs(floored.entry_price - raw.entry_price) < 1e-9 + assert abs(floored.take_profit_1 - 3318.0) < 1e-9, "TP1 printed at exactly 1R" + assert floored.risk_reward == 1.0 + assert floored.take_profit_2 >= floored.take_profit_1 + 8.0 - 1e-9, "TP2 stays >= 1 ATR beyond TP1" + assert abs(floored.reward_pips - 180.0) < 1e-9 and abs(floored.risk_pips - 180.0) < 1e-9 + # A target the gates reject stays rejected: ATR 4 -> TP1 6 < $9 spread gate, + # with or without the printed floor (the floor runs after the gate). + cfg = make_config(Timeframe.M5); agg = SignalAggregator(cfg) + r = SignalReport(symbol="XAU/USD", display_name="XAU/USD", timestamp="t") + r.final_signal = SignalStrength.BUY; r.signal_quality = "B"; r.current_price = 3300.0; r.final_score = 30.0 + r.primary_analysis = TechnicalAnalysis(symbol="XAU/USD", timeframe="5min", timestamp="t", + key_levels={"atr": 4.0, "swing_low_1": 3296.0}) + assert agg._generate_trade_setup(r, 0.10) is None + assert any("SPREAD GATE" in w for w in r.warnings) + # The effective floor is max(tp_floor_rr, tp_print_floor_rr). + assert SignalAggregator(make_config(Timeframe.M15))._printed_target_floor() == 1.0 + m5 = make_config(Timeframe.M5); m5.signals.tp_print_floor_rr = 0.0 + assert SignalAggregator(m5)._printed_target_floor() == 0.5 + + print("\n═══ Module 6: main.py ═══") from main import ( @@ -1716,34 +1766,19 @@ def _(): assert "$7.50" in msg -@test("format_signal_message renders 100-pip informational TP") +@test("format_signal_message has no fixed-pip target line (removed 2026-09-26 by owner request)") def _(): - def _setup(direction="BUY", **extra): - return TradeSetup( - pair="XAU/USD", display_name="XAU/USD", direction=direction, - entry_price=3300.0, stop_loss=3290.0, take_profit_1=3315.0, - take_profit_2=3325.0, risk_reward=1.5, risk_pips=100, - reward_pips=150, pip_value=0.10, **extra, - ) report = SignalReport(symbol="XAU/USD", display_name="XAU/USD", timestamp="now") report.final_signal = SignalStrength.BUY report.signal_quality = "A" - # BUY: 100 pips × $0.10 = $10 above entry; risk $10 → exactly 1.0R. - report.trade_setup = _setup() + report.trade_setup = TradeSetup( + pair="XAU/USD", display_name="XAU/USD", direction="BUY", + entry_price=3300.0, stop_loss=3290.0, take_profit_1=3315.0, + take_profit_2=3325.0, risk_reward=1.5, risk_pips=100, + reward_pips=150, pip_value=0.10) msg = format_signal_message(report) - assert "TP 100p" in msg - assert "3310.00" in msg, "BUY 100p TP must be entry + $10.00" - assert "≈1.0R" in msg - # SELL mirror: $10 below entry. - report.final_signal = SignalStrength.SELL - report.trade_setup = _setup(direction="SELL") - msg = format_signal_message(report) - assert "TP 100p" in msg - assert "3290.00" in msg, "SELL 100p TP must be entry - $10.00" - # No setup → no informational TP line. - report.trade_setup = None - msg = format_signal_message(report) - assert "TP 100p" not in msg + assert "TP 100p" not in msg and "TP 100" not in msg + assert not hasattr(__import__("telegram_bot"), "_INFO_TP_PIPS") @test("_indicator_summary condenses indicators to one arrow line") @@ -2117,6 +2152,8 @@ def _(): "candlestick": 1.0, "divergence": 2.0, "adx": 1.0, }, } + # Grade-A SELLs blocked on M15 (2026-09-26 campaign). + inline_cfg.xau_strategy.sell_min_quality = ["A+", "B"] if tf == Timeframe.M5: # Documented per-TF overrides (see xauusd.py docstring): # hour 9 UTC blocked, hour 21 UNBLOCKED (2026-07-30 @@ -2618,6 +2655,120 @@ def _(): d.pop(key, None) +@test("Alert cooldown: no second alert within 5 bars of the last alerted entry (backtester rule)") +def _(): + import telegram_bot as tb + from signal_aggregator import SignalReport, TradeSetup + + def rpt(bar, entry, quality="A"): + r = SignalReport(symbol="XAU/USD", display_name="XAU/USD", timestamp=bar) + r.final_signal = SignalStrength.SELL + r.signal_quality = quality + r.trade_setup = TradeSetup( + pair="XAU/USD", display_name="XAU/USD", direction="SELL", + entry_price=entry, stop_loss=entry + 30.0, take_profit_1=entry - 30.0, + take_profit_2=entry - 45.0, risk_reward=1.0, risk_pips=300.0, + reward_pips=300.0, pip_value=0.10, strategy="GENERIC") + return r + + key = "XAU/USD_15min" # the live key shape: symbol + tf.value + dicts = (tb._last_alert_fp, tb._last_alert_ts, tb._last_alert_setup, + tb._last_alert_quality, tb._last_alert_bar) + saved = [dict(d) for d in dicts] + for d in dicts: + d.pop(key, None) + try: + assert tb._ALERT_COOLDOWN_BARS == 5, "must equal backtest.cooldown_bars" + assert tb._key_bar_seconds(key) == 900 + assert tb._key_bar_seconds("XAU/USD_5min") == 300 + assert tb._key_bar_seconds("TEST_ALERT_FP") is None, "unknown key → no cooldown" + # 2026-09-15: four consecutive M15 SELL setups as price fell (the + # live log alerted them all; the backtester takes one). + first = rpt("2026-09-15 08:00:00+00:00", 4300.0) + assert tb._should_alert(key, first) + assert not tb._inside_alert_cooldown(key, first), "nothing alerted yet" + # A failed send does not commit → the next bar is still eligible. + assert tb._should_alert(key, rpt("2026-09-15 08:15:00+00:00", 4280.0)) + tb._commit_alert(key, first) + for bar, px in (("2026-09-15 08:15:00+00:00", 4280.0), # entry moved 0.67R → old rule re-alerted + ("2026-09-15 08:30:00+00:00", 4260.0), + ("2026-09-15 08:45:00+00:00", 4240.0), + ("2026-09-15 09:00:00+00:00", 4220.0)): # 4 bars later: still inside + r = rpt(bar, px) + assert tb._inside_alert_cooldown(key, r), bar + assert not tb._should_alert(key, r), f"{bar} must be inside the 5-bar cooldown" + # A grade upgrade does not break the cooldown either. + assert not tb._should_alert(key, rpt("2026-09-15 08:30:00+00:00", 4260.0, "A+")) + # 5 bars after the alerted entry the cooldown has lapsed; the + # entry has moved far more than half the risk → re-alert. + later = rpt("2026-09-15 09:15:00+00:00", 4200.0) + assert not tb._inside_alert_cooldown(key, later) + assert tb._should_alert(key, later) + # A report without a setup is never held by the cooldown (it is + # not an alert candidate anyway) and an unparsable bar label + # disables the rule rather than blocking alerts. + no_setup = rpt("2026-09-15 08:15:00+00:00", 4280.0); no_setup.trade_setup = None + assert not tb._inside_alert_cooldown(key, no_setup) + assert not tb._inside_alert_cooldown(key, rpt("not-a-time", 4280.0)) + # The cooldown is per key: the M5 loop is untouched by an M15 alert. + assert not tb._inside_alert_cooldown("XAU/USD_5min", rpt("2026-09-15 08:05:00+00:00", 4280.0)) + # The bar label is recorded at commit time from the report. + assert tb._last_alert_bar[key] == pd.Timestamp("2026-09-15 08:00:00", tz="UTC") + finally: + for d, old in zip(dicts, saved): + d.clear(); d.update(old) + + +@test("format_signal_message: trail first as the tested exit, TP1 a guide with its R, book line") +def _(): + import telegram_bot as tb + import agent_panel + from signal_aggregator import SignalReport, TradeSetup + from engine import TechnicalAnalysis + + report = SignalReport(symbol="XAU/USD", display_name="XAU/USD", timestamp="now") + report.final_signal = SignalStrength.BUY + report.signal_quality = "A" + report.primary_analysis = TechnicalAnalysis(symbol="XAU/USD", timeframe="15min", timestamp="now") + # Grade-A gold ATR geometry: stop 2 ATR, TP1 1.5 ATR → planned RR 0.75. + report.trade_setup = TradeSetup( + pair="XAU/USD", display_name="XAU/USD", direction="BUY", + entry_price=3300.0, stop_loss=3280.0, take_profit_1=3315.0, + take_profit_2=3322.5, risk_reward=0.75, risk_pips=200.0, + reward_pips=150.0, pip_value=0.10, strategy="XAU_ATR", + trail_arm_r=0.70, trail_dist_r=0.75) + saved = agent_panel._base_rates_cache + agent_panel._base_rates_cache = {"classes": { + "15min|XAU_ATR|A|BUY": {"n": 120, "win_rate": 55.0, "mean_r": 0.04}, + "15min|*|*|BUY": {"n": 635, "win_rate": 63.31, "mean_r": 0.2309}, + }} + try: + msg = tb.format_signal_message(report) + assert "Exit rule (tested)" in msg + assert "TP1 guide" in msg and "= 0.8R" in msg, "TP1 must carry its R (1.5 ATR / 2 ATR)" + assert "TP2 guide" in msg and "(1.1R)" in msg + assert "= 1R" in msg, "the SL line names 1R" + assert "Planned TP1 RR 1:0.8" in msg and "the trail is the exit" in msg + assert msg.index("Exit rule") < msg.index("TP1 guide") < msg.index("Planned TP1 RR"), \ + "the tested exit must be printed before the guides" + assert "📚 Book (this setup class): 120 trades, 55% winners, avg +0.04R per trade" in msg + assert "TP1 is a guide, let it run" not in msg, "old wording replaced" + # Under 30 trades in the exact class → the wider direction class. + agent_panel._base_rates_cache["classes"]["15min|XAU_ATR|A|BUY"]["n"] = 12 + msg = tb.format_signal_message(report) + assert "Book (BUY setups on this timeframe): 635 trades, 63% winners, avg +0.23R" in msg + # No table → no book line, and the message still renders. + agent_panel._base_rates_cache = {} + msg = tb.format_signal_message(report) + assert "📚 Book" not in msg and "Exit rule (tested)" in msg + # No primary analysis (hand-built report) → no book line, no error. + agent_panel._base_rates_cache = {"classes": {"15min|*|*|*": {"n": 9, "win_rate": 1, "mean_r": 1}}} + report.primary_analysis = None + assert "📚 Book" not in tb.format_signal_message(report) + finally: + agent_panel._base_rates_cache = saved + + @test("Alert candidates: a quality-passing signal without a trade setup is skipped and logged") def _(): import logging @@ -2917,11 +3068,25 @@ def _(): assert ts.strategy == "XAU_CP" and ts.direction == "BUY" assert ts.setup_pattern == "double_bottom" assert ts.stop_loss < ts.entry_price - assert abs((ts.take_profit_1 - ts.entry_price) - 19.0) < 1e-6, \ - "TP1 must be the measured move" + # The measured move (19) is the gated target; the PRINTED TP1 is at + # least 1R since 2026-09-26 (tp_print_floor_rr), never less than the + # measured move. + sl_dist = ts.entry_price - ts.stop_loss + assert abs((ts.take_profit_1 - ts.entry_price) - max(19.0, sl_dist)) < 1e-6, \ + "printed TP1 must be max(measured move, 1R)" + assert ts.risk_reward >= 1.0 assert rep.signal_quality == "B" assert rep.bullish_reasons[0].startswith("XAU CHART PATTERN"), \ "tag marker must sit at reasons[0]" + # With the printed floor off, TP1 is exactly the measured move (the + # historical geometry) and the stop is identical: the floor never + # changes the trade. + cfg2 = _cp_config(); cfg2.signals.tp_print_floor_rr = 0.0 + rep2 = _cp_report(df, cfg2) + SignalAggregator(cfg2)._try_chart_pattern(rep2, df, 0.10) + assert abs((rep2.trade_setup.take_profit_1 - rep2.trade_setup.entry_price) - 19.0) < 1e-6, \ + "TP1 must be the measured move when the printed floor is off" + assert abs(rep2.trade_setup.stop_loss - ts.stop_loss) < 1e-9 @test("CP gate: accepted setup removes stale vetoes and deduplicates extension context") @@ -5743,8 +5908,12 @@ def _(): cfg = _mk(tf) assert cfg.xau_strategy.sell_blocked_regimes == [], \ f"{tf}: sell_blocked_regimes must stay at the default off-M5" - assert cfg.xau_strategy.sell_min_quality == ["A+", "A", "B"], \ - f"{tf}: sell_min_quality must stay at the default off-H1" + # 2026-09-26 campaign: M15 blocks grade-A shorts (A+ and B still + # trade); H4 keeps the default allow-list. + assert _mk(Timeframe.M15).xau_strategy.sell_min_quality == ["A+", "B"], \ + "M15 must block grade-A SELLs (M15 override 6)" + assert _mk(Timeframe.H4).xau_strategy.sell_min_quality == ["A+", "A", "B"], \ + "H4: sell_min_quality must stay at the default" @test("GPU parity: cache miss falls back LOUDLY (counter + warning)") @@ -6107,6 +6276,37 @@ def _(): _aio21.run(main()) +@test("Breach rescan is skipped while the M15 bar to read sits in the settlement pause") +def _(): + async def main(): + _m21c_reset() + _tb21._autoscan_chats.add(911) + calls = {"n": 0} + good = _m21c_report() + + async def working(*a, **k): + calls["n"] += 1 + return good + + bot = _M21cBot() + with patch.object(_tb21, "run_signal", working): + # 2026-09-22 22:01 UTC: the bar "just closed" is labelled 21:46, + # inside the pause — the live log re-logged the 20:45 bar here. + with patch.object(_tb21, "_closed_bar_in_settlement", lambda *a, **k: True): + await _tb21._handle_breach(bot, "XAU/USD", 3301.0, "swing_high_1") + assert calls["n"] == 0, "no rescan during the settlement pause" + assert not bot.sent, "and no alert" + assert "XAU/USD" not in _tb21._pending_breach, "nothing parked either" + assert "XAU/USD" not in _tb21._scan_cooldowns, "the cooldown is not consumed" + # Outside the pause the same crossing rescans as before. + with patch.object(_tb21, "_closed_bar_in_settlement", lambda *a, **k: False): + await _tb21._handle_breach(bot, "XAU/USD", 3301.0, "swing_high_1") + assert calls["n"] == 1 + _m21c_reset() + + _aio21.run(main()) + + @test("H33c: breach reuses this bar's M15 scan; a new bar rescans once") def _(): async def main(): @@ -7049,6 +7249,15 @@ def _(): assert v.source == "macro" assert [t.persona for t in v.takes] == \ ["macro", "technical", "risk", "contrarian"] + # The macro take's p_win is the verdict's probability and reaches + # the report (2026-09-26: every live v2 row under this source had + # panel_probability NULL because the verdict carried none). + macro_take = next(t for t in v.takes if t.persona == "macro") + assert macro_take.p_win is not None # v1 reply -> pseudo-probability + assert v.probability == round(float(macro_take.p_win), 1) + rep = _panel_report() + ap._write_fields(rep, v, "MISS") + assert rep.panel_probability == v.probability and rep.panel_probability > 0 finally: lc._client = None ap.reset_panel_state() @@ -13336,13 +13545,20 @@ def _(): "p_win": 30.0, "reason_for": "f", "reason_against": "The stop is huge"}, ] msg = tb.format_signal_message(report) - assert "AI estimate" in msg and "45%" in msg and "60%" in msg - assert "Still being tested" in msg - assert "Biggest worry: The stop is huge" in msg, "lowest p_win's reason_against" + # 2026-09-26 owner rule: simple and clear for a new trader. + assert "AI check: the odds look worse than usual." in msg, "45 vs usual 60" + assert "about *45 in 100*" in msg and "usual for this kind of setup: 60 in 100" in msg + assert "The move already ran far." in msg + assert "Against: The stop is huge" in msg, "the take furthest from 50 gives the reasons" assert "macro 45%" in msg and "technical 65%" in msg and "risk 30%" in msg - tail = msg.split("AI estimate", 1)[1] + assert "Do not trade on it alone" in msg + tail = msg.split("AI check", 1)[1] assert "NEUTRAL" not in tail and "AGREE" not in tail and "OPPOSE" not in tail, \ - "the v2 line shows a probability, never a verdict word" + "the line shows a probability, never a verdict word" + assert "Still being tested" not in msg and "Biggest worry" not in msg and "%* chance" not in msg + assert tb._panel_odds_word(66.0, 60.0) == "the odds look about usual" + assert tb._panel_odds_word(68.0, 60.0) == "the odds look better than usual" + assert tb._panel_odds_word(45.0, None) == "the odds look about even" d = report_to_dict(report) assert d["panel_probability"] == 45.0 and d["panel_verdict_source"] == "mean" assert d["panel_prompt_version"] == ap.PANEL_PROMPT_VERSION @@ -13353,13 +13569,18 @@ def _(): print_signal_report(report) out = buf.getvalue() assert "AI estimate" in out and "45%" in out and "against: The stop is huge" in out - # legacy (macro) rendering is byte-for-byte the old format + # a macro-source verdict WITH a probability (since 2026-09-26) renders + # the same plain block; the legacy verdict line is only for a verdict + # that carries no probability (v1 rows, the chair). report.panel_verdict_source = "macro" report.panel_verdict = "OPPOSE" report.panel_confidence = 64.0 + macro = tb.format_signal_message(report) + assert "AI check" in macro and "OPPOSE" not in macro.split("AI check", 1)[1] + report.panel_probability = 0.0 legacy = tb.format_signal_message(report) assert "AI macro view" in legacy and "OPPOSE" in legacy and "(64%)" in legacy - assert "AI estimate" not in legacy + assert "AI check" not in legacy @test("app_config: agent_panel.verdict_source accepts mean, rejects others") @@ -13409,7 +13630,7 @@ def _(): # single-persona rendering: no votes line, the headline carries the number import telegram_bot as tb msg = tb.format_signal_message(report) - assert "AI estimate" in msg and "58%" in msg + assert "AI check" in msg and "58 in 100" in msg assert "macro 58%" not in msg, "one persona: no separate votes line" assert "macro 58%" in tb.format_signal_message(report2) finally: