Conviction campaign 2026-09-27 (FAILED, research knob kept) + plain-words alert wording

Owner question: can score and confidence be combined into one number that is
easier to understand and for the system to decide on?

Research (pre-registered, nothing ships to the signal path):
- signals.grading_mode: "pair" (default, the shipped two-floor rule, byte-
  identical: all five reference MD5s reproduce on both scripts), "min"
  (min(|score|, 0.6*conf)), "product" (|score|*conf/100); thresholds fixed from
  the control's corner points, no tuning. Helpers _final_signal_and_grade /
  _transitioning_boost / conviction / CONVICTION_THRESHOLDS in signal_aggregator;
  mirrored in K2 (GRADING_MODE_CODES, PF_conv_* packed from the live module);
  twin refuses other values; M15 OOS MD5 equal on both scripts for both modes.
- Verdict: FAILED on M5 and M15. K-MIN moves 43-54 trades for -1.2R / -2.9R
  (clauses a,b,e / a,b,d); K-PROD -29R on M15 (PF 1.29, W3 DD 28.9R), neutral
  on M5. H1/H4 product +11.9R / +17.0R on W3 from extra low-score trades
  (H1 PF 1.98 -> 1.75, DD doubles): hypothesis only.
- Code review (high) before the runs: 6 findings fixed, 1 recorded
  (engine_config_fingerprint omits the switch under "pair", so live rows keep
  their fingerprint).

Delivery (books untouched): alerts lead with the grade and print the two raw
numbers in words (main.plain_words: "Direction: strong buy · Reliability: good
· timeframes agree", cut-offs = the grade table's own) on Telegram, /status,
the CLI and the dashboard card (plain_words in the snapshot); "MTF" label
replaced by "Other timeframes"; guides updated. Suite 566 passed.

Evidence: backtest/research/conviction_campaign_2026-09-27.md

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Harwinder Sekhon
2026-09-27 14:54:36 -04:00
parent 1454ea1838
commit 6af1237c2d
13 changed files with 808 additions and 59 deletions
+21 -7
View File
@@ -398,12 +398,22 @@ def _book_rate_line(report: SignalReport) -> Optional[str]:
f"{rate['win_rate']:.0f}% winners, avg {rate['mean_r']:+.2f}R per trade")
def _plain_words_line(report: SignalReport) -> str:
"""Grade first, then the two raw numbers in words (owner request
2026-09-27): "Direction: strong buy · Reliability: good · timeframes
agree" replaces "Score +38.2 · Conf 62% · MTF ✅". The words come from
main.plain_words so every surface says the same thing."""
from main import direction_words, reliability_words # display helper, no side effects
agree = "timeframes agree" if report.mtf_agreement else "timeframes disagree"
return (f"📐 Direction: *{direction_words(report.final_score)}* · "
f"Reliability: *{reliability_words(report.final_confidence)}* · {agree}")
def format_signal_message(report: SignalReport) -> str:
sig = report.final_signal
emoji = _SIGNAL_EMOJI.get(sig, "⬜")
qe = _QUALITY_EMOJI.get(report.signal_quality, "")
label = _signal_label(sig)
mtf_tag = "MTF ✅" if report.mtf_agreement else "MTF ❌"
tf = report.primary_analysis.timeframe if report.primary_analysis else ""
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
@@ -411,14 +421,14 @@ def format_signal_message(report: SignalReport) -> str:
vol_str = _escape_md(str(report.volatility_regime))
lines: list[str] = [
f"{emoji} *{label} · {report.display_name} · {tf}* {qe}{report.signal_quality}",
f"{emoji} *Grade {report.signal_quality} · {label} · {report.display_name} · {tf}* {qe}",
f"🕐 {now} · 💰 {_fmt_price(report.current_price)}",
]
live = _live_price_line(report.symbol)
if live:
lines.append(live)
lines += [
f"Score *{report.final_score:+.1f}* · Conf *{report.final_confidence:.0f}%* · {mtf_tag}",
_plain_words_line(report),
f"📊 Regime: {regime_str} · Vol: {vol_str}",
]
if report.action_recommendation:
@@ -498,7 +508,7 @@ def format_signal_message(report: SignalReport) -> str:
extra: list[str] = []
mtf = _mtf_line(report)
if mtf:
extra.append(f"📈 MTF: {mtf}")
extra.append(f"📈 Other timeframes: {mtf}")
ind_summary = _indicator_summary(report)
if ind_summary:
extra.append(f"📋 {ind_summary}")
@@ -1852,6 +1862,7 @@ async def _cb_scan_all(query, symbol: str = XAUUSD) -> None:
"",
]
from main import direction_words, reliability_words # display helper (2026-09-27)
for r in reports:
emoji = _SIGNAL_EMOJI.get(r.final_signal, "⬜")
qe = _QUALITY_EMOJI.get(r.signal_quality, "")
@@ -1859,10 +1870,13 @@ async def _cb_scan_all(query, symbol: str = XAUUSD) -> None:
sig = _signal_label(r.final_signal)
regime = r.market_regime.replace("_", " ").title()
lines.append(
f"{emoji} *{tf_label}* {sig} {qe}{r.signal_quality} "
f"Score: {r.final_score:+.1f} {r.final_confidence:.0f}%"
f"{emoji} *{tf_label}* Grade {r.signal_quality} {qe} · {sig}"
)
lines.append(f" _{_escape_md(regime)}_ | {'✅ MTF' if r.mtf_agreement else '❌ MTF'}")
lines.append(
f" {direction_words(r.final_score)}, {reliability_words(r.final_confidence)} reliability, "
f"{'timeframes agree' if r.mtf_agreement else 'timeframes disagree'}"
)
lines.append(f" _{_escape_md(regime)}_")
if r.trade_setup and r.final_signal != SignalStrength.NEUTRAL:
ts = r.trade_setup
d = "📈" if ts.direction == "BUY" else "📉"