From c5f6b07a3e53834c0cacbc90ad3c324a174d8425 Mon Sep 17 00:00:00 2001
From: Dennis Thiessen
Date: Tue, 30 Jun 2026 17:14:54 +0200
Subject: [PATCH] feat: extend take-profit sweep into the tail + clarify it
ignores the target
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Avg R was still rising at the previous top level (+15%), so the optimum was off
the table. Extend TP_LEVELS to 20/25/30% to reveal where letting winners run
stops paying (it plateaus toward "just hold to the horizon close").
Also clarify in the panel that the take-profit model deliberately does NOT use
the setup's S/R target — it's a standalone fixed-% exit; exiting at the target is
the target-vs-stop model above. The two are complementary ends, not in conflict.
Co-Authored-By: Claude Opus 4.8
---
app/services/backtest_service.py | 5 ++++-
frontend/src/components/signals/BacktestPanel.tsx | 3 ++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/app/services/backtest_service.py b/app/services/backtest_service.py
index fac7393..147096d 100644
--- a/app/services/backtest_service.py
+++ b/app/services/backtest_service.py
@@ -322,7 +322,10 @@ def _bucket_stats(cands: list[dict]) -> dict:
# Fixed take-profit levels (fractions) swept for the take-profit exit model.
-TP_LEVELS = (0.04, 0.06, 0.08, 0.10, 0.12, 0.15)
+# Extended into the tail so the avg-R peak/plateau is visible (it's where letting
+# winners run stops paying). Note: this model ignores the setup's S/R target —
+# it's a standalone fixed-% exit; exiting at the target is the target model.
+TP_LEVELS = (0.04, 0.06, 0.08, 0.10, 0.12, 0.15, 0.20, 0.25, 0.30)
def _take_profit_bucket(cands: list[dict], tp: float) -> dict:
diff --git a/frontend/src/components/signals/BacktestPanel.tsx b/frontend/src/components/signals/BacktestPanel.tsx
index d43fbad..f3dd116 100644
--- a/frontend/src/components/signals/BacktestPanel.tsx
+++ b/frontend/src/components/signals/BacktestPanel.tsx
@@ -248,7 +248,8 @@ export function BacktestPanel() {
the stop, else exit at the {report.params.horizon_days}-day close. In R, so it compares to the
target model above. Hit Rate = how often you'd have banked
+X% (how far winners actually run) — no top-ticking, it's the level you'd really set.
- ★ = best avg R.
+ The setup's own S/R target is not used here (exiting at that target is the model
+ above); this is a pure fixed-% exit. ★ = best avg R.