{% extends "journal/base.html" %} {% block title %}Reports — Setup Log Pro{% endblock %} {% block content %}

Reports

{% for tab in view_tabs %} {{ tab.label }} {% endfor %}
– {% for label, from_date, to_date in date_presets %} {% endfor %}
{% if view == "mistakes" or view == "cost" or view == "setup_mistakes" %} {% endif %} {% if view == "duration" %}
{% if duration_group_threshold > 0 %} {% endif %}
{% endif %}
Clear
{% if view == "mistakes" %}

Mistake rate: this period vs. preceding

This period ({{ current_period.start|date:"M j" }}–{{ current_period.end|date:"M j, Y" }}) {% if current_period.pct is not None %}{{ current_period.pct|floatformat:1 }}%{% else %}—{% endif %}
Preceding period ({{ preceding_period.start|date:"M j" }}–{{ preceding_period.end|date:"M j, Y" }}) {% if preceding_period.pct is not None %}{{ preceding_period.pct|floatformat:1 }}%{% else %}—{% endif %}
Trend {% if trend is None %}— {% elif trend.improving %}↓ improving ({{ trend.diff|floatformat:1 }} pts) {% elif trend.worsening %}↑ worsening (+{{ trend.diff|floatformat:1 }} pts) {% else %}→ flat {% endif %}
Mistake trades {{ current_period.mistakes }} / {{ current_period.total }}
How to read the trend
TrendWhat it likely means
Improving (mistake % down) A smaller share of your trades involved a flagged mistake than last period — discipline is trending the right way.
Worsening (mistake % up) A larger share of your trades involved a flagged mistake — worth a look at what changed (new setup, fatigue, revenge trading, overtrading a specific symbol, etc). Check the monthly table below for exactly when it started.
Flat Not enough movement to draw a conclusion, especially on a small trade count.

Mistake rate by month

{% for row in monthly_rows %} {% empty %} {% endfor %}
Month Trades Mistakes Mistake %
{{ row.label }} {{ row.total }} {{ row.mistakes }} {% if row.pct is not None %}{{ row.pct|floatformat:1 }}%{% else %}—{% endif %}
No trades match the current filters.
{% elif view == "cost" %}

Cost of mistakes: this period vs. preceding

This period ({{ pnl_unit }}) ({{ current_cost_period.start|date:"M j" }}–{{ current_cost_period.end|date:"M j, Y" }}) {% if current_cost_period.total is not None %}{% if pnl_unit == "$" %}${{ current_cost_period.total|floatformat:2 }}{% else %}{{ current_cost_period.total|floatformat:2 }}R{% endif %}{% else %}—{% endif %}
Preceding period ({{ preceding_cost_period.start|date:"M j" }}–{{ preceding_cost_period.end|date:"M j, Y" }}) {% if preceding_cost_period.total is not None %}{% if pnl_unit == "$" %}${{ preceding_cost_period.total|floatformat:2 }}{% else %}{{ preceding_cost_period.total|floatformat:2 }}R{% endif %}{% else %}—{% endif %}
Trend {% if cost_trend is None %}— {% elif cost_trend.improving %}↑ improving ({% if pnl_unit == "$" %}+${{ cost_trend.diff|floatformat:2 }}{% else %}+{{ cost_trend.diff|floatformat:2 }}R{% endif %}) {% elif cost_trend.worsening %}↓ worsening ({% if pnl_unit == "$" %}${{ cost_trend.diff|floatformat:2 }}{% else %}{{ cost_trend.diff|floatformat:2 }}R{% endif %}) {% else %}→ flat {% endif %}
Mistake trades {{ current_cost_period.count }}
How to read the trend
TrendWhat it likely means
Improving (cost smaller/less negative) Mistakes cost you less this period — either you made fewer of them, or the ones you made were smaller (better damage control, e.g. cutting losses faster).
Worsening (cost bigger/more negative) Mistakes cost you more this period — could be more frequent mistakes, or the same frequency with bigger losses each time. Sort the table below by Total to see which specific mistake is driving it.
Flat Not enough movement to draw a conclusion, especially on a small trade count.

Cost by mistake

Total cost ({{ pnl_unit }}) {% if overall_cost is not None %}{% if pnl_unit == "$" %}${{ overall_cost|floatformat:2 }}{% else %}{{ overall_cost|floatformat:2 }}R{% endif %}{% else %}—{% endif %}
Mistake trades {{ overall_cost_count }}

A trade tagged with more than one mistake counts toward each one's total below, so the per-mistake rows can add up to more than the total above (which counts each mistake trade once).

RISING = happened more than once this period, also happened last period, and is happening more often now — a recurring pattern, not a one-off. BIGGEST IMPACT = one of the 3 costliest mistakes in the table below.

{% for row in cost_rows %} {% empty %} {% endfor %}
Mistake Trades Total ({{ pnl_unit }}) Avg ({{ pnl_unit }})
{{ row.tag }} {% if row.rising %}RISING{% endif %} {% if row.high_impact %}BIGGEST IMPACT{% endif %} {{ row.count }} {% if row.total is not None %}{% if pnl_unit == "$" %}${{ row.total|floatformat:2 }}{% else %}{{ row.total|floatformat:2 }}R{% endif %}{% else %}—{% endif %} {% if row.avg is not None %}{% if pnl_unit == "$" %}${{ row.avg|floatformat:2 }}{% else %}{{ row.avg|floatformat:2 }}R{% endif %}{% else %}—{% endif %}
No mistake-tagged trades match the current filters.
{% elif view == "setups" %}

Setups: this period vs. preceding

Real = every trade. No mistakes = same trades restricted to no mistake tag (or only ones currently excluded from the mistake count, e.g. NONE) — "how it would have looked without the mistakes."

This period ({{ current_overall.start|date:"M j" }}–{{ current_overall.end|date:"M j, Y" }})

Real ({{ pnl_unit }}) {% if current_overall.total_value is not None %}{% if pnl_unit == "$" %}${{ current_overall.total_value|floatformat:2 }}{% else %}{{ current_overall.total_value|floatformat:2 }}R{% endif %}{% else %}—{% endif %}
No mistakes ({{ pnl_unit }}) {% if current_overall_clean.total_value is not None %}{% if pnl_unit == "$" %}${{ current_overall_clean.total_value|floatformat:2 }}{% else %}{{ current_overall_clean.total_value|floatformat:2 }}R{% endif %}{% else %}—{% endif %}
Real win rate {% if current_overall.win_rate is not None %}{{ current_overall.win_rate|floatformat:1 }}%{% else %}—{% endif %}
No mistakes win rate {% if current_overall_clean.win_rate is not None %}{{ current_overall_clean.win_rate|floatformat:1 }}%{% else %}—{% endif %}
Trades {{ current_overall.total }} / {{ current_overall_clean.total }}

Preceding period ({{ preceding_overall.start|date:"M j" }}–{{ preceding_overall.end|date:"M j, Y" }})

Real ({{ pnl_unit }}) {% if preceding_overall.total_value is not None %}{% if pnl_unit == "$" %}${{ preceding_overall.total_value|floatformat:2 }}{% else %}{{ preceding_overall.total_value|floatformat:2 }}R{% endif %}{% else %}—{% endif %}
No mistakes ({{ pnl_unit }}) {% if preceding_overall_clean.total_value is not None %}{% if pnl_unit == "$" %}${{ preceding_overall_clean.total_value|floatformat:2 }}{% else %}{{ preceding_overall_clean.total_value|floatformat:2 }}R{% endif %}{% else %}—{% endif %}
Real win rate {% if preceding_overall.win_rate is not None %}{{ preceding_overall.win_rate|floatformat:1 }}%{% else %}—{% endif %}
No mistakes win rate {% if preceding_overall_clean.win_rate is not None %}{{ preceding_overall_clean.win_rate|floatformat:1 }}%{% else %}—{% endif %}
Trades {{ preceding_overall.total }} / {{ preceding_overall_clean.total }}

Trend (this period vs. preceding)

Real ({{ pnl_unit }}) {% if overall_value_trend is None %}— {% elif overall_value_trend.improving %}↑ improving (+{% if pnl_unit == "$" %}${{ overall_value_trend.diff|floatformat:2 }}{% else %}{{ overall_value_trend.diff|floatformat:2 }}R{% endif %}) {% elif overall_value_trend.worsening %}↓ worsening ({% if pnl_unit == "$" %}${{ overall_value_trend.diff|floatformat:2 }}{% else %}{{ overall_value_trend.diff|floatformat:2 }}R{% endif %}) {% else %}→ flat {% endif %}
No mistakes ({{ pnl_unit }}) {% if overall_clean_value_trend is None %}— {% elif overall_clean_value_trend.improving %}↑ improving (+{% if pnl_unit == "$" %}${{ overall_clean_value_trend.diff|floatformat:2 }}{% else %}{{ overall_clean_value_trend.diff|floatformat:2 }}R{% endif %}) {% elif overall_clean_value_trend.worsening %}↓ worsening ({% if pnl_unit == "$" %}${{ overall_clean_value_trend.diff|floatformat:2 }}{% else %}{{ overall_clean_value_trend.diff|floatformat:2 }}R{% endif %}) {% else %}→ flat {% endif %}
Real win rate {% if overall_win_rate_trend is None %}— {% elif overall_win_rate_trend.improving %}↑ improving (+{{ overall_win_rate_trend.diff|floatformat:1 }} pts) {% elif overall_win_rate_trend.worsening %}↓ worsening ({{ overall_win_rate_trend.diff|floatformat:1 }} pts) {% else %}→ flat {% endif %}
No mistakes win rate {% if overall_clean_win_rate_trend is None %}— {% elif overall_clean_win_rate_trend.improving %}↑ improving (+{{ overall_clean_win_rate_trend.diff|floatformat:1 }} pts) {% elif overall_clean_win_rate_trend.worsening %}↓ worsening ({{ overall_clean_win_rate_trend.diff|floatformat:1 }} pts) {% else %}→ flat {% endif %}
How to read the trend combinations
RealNo mistakesWhat it likely means
WorseningWorsening The decline isn't just about mistakes — your underlying setups/edge are losing too. Cutting mistakes alone won't fix this; the strategy itself needs a look.
WorseningImproving or flat Your clean trades are holding up fine or getting better, but mistakes are dragging the real numbers down. This is a discipline problem, not a strategy problem — fixing execution should show up directly in the real results.
ImprovingWorsening Overall results look better, but the mistake-free trades are actually getting worse — the improvement may be masking a fading edge (e.g. fewer mistakes this period, or a small sample skewing the "no mistakes" figure). Worth digging into before assuming things are genuinely better.
ImprovingImproving Genuine improvement — both discipline and edge are moving the right way.
Flatanything Not enough movement to draw a conclusion — likely normal noise, especially on a small trade count.

Setups

Total R reflects whatever's currently selected via the Date filter above (all-time if none is set).

{% for row in setup_rows %} {% empty %} {% endfor %}
Setup Trades Win rate Total R Avg R Avg Max Move (R) Avg Max Pullback (R)
{{ row.tag }} {{ row.count }} {% if row.win_rate is not None %}{{ row.win_rate|floatformat:1 }}%{% else %}—{% endif %} {% if row.total_r is not None %}{{ row.total_r|floatformat:2 }}R{% else %}—{% endif %} {% if row.avg_r is not None %}{{ row.avg_r|floatformat:2 }}R{% else %}—{% endif %} {% if row.avg_max_move is not None %}{{ row.avg_max_move|floatformat:2 }}R{% else %}—{% endif %} {% if row.avg_max_pullback is not None %}{{ row.avg_max_pullback|floatformat:2 }}R{% else %}—{% endif %}
No setup-tagged trades match the current filters.
{% elif view == "setup_mistakes" %}

Mistakes by setup

Trades reflect whatever's currently selected via the Date filter above (all-time if none is set). NONE (and any other tag currently excluded above) doesn't count as a mistake, and trades with no mistake tag at all are never counted.

How to read this data
SituationWhat it likely means
High mistake % + many trades A real, actionable pattern — this setup consistently correlates with mistakes. Worth asking whether the setup itself invites bad behavior (overtrading it, chasing entries, ignoring rules because it "feels" high-probability) or whether execution specifically around this setup needs tightening.
High mistake % + few trades Likely noise, not a pattern — a single mistake on a setup you've only taken once or twice will show 100%. Don't over-react until the trade count builds up.
Low mistake % + many trades A setup you execute cleanly and consistently — whatever discipline you have here, it's working.
Low trade count overall Not enough data yet to draw a conclusion either way.

Priority to address: setups with both a high trade count and a high mistake % — those are costing the most in aggregate, not just individually.

{% for row in setup_mistake_rows %} {% empty %} {% endfor %}
Setup Trades Mistakes Mistake % Cost R Cost $
{{ row.tag }} {{ row.total }} {{ row.mistake_count }} {% if row.pct is not None %}{{ row.pct|floatformat:1 }}%{% else %}—{% endif %} {% if row.mistake_total_r is not None %}{{ row.mistake_total_r|floatformat:2 }}R{% else %}—{% endif %} {% if row.mistake_total_pnl is not None %}${{ row.mistake_total_pnl|floatformat:2 }}{% else %}—{% endif %}
No setup-tagged trades match the current filters.
{% elif view == "duration" %}

Success rate by hold time

"Hold time" is how long the trade was held (exit minus entry), not time of day. Buckets reflect whatever's currently selected via the filters above (all-time if no Date filter is set).

By default every row is a raw 5-minute bucket. Use Group nearby buckets above to merge adjacent buckets whose win rates are close together — a value around {{ default_duration_group_threshold }} percentage points is a reasonable starting point: tight enough to keep genuinely different durations apart, loose enough to smooth out noise from small buckets (and it can pull a too-small bucket over the {{ min_trades_for_optimal }}-trade "optimal" threshold by combining it with a similar neighbor). Too high a value risks blending durations that actually behave differently. Leave it blank to see every raw bucket.

Read each row as your own historical odds: if a bucket shows a 55% win rate over 200 trades, that's roughly what you can expect the next time you hold a trade that long — it's not a coincidence, it's your track record. If win rate (and Total R) climb the longer you hold, that's real evidence from your own data that staying in the trade works for you — a reason to trust the process in the moment, not second-guess it.

How to read this data

Success rate here is win rate (wins ÷ closed trades) for trades held that long. "Optimal" below is the bucket with the highest win rate among buckets with at least {{ min_trades_for_optimal }} trades — a bucket with 1-2 trades can show 100% purely by luck, so it's deliberately excluded from being called optimal until there's enough of a sample to trust.

SituationWhat it likely means
High win rate + low avg R You're right often in this bucket but not making much when you are — small wins, or winners cut short. Win rate alone doesn't mean the duration is actually profitable; check Avg R alongside it.
Lower win rate + high avg R Fewer winners, but they're big enough to matter — a duration you might be exiting too early on the losers rather than one to avoid entirely.
Very short holds (under 5 min) underperforming Common pattern: fast in-and-out trades often correlate with impulsive entries rather than planned ones. Worth cross-checking against the Mistakes reports for the same period.
A bucket with very few trades Not enough data to trust the number either way — that's why it's excluded from "optimal" below regardless of how good or bad it looks. Try raising "Group nearby buckets" above to combine it with a similar neighbor.
{% if optimal_duration %}
Optimal duration {{ optimal_duration.label }}
Win rate {{ optimal_duration.win_rate|floatformat:1 }}%
Trades {{ optimal_duration.count }}
Avg R {% if optimal_duration.avg_r is not None %}{{ optimal_duration.avg_r|floatformat:2 }}R{% else %}—{% endif %}
{% else %}

No duration bucket has {{ min_trades_for_optimal }}+ trades yet, so no bucket is called out as optimal.

{% endif %} {% for row in duration_rows %} {% empty %} {% endfor %}
Hold time Trades Win rate Total R Avg R
{{ row.label }} {% if row.is_optimal %}OPTIMAL{% endif %} {% if not row.eligible %}(too few trades){% endif %} {{ row.count }} {% if row.win_rate is not None %}{{ row.win_rate|floatformat:1 }}%{% else %}—{% endif %} {% if row.total_r is not None %}{{ row.total_r|floatformat:2 }}R{% else %}—{% endif %} {% if row.avg_r is not None %}{{ row.avg_r|floatformat:2 }}R{% else %}—{% endif %}
No closed trades match the current filters.

Setups: optimal hold time

For each setup, the hold-time bucket with the best win rate among that setup's own trades (same {{ min_trades_for_optimal }}-trade minimum). Setups with too few trades in every bucket show "Not enough data" rather than a guess.

{% for row in setup_duration_rows %} {% if row.optimal %} {% else %} {% endif %} {% empty %} {% endfor %}
Setup Trades Optimal hold time Win rate Trades (bucket) Avg R
{{ row.tag }} {{ row.total_trades }}{{ row.optimal.label }} {{ row.optimal.win_rate|floatformat:1 }}% {{ row.optimal.count }} {% if row.optimal.avg_r is not None %}{{ row.optimal.avg_r|floatformat:2 }}R{% else %}—{% endif %}Not enough data
No setup-tagged trades match the current filters.

Full matrix: setup × hold time

Win rate for every setup against every hold-time bucket at once. Always the raw 5-minute buckets, regardless of the "Group nearby buckets" control above — grouping is adaptive per setup, so different setups would merge different buckets and the columns wouldn't line up. Bold green is that row's best bucket among ones with {{ min_trades_for_optimal }}+ trades; a dash means no trades in that cell.

{% for col in duration_matrix_columns %}{% endfor %} {% for row in duration_matrix_rows %} {% for cell in row.cells %} {% endfor %} {% empty %} {% endfor %}
Setup{{ col }}
{{ row.tag }} ({{ row.total_trades }}) {% if cell and cell.win_rate is not None %} {{ cell.win_rate|floatformat:0 }}% ({{ cell.count }}) {% else %} — {% endif %}
No setup-tagged trades match the current filters.
{% elif view == "timeofday" %}

Success rate by time of day

When the trade was entered, in fixed 15-minute windows across the day (exchange local time) — not hold time, see the Hold time tab for that. Only windows that actually contain a trade are shown. Buckets reflect whatever's currently selected via the filters above (all-time if no Date filter is set).

How to read this data

Best win rate is the time window with the highest win rate among windows with at least {{ min_trades_for_optimal }} trades. Best result is the window with the highest total {{ pnl_unit }} among the same eligible windows — often the same window as best win rate, but not always: a window can win often in small increments (high win rate, modest total) while another wins less often but bigger (lower win rate, bigger total). A window can earn both badges at once.

{% if best_winrate_bucket %}
Best win rate {{ best_winrate_bucket.label }}
Win rate {{ best_winrate_bucket.win_rate|floatformat:1 }}%
Trades {{ best_winrate_bucket.count }}
{% else %}
No time window has {{ min_trades_for_optimal }}+ trades yet, so no "best win rate" window is called out.
{% endif %}
{% if best_result_bucket %}
Best result {{ best_result_bucket.label }}
Total ({{ pnl_unit }}) {% if pnl_unit == "$" %}${{ best_result_bucket.total_pnl|floatformat:2 }}{% else %}{{ best_result_bucket.total_r|floatformat:2 }}R{% endif %}
Trades {{ best_result_bucket.count }}
{% else %}
No time window has {{ min_trades_for_optimal }}+ trades yet, so no "best result" window is called out.
{% endif %}
{% for row in timeofday_rows %} {% if pnl_unit == "$" %} {% else %} {% endif %} {% empty %} {% endfor %}
Time Trades Win rate Total ({{ pnl_unit }}) Avg ({{ pnl_unit }})
{{ row.label }} {% if row.is_best_winrate %}BEST WIN RATE{% endif %} {% if row.is_best_result %}BEST RESULT{% endif %} {% if not row.eligible %}(too few trades){% endif %} {{ row.count }} {% if row.win_rate is not None %}{{ row.win_rate|floatformat:1 }}%{% else %}—{% endif %}{% if row.total_pnl is not None %}${{ row.total_pnl|floatformat:2 }}{% else %}—{% endif %} {% if row.avg_pnl is not None %}${{ row.avg_pnl|floatformat:2 }}{% else %}—{% endif %}{% if row.total_r is not None %}{{ row.total_r|floatformat:2 }}R{% else %}—{% endif %} {% if row.avg_r is not None %}{{ row.avg_r|floatformat:2 }}R{% else %}—{% endif %}
No trades match the current filters.

Setups: best times

For each setup, its own best-win-rate time window and best-result time window (same {{ min_trades_for_optimal }}-trade minimum) — often the same window, not always. Setups with too few trades in every window show "Not enough data".

{% for row in setup_timeofday_rows %} {% if row.best_winrate %} {% else %} {% endif %} {% if row.best_result %} {% else %} {% endif %} {% empty %} {% endfor %}
Setup Trades Best win-rate time Win rate Best result time Total ({{ pnl_unit }})
{{ row.tag }} {{ row.total_trades }}{{ row.best_winrate.label }} ({{ row.best_winrate.count }}) {{ row.best_winrate.win_rate|floatformat:1 }}%Not enough data{{ row.best_result.label }} ({{ row.best_result.count }}) {% if pnl_unit == "$" %}${{ row.best_result.result|floatformat:2 }}{% else %}{{ row.best_result.result|floatformat:2 }}R{% endif %}Not enough data
No setup-tagged trades match the current filters.
{% elif view == "efficiency" %}

Max-move efficiency

⚠ Not financial advice. This report is statistical evidence from your own past trades only — not a guarantee or prediction of future results. Use it as one input alongside your own judgment, not a substitute for it.

"Capture ratio" is realized R divided by the trade's own best favorable move (in R) before exit — how much of the move that was actually on the table you kept. Trades with no favorable move at all (went against you from the first tick) aren't counted; there was nothing to capture. Trades with under $5 of computed risk are also excluded — same as the known-bad-stop cleanup elsewhere in this app, since a near-zero risk denominator blows the ratio up to a meaningless four/five-digit value. A handful of symbols also have cached daily-bar data wildly out of scale with the trade's actual entry price (likely reverse-split-unadjusted history from the data provider) — trades where that would produce a physically implausible max move (20x+ the position's own value) are excluded too.

A low capture ratio on a setup you otherwise trust is a signal to try holding longer or widening the target rather than partialing out early — the market was offering more than you took. A ratio near or above 100% means you're already capturing close to the full move; there's little room left to gain by holding longer there.

Coverage matters here. "Max move" only exists for a trade with cached intraday chart bars or an imported MFE value (TraderSync's own max-favorable-excursion column) — a trade with neither (a fetch that failed, or an older import outside the data provider's retention window) has no max move and is skipped by "Total available" and the capture ratio below. "Total realized" is unaffected — it's just pnl/risk, same as everywhere else in the app, so it always matches the period's real total. The coverage count shows how much of the period has a usable max move to actually measure capture ratio against.

{% if efficiency_summary and efficiency_summary.closed_trade_count %}
Total realized {{ efficiency_summary.total_realized_r|floatformat:1 }}R ({{ efficiency_summary.closed_trade_count }} trades, chart-independent — matches the Trades page)
Coverage {{ efficiency_summary.trade_count }} of {{ efficiency_summary.closed_trade_count }} closed trades{% if efficiency_summary.coverage_pct is not None %} ({{ efficiency_summary.coverage_pct|floatformat:0 }}%){% endif %}
Overall capture ratio {% if efficiency_summary.overall_capture_pct is not None %}{{ efficiency_summary.overall_capture_pct|floatformat:1 }}%{% else %}—{% endif %}
Total available {{ efficiency_summary.total_max_move_r|floatformat:1 }}R

(Realized R among just the {{ efficiency_summary.trade_count }} covered trades: {{ efficiency_summary.covered_realized_r|floatformat:1 }}R — this is what "Total available" and the capture ratio above are actually measured against, since max-move only exists for those trades. Avg per-trade capture: {% if efficiency_summary.avg_capture_pct is not None %}{{ efficiency_summary.avg_capture_pct|floatformat:1 }}%{% else %}—{% endif %} — this simple average is easily dominated by trades with a tiny favorable move and a big loss; the overall ratio above, which weights by size, is the more reliable headline number.)

{% else %}

No closed trades with recorded max-move data match the current filters.

{% endif %}

Distribution

{% for b in efficiency_distribution %} {% empty %} {% endfor %}
Capture ratioTrades% of trades
{{ b.label }} {{ b.count }} {{ b.pct_of_total|floatformat:1 }}%
No closed trades match the current filters.

By setup, worst capture first

Sorted so the setup with the most room to improve — the biggest gap between what the market offered and what got realized — shows up first.

{% for row in efficiency_rows %} {% empty %} {% endfor %}
Setup Trades Avg Max-Move R Avg Realized R Capture ratio
{{ row.tag }} {% if not row.eligible %}(too few trades){% endif %} {{ row.count }} {{ row.avg_max_move_r|floatformat:2 }}R {{ row.avg_realized_r|floatformat:2 }}R {% if row.capture_pct is not None %}{{ row.capture_pct|floatformat:1 }}%{% else %}—{% endif %}
No setup-tagged trades match the current filters.

Target simulator: what fixed target would match or beat today's actual profits?

For each candidate target below, every trade whose own best favorable move reached that far is assumed to have exited exactly there (a resting order would have filled); a trade that never reached it keeps its real result unchanged. This is the same trustworthy-move assumption the capture ratio above already relies on. It answers "if I'd set a single fixed target across the board, would I have done at least as well as I actually did?"

{% if target_sim_rows %}
Actual total {{ target_sim_baseline_r|floatformat:1 }}R
{% if target_sim_breakeven %}
Lowest target that matches or beats it {{ target_sim_breakeven.target|floatformat:2 }}R
{% else %}
Lowest target that matches or beats it none in this range
{% endif %} {% if target_sim_best %}
Best target in this range {{ target_sim_best.target|floatformat:2 }}R ({{ target_sim_best.total_r|floatformat:1 }}R total)
{% endif %}
{% endif %} {% for row in target_sim_rows %} {% empty %} {% endfor %}
TargetTrades hitting itSimulated total RChange vs actual
{{ row.target|floatformat:2 }}R {% if target_sim_breakeven and row.target == target_sim_breakeven.target %}BREAK-EVEN OR BETTER{% endif %} {% if target_sim_best and row.target == target_sim_best.target %}(best){% endif %} {{ row.hit_count }} {{ row.total_r|floatformat:1 }}R {% if row.delta_r > 0 %}+{% endif %}{{ row.delta_r|floatformat:1 }}R
No closed trades match the current filters.

Capture ratio by month

Always every month regardless of the Date filter above, so the trend stays visible. "Change" is versus the previous month shown. "Actual R" and "Trades" are every closed trade that month, matching the Trades page total — "Covered" is the chart-data subset the capture ratio is actually computed from; a low coverage count means the capture ratio that month is based on very little data.

{% for row in efficiency_monthly_rows %} {% empty %} {% endfor %}
MonthTradesActual RCoveredRealized R (covered)Available RCapture ratioChange
{{ row.label }} {{ row.closed_count }} {{ row.true_realized_r|floatformat:1 }}R {{ row.count }}{% if row.closed_count %} ({% widthratio row.count row.closed_count 100 %}%){% endif %} {{ row.total_realized_r|floatformat:1 }}R {{ row.total_max_move_r|floatformat:1 }}R {% if row.capture_pct is not None %}{{ row.capture_pct|floatformat:1 }}%{% else %}—{% endif %} {% if row.delta_vs_prev is not None %}{% if row.delta_vs_prev > 0 %}▲ +{% elif row.delta_vs_prev < 0 %}▼ {% endif %}{{ row.delta_vs_prev|floatformat:1 }}pp{% else %}—{% endif %}
No months with eligible trades yet.
{% elif view == "stops" %}

Stop sizing

⚠ Not financial advice. This report is statistical evidence from your own past trades only — not a guarantee or prediction of future results. Use it as one input alongside your own judgment, not a substitute for it.

"Pullback" is the worst a trade ever moved against you before it resolved, in R (1.0R is your current stop distance, by definition). If a setup's average pullback is well under 1.0R, price rarely tests the full stop — that's room to tighten it. This uses the same trustworthy-data guards as Max-move efficiency (near-zero risk, cached bar data wildly out of scale with the entry price), so a corrupted trade can't skew the numbers.

The simulator below assumes a stop at a given fraction of your current distance would have closed the trade the moment price reached that far — a real historical event, not a guess, since the pullback is measured up to the trade's actual outcome. Tightening always saves on trades that were going to lose anyway, but it can also cut off a real winner that dipped hard before recovering — "Winners cut short" shows exactly how many of those there'd have been at each fraction, so a good choice balances total R against that cost. A trade priced above $10 never gets tightened below 4¢/share, even at fractions where the rest of its setup does — tighter than that sits inside ordinary spread and noise the simulation can't see, so it wouldn't survive real execution even if it wins on paper. That trade just keeps its real result at those fractions instead, so one hard-to-tighten trade can't block a fraction for the rest of its setup.

By setup, tightest pullback first

Sorted by average pullback — the setups at the top have the most historical room to tighten their stop. "Suggested stop" is this setup's own best-performing fraction from the same simulator, run against just its trades.

{% for row in stop_setup_rows %} {% empty %} {% endfor %}
Setup Trades Avg pullback Median P90 Avg risk ($) Suggested stop Improvement
{{ row.tag }} {% if row.tight_candidate %}TIGHTEN?{% endif %} {% if not row.eligible %}(too few trades){% endif %} {{ row.count }} {{ row.avg_pullback_r|floatformat:2 }}R {% if row.median_pullback_r is not None %}{{ row.median_pullback_r|floatformat:2 }}R{% else %}—{% endif %} {% if row.p90_pullback_r is not None %}{{ row.p90_pullback_r|floatformat:2 }}R{% else %}—{% endif %} ${{ row.avg_risk|floatformat:0 }} {{ row.best_fraction|floatformat:1 }} × current {% if row.best_delta_r > 0 %}+{% endif %}{{ row.best_delta_r|floatformat:1 }}R
No setup-tagged trades with recorded pullback data match the current filters.

Stop-tightening simulator

Applies to whatever's currently filtered above (all setups unless you've picked one via the Setup filter).

{% if stop_sweep_rows %}
Actual total {{ stop_sweep_baseline_r|floatformat:1 }}R
{% if stop_sweep_best %}
Best fraction in this range {{ stop_sweep_best.fraction|floatformat:1 }} × current ({{ stop_sweep_best.total_r|floatformat:1 }}R total, {{ stop_sweep_best.winners_cut_short }} winner{{ stop_sweep_best.winners_cut_short|pluralize }} cut short)
{% endif %}
{% endif %} {% for row in stop_sweep_rows %} {% empty %} {% endfor %}
Stop fractionTrades affectedWinners cut shortSimulated total RChange vs actual
{{ row.fraction|floatformat:1 }} × current {% if stop_sweep_best and row.fraction == stop_sweep_best.fraction %}BEST{% endif %} {{ row.affected }} {{ row.winners_cut_short }} {{ row.total_r|floatformat:1 }}R {% if row.delta_r > 0 %}+{% endif %}{{ row.delta_r|floatformat:1 }}R
No closed trades match the current filters.
{% endif %} {% endblock %}