Quant review 2026-09-26: alert cooldown, plain-language alerts, M15 grade-A SELL block, 1R printed targets, panel/breach fixes

Live signals.db review (2026-09-04 → 09-25, 100 unique setups replayed on the
scans' stored candles): the stop-hunt claim is refuted (71% of stop-outs 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 sits at the 3rd–5th percentile of the books' three-week
windows.

Delivery (book-neutral):
- telegram_bot: 5-bar alert cooldown per timeframe (_ALERT_COOLDOWN_BARS =
  backtest.cooldown_bars); setup block prints the trail first as the tested
  exit, TP1/TP2 as guides with their R, "Planned TP1 RR", and one validated-book
  line per setup class (agent_panel_base_rates.json); the informational 100-pip
  TP line is removed (owner request).
- AI block rewritten for a new trader: "AI check: the odds look better than /
  worse than / about usual", the chance "in 100" against the usual odds, one
  reason for and one against, no verdict word or confidence figure; rendered
  whenever the verdict carries a probability. agent_panel: persona prompts ban
  trading jargon (PANEL_PROMPT_VERSION v2.1-2026-09-26); the macro-source
  verdict now carries its persona's p_win (every live v2 row had
  panel_probability NULL). Dashboard and guides mirror the wording.
- telegram_bot: level-breach rescans skip the settlement-pause window (the
  20:45 M15 bar was re-logged at 22:01/22:06/22:11 every evening).

Signal (pre-registered rule-1 campaign, CUDA twin paired with gpu_backtest.py):
- M15 grade-A SELLs blocked (sell_min_quality ["A+","B"], M15 override 6):
  W2 813/+154.15R PF 1.53 vs 931/+146.14R PF 1.43, OOS +68.5 vs +60.67,
  W3 max DD 20.9 vs 19.3R. PASSED a–f.
- Printed target 1 floored at 1R in every generator (signals.tp_print_floor_rr),
  applied AFTER the min-RR and spread gates so no trade changes; mirrored in
  cuda_signal_kernels.py K2. Label-only re-pin of all four timeframes (only
  tp1/rr_planned columns move). A floor applied before the gate cost −24.6R and
  was rejected.
- FAILED: M15 min_rr_ratio 1.0 (trade-identical, −2.0R). Owner's call: M5
  tp_floor_rr 1.0 (+48.9R W2, max DD 10.1 → 15.9R), not shipped.

New pins: M5 W2 e599c8a8…, M15 W2 5b65ecf6…, M15 OOS 09b8a8e7…, H1 W3
a44fd19b…, H4 W3 f3587bae…; twin and reference MD5s equal on every window.
Base-rate table rebuilt from the new pins. Suite 558 passed.

Evidence: backtest/research/rr_and_sell_campaign_2026-09-26.md (plus the
2026-09-18 loss-anatomy and wrong-direction reports it builds on).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Harwinder Sekhon
2026-09-26 21:06:05 -04:00
parent 4f33480ce1
commit 1454ea1838
16 changed files with 1695 additions and 201 deletions
+42 -14
View File
@@ -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) |
+20 -5
View File
@@ -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": "<max 12 words>",
"reason_against": "<max 12 words>", "note": "<max 15 words>"}
@@ -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
+53 -69
View File
@@ -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"
}
+35 -6
View File
@@ -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);
@@ -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.
@@ -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 <tf> --start <s>
--end <e> --spread <0.65|0.30> [--set ...] --export-csv <out>` (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=<x>` 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).
@@ -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.
+10
View File
@@ -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
+8 -3
View File
@@ -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)</div>
🔄 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</div>
<ul>
<li><b>Grade (A+, A, B, C):</b> the bot's own mark for how clean the setup is. A+ is best. It never sends anything worse than C.</li>
<li><b>Confidence and the indicator list:</b> extra detail for curious people. You don't need to understand it to trade the signal.</li>
+14 -9
View File
@@ -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</div>
🛑 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</div>
<table>
<tr><th style="width:28%">Field</th><th>What it means</th></tr>
@@ -70,10 +73,12 @@
<tr><td><b>MTF aligned</b></td><td>The two confirmation timeframes agree with the trade direction. Signals without alignment are filtered out on M5.</td></tr>
<tr><td><b>Warnings ⚠️</b></td><td>Context, not veto. Backtesting shows warned trades perform about the same — strong momentum often triggers warnings.</td></tr>
<tr><td><b>Violent-move watch 🌪</b></td><td>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 <i>volatility</i>, not a direction call, and it <b>never</b> 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.</td></tr>
<tr><td><b>AI estimate 🤖</b></td><td>An advisory line such as <i>"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."</i> 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 <b>not</b> a recommendation and it <b>never</b> 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.</td></tr>
<tr><td><b>AI check 🤖</b></td><td>An advisory block written for a new trader, such as <i>"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."</i> 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 <b>not</b> a recommendation and it <b>never</b> 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.</td></tr>
<tr><td><b>Market context 📰</b></td><td>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.</td></tr>
<tr><td><b>SL / TP1 / RR</b></td><td>Stop loss sits behind the nearest swing level with an ATR buffer. TP1 is the nearest opposing level. <b>1R</b> = the entry-to-SL distance = the amount you risk.</td></tr>
<tr><td><b>Trail plan 🔄</b></td><td>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. <b>TP1 is advisory — let winners run.</b></td></tr>
<tr><td><b>SL / TP1 guide / planned RR</b></td><td>Stop loss sits behind the nearest swing level with an ATR buffer (grade A+/A setups use a 2 × ATR stop). <b>1R</b> = the entry-to-SL distance = the amount you risk. TP1 is a <i>guide</i> (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 <b>not</b> 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).</td></tr>
<tr><td><b>Exit rule 🔄</b></td><td>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. <b>TP1 is advisory — let winners run.</b></td></tr>
<tr><td><b>Book line 📚</b></td><td>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.</td></tr>
<tr><td><b>One alert per move</b></td><td>After 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.</td></tr>
</table>
<h2>3 · Acting on a signal — step by step</h2>
+16 -3
View File
@@ -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} "
+27
View File
@@ -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
+65 -1
View File
@@ -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
+12 -9
View File
@@ -595,22 +595,24 @@ function planHTML(ts) {
No trade plan. The signal did not clear the gate that releases an entry, stop and target.</div>`;
}
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 `<div class="plan ${buy ? "sig-buy" : "sig-sell"}">
<div class="plan-head">
<i class="fa-solid ${buy ? "fa-arrow-trend-up" : "fa-arrow-trend-down"}" aria-hidden="true"></i>
${buy ? "Buy plan" : "Sell plan"}
<span class="plan-strategy">${esc(ts.strategy || "")}</span>
<span class="plan-rr">${(+ts.risk_reward).toFixed(1)} to 1</span>
<span class="plan-rr" title="Planned target-1 distance divided by the stop distance. A guide only: the tested exit is the trailing stop.">planned ${(+ts.risk_reward).toFixed(1)} to 1</span>
</div>
<dl class="plan-grid">
<div><dt><i class="fa-solid fa-right-to-bracket" aria-hidden="true"></i>Entry</dt>
<dd>${fmt$(ts.entry)}</dd></div>
<div><dt><i class="fa-solid fa-shield-halved" aria-hidden="true"></i>Stop</dt>
<dd class="down">${fmt$(ts.stop_loss)}</dd></div>
<div><dt><i class="fa-solid fa-bullseye" aria-hidden="true"></i>Target 1</dt>
<div><dt><i class="fa-solid fa-bullseye" aria-hidden="true"></i>Target 1 (guide)</dt>
<dd class="up">${fmt$(ts.take_profit_1)}</dd></div>
<div><dt><i class="fa-solid fa-flag-checkered" aria-hidden="true"></i>Target 2</dt>
<dd class="up">${fmt$(ts.take_profit_2)}</dd></div>
@@ -873,21 +875,22 @@ function detailHTML(tf) {
${esc(r.news_summary || "")}</div>`);
}
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 =>
`<tr><td class="ind-k">${esc(t.persona)}</td>
<td class="num text-end">${t.p_win != null ? Math.round(t.p_win) + "%" : "—"}</td>
<td class="flat">${esc(t.reason_for || "")}</td>
<td class="flat">${esc(t.reason_against || "")}</td></tr>`).join("");
out += sec("AI estimate", `<div class="flat mb-2">
About a <strong>${Math.round(r.panel_probability || 0)}%</strong> chance this trade pays off.${esc(usual)}
It is advisory and still being tested — nothing in the signal reads it.<br>
${esc(r.panel_rationale || "")}</div>
Chance it pays off: about <strong>${Math.round(r.panel_probability || 0)} in 100</strong>.${esc(usual)}
${esc(r.panel_rationale || "")}<br>
This check is an experiment. Do not trade on it alone.</div>
${takes ? `<div class="detail-scroll"><table class="table table-sm d-table">
<thead><tr><th>Persona</th><th class="text-end">Chance</th><th>For</th><th>Against</th></tr></thead>
<tbody>${takes}</tbody></table></div>` : ""}`);
+175 -47
View File
@@ -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
+256 -35
View File
@@ -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: