Skip to content

Fast-analysis suggested TP/SL frequently implies risk/reward < 1 (no floor or warning) #216

Description

@StevenSN01

Summary

The ATR/support-resistance trading levels in fast analysis frequently produce take-profit/stop-loss pairs where the reward distance is smaller than the risk distance (R/R < 1), and the computed risk_reward_ratio is never enforced or flagged anywhere.

Location

backend_api_python/app/services/market/technical_indicators.py (v5.0.17):

atr_stop_loss = current_price - (2 * atr)
suggested_stop_loss = max(atr_stop_loss, support_stop * 0.99)      # picks the NEARER stop
atr_take_profit = current_price + (3 * atr)
suggested_take_profit = min(atr_take_profit, resistance_tp * 1.01) # picks the NEARER target
risk = current_price - suggested_stop_loss
reward = suggested_take_profit - current_price
"risk_reward_ratio": round(reward / risk, 2) if risk > 0 else 0,

When price trades near resistance (e.g. price_position ≈ 80), the take-profit gets pinned just above the nearby resistance while the stop keeps the full 2×ATR distance — R/R collapses below 1. This is asymmetric by construction: the stop takes the nearer anchor, the target also takes the nearer anchor.

Additionally, _finalize_trading_plan_for_decision in fast_analysis.py overwrites the LLM's chosen stop_loss/take_profit with these suggested levels whenever they exist, so every displayed plan uses this construction regardless of what the model proposed.

Production evidence

From a v5.0.17 deployment running hourly fast-analysis on 6 symbols (crypto + US stocks), 4 days, 518 completed analyses:

  • 70% of trading plans had R/R < 1.0 (average 0.91, computed from each plan's entry/sl/tp)
  • During a strong rally in the window (BTC +24%, ETH +33%, SOL +25%, HYPE +32%), signals were issued with price at the 77–81st percentile of the recent range
  • risk_reward_ratio shown alongside the plan can be < 1 with no warning to the user

Backtest experiments (important: the naive fix is harmful)

We replicated this construction as a fade-overbought short strategy and ran it through QuantDinger's own Strategy V2 backtest engine (BTC & ETH, 1D, 2024-09 → 2026-08, commission 0.04%, slippage 0.05%):

Variant BTC ETH Trades
Current construction (baseline) −10.6% −43.9% 12 / 9
Naive fix: force R/R ≥ 1.2 by widening TP −30.5% (win rate 67% → 36%) −36.4% 11 / 9
Naive fix: block counter-trend entries when MA20>MA60 & price>MA60 0.0% (0 trades in 2 years) −27.8% 0 / 2

(Small samples, n = 0–13 trades — directional, not statistically significant.)

The takeaway: simply widening the take-profit to enforce a floor made BTC three times worse, because the near resistance-cap was the only thing giving the construction its hit rate. Any remedy here should be backtested before shipping.

Suggestions

  1. Minimum: when the finalized plan implies R/R < 1, flag it in the response/UI (e.g. rr_warning: true) instead of silently presenting it as a normal plan.
  2. Consider using the next resistance level (R2) when R1 is within ~2×ATR of price — but please validate with backtests first; our data suggests naive TP widening is counterproductive.
  3. Consider whether _finalize_trading_plan_for_decision discarding the LLM's levels is intended — it makes the detailed prompt instructions for stop/target placement moot.

Happy to share the replay methodology or backtest code if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions