Tradability Analysis: Entry, Hold, Exit
Market Synthesis is validated on detection accuracy. Whether a fund manager could profit from these signals is a second, much harder question, and this page now documents why the answer is not yet demonstrated.
This page describes the trading algorithm derived from backtesting 303 signals (2014-2026) against real market data via yfinance, using an adversarial LLM-based exit system. The algorithm is real and runs in paper trading. Its edge is not established.
Correction (2026-08-25): the exit system does not beat a random exit
This page previously led with “Key Finding: Adversarial Exit Beats Mechanical Rules” — mean P&L +7.98% against +4.86% for a fixed hold, and an estimated ~22% p.a. That comparison was not a control, and the claim is withdrawn.
Beating a fixed hold is the wrong test. A debate that exits early to cut a loss beats a fixed hold partly by having a stop, which says nothing about whether it picked a good day. The control that answers the actual question is:
given that a trade was exited on day D, is D better than a random day drawn from the same distribution of realised exit days?
That arm was run on 2026-08-25 over 116 trades, recomputed from the price cache:
| Exit policy | Median return | Win rate |
|---|---|---|
| Adversarial debate exit | +8.44% | 70.7% |
| Random exit, same day distribution | +9.67% | 78.4% |
Edge: −1.23pp. Only 2 of 5 categories with n≥8 beat their own random-exit control. Across the full available range of exit days, the debate captured 34% of the span between the best and worst achievable exit; a random exit captured 37%.
The debate’s exit timing is not adding information. The per-category “alpha vs hold” table that stood here is a comparison against the weaker alternative and has been removed rather than annotated.
Six other execution-layer claims have failed their own controls (base-rate and date-shift placebo tests), including a withdrawn 24.5% CAGR portfolio simulation, the commodity strangle (39.3% win rate against a 50.6% base rate), and the central-bank strangle (0 wins in 16). The detection results elsewhere on this site are unaffected — they are measured against labelled outcomes, not P&L.
What this does not change: detection precision holds ~0.75 out of sample across 12 years and five macro regimes with no look-ahead, and commodity detection reads 0.974 precision on 39 signals. The signal is real. Converting it into return is the open problem.
Entry Timing Algorithm
Entry timing is category-specific. Fast-repricing events need immediate entry; slow-building events benefit from confirmation.
Score ≥ 70"] --> ROUTE{"Category?"} ROUTE -->|"central_bank
currency_peg
geopolitical
real_estate"| IMM["⚡ IMMEDIATE ENTRY
Next market open"] ROUTE -->|"sovereign_debt"| MOM["📈 MOMENTUM CONFIRM
Wait for 2 consecutive
favorable-direction days"] ROUTE -->|"commodity"| WAIT["⏳ WAIT 3 DAYS
Let initial volatility
settle"] IMM --> SIZE["Position Size
Score 90+ → 100%
Score 80-89 → 75%
Score 70-79 → 50%"] MOM --> SIZE WAIT --> SIZE SIZE --> STOP["Set Stop-Loss
(category-specific)"] style DETECT fill:#1d4ed8,stroke:#153ca8,color:#ffffff style ROUTE fill:#f0f9ff,stroke:#0284c7,color:#0c4a6e style IMM fill:#dcfce7,stroke:#16a34a,color:#14532d style MOM fill:#fef3c7,stroke:#d97706,color:#78350f style WAIT fill:#fee2e2,stroke:#dc2626,color:#7f1d1d style SIZE fill:#e0e7ff,stroke:#4f46e5,color:#312e81 style STOP fill:#f8fafc,stroke:#94a3b8,color:#0f2b57
Category-specific entry timing — derived from backtest of 4 strategies on each category
Entry Rules by Category
| Category | Entry Trigger | Rationale | Backtested Win Rate |
|---|---|---|---|
| Sovereign Debt | 2 consecutive favorable days | Counter-move (disbelief rally) common in first 1-5 days; momentum confirmation filters false starts | 75% (vs 64% immediate) |
| Central Bank | Immediate (day 0) | Shock reprices markets in hours. Waiting 3 days = -3.7% avg (move already done) | 57% |
| Commodity | Wait 3 days | Extreme initial volatility (-10.7% DD). After 3 days, dust settles, DD drops to -5.3% | 40% |
| Geopolitical | Immediate (day 0) | Crisis escalates from detection. Small drawdown (-2.7%). Delay = worse | 50% |
| Real Estate | Immediate (day 0) | Slow grind; entry timing matters less than exit timing | 33% |
| Currency Peg | Immediate (day 0) | Peak at day 6. Any delay loses the entire move | 33% |
What Does NOT Help for Entry Timing
Tested and rejected:
- Volume spikes: Only 33% correlation with optimal entry. Volume peaks on day 1 (headline splash), not at the drawdown trough.
- VIX reversal: Marginal improvement for sovereign_debt only. Not worth the complexity.
- RSI / oversold signals: Insufficient data, unreliable on macro events.
Position Management Algorithm
Set initial stop-loss"] --> D5{"Day 5:
Profitable?"} D5 -->|"Yes"| TIGHT["Tighten stop
to breakeven"] D5 -->|"No"| HOLD1["Hold with
original stop"] TIGHT --> SIG1{"First exit
signal?"} HOLD1 --> SIG1 SIG1 -->|"Yes"| RED1["Reduce 30%
of position"] SIG1 -->|"No"| PEAK{"Peak day
reached?"} RED1 --> CLUST{"Signal
cluster?
(2+ signals
in 3 days)"} CLUST -->|"Yes"| RED2["Reduce another
30%"] CLUST -->|"No"| PEAK RED2 --> PEAK PEAK -->|"Yes"| CLOSE["CLOSE
remaining position"] PEAK -->|"No"| SIG1 style ENTRY fill:#1d4ed8,stroke:#153ca8,color:#ffffff style D5 fill:#fef3c7,stroke:#d97706,color:#78350f style TIGHT fill:#dcfce7,stroke:#16a34a,color:#14532d style HOLD1 fill:#f8fafc,stroke:#94a3b8,color:#0f2b57 style SIG1 fill:#fef3c7,stroke:#d97706,color:#78350f style RED1 fill:#fee2e2,stroke:#dc2626,color:#7f1d1d style CLUST fill:#fef3c7,stroke:#d97706,color:#78350f style RED2 fill:#fee2e2,stroke:#dc2626,color:#7f1d1d style PEAK fill:#e0e7ff,stroke:#4f46e5,color:#312e81 style CLOSE fill:#7f1d1d,stroke:#450a0a,color:#ffffff
Position management: time-based exit with headline-driven partial profit-taking
Category Parameters
| Category | Stop-Loss | Peak Day | Hold Period | Reward:Risk |
|---|---|---|---|---|
| Sovereign Debt | -6% | Day 25 | 3-4 weeks | 2.1:1 |
| Commodity | -7% | Day 29 | 3-4 weeks | 2.9:1 |
| Central Bank | -7% | Day 19 | 2-3 weeks | 1.7:1 |
| Geopolitical | -5% | Day 22 | 3 weeks | 2.5:1 |
| Real Estate | -8% | Day 18 | 2-3 weeks | 0.8:1 |
| Currency Peg | -6% | Day 6 | 1 week | 0.8:1 |
Adversarial Exit System
The exit system is an LLM-based weekly check that asks: “is the narrative-reality gap that justified this trade still open?”
Gap still open?"} WEEK -->|"gap_still_open"| HOLD["Hold Position
(check again next week)"] WEEK -->|"gap_closed / reversed"| EXIT["Exit Position"] HOLD --> WEEK STOP{"Stop-loss hit?
(per-category threshold)"} -->|"Yes"| FORCE["Force Exit"] ENTRY --> STOP style ENTRY fill:#1d4ed8,stroke:#153ca8,color:#ffffff style WEEK fill:#fef3c7,stroke:#d97706,color:#78350f style HOLD fill:#dcfce7,stroke:#16a34a,color:#14532d style EXIT fill:#fee2e2,stroke:#dc2626,color:#7f1d1d style STOP fill:#f8fafc,stroke:#94a3b8,color:#0f2b57 style FORCE fill:#7f1d1d,stroke:#450a0a,color:#ffffff
Adversarial exit: LLM weekly check replaces mechanical time-based exits
How it works
Each week, the exit detector receives:
- The original signal (thesis, category, direction, gap summary)
- Current news headlines from that week
- Current price data for the instrument
It then decides whether the gap is still exploitable. This produces two key behaviors:
- Holds winners longer — if a commodity supply disruption is still unresolved, the system keeps the position open past the mechanical hold period
- Cuts losers earlier — if a central bank successfully defends, the system exits before the stop-loss is hit
Exit verdict distribution (303 signals)
| Verdict | Count | Meaning |
|---|---|---|
| gap_still_open | 258 | Held to maturity — gap persisted |
| stop_loss | 45 | Exited early on stop-loss or gap closure |
The rationale for adversarial exit — and what testing it showed
The design argument was that mechanical exits (fixed hold + stop-loss) fail in two ways:
- Exit too early on winners where the thesis is still playing out (commodity supply disruptions can last months)
- Exit too late on losers where the gap has already closed (central bank successfully defended)
An LLM exit check should solve both, because it reasons about whether the thesis is still valid rather than only whether price moved.
Measured, it does not. Against a random exit drawn from the same distribution of realised exit days, the debate exit is 1.23pp worse on median return (see the correction at the top of this page). The reasoning is plausible and the mechanism does fire — 45 of 303 signals exited early — but the days it chooses are not better than chance. The argument above is why the system was built this way, not evidence that it works.
Instrument Selection
The system uses a 3-layer instrument selector:
- Disk cache — previously validated ticker assignments (living map, updated as results come in)
- Static registry — category × region → default instruments (e.g., sovereign_debt + Europe → EUFN, TLT)
- LLM fallback — Sonnet adjudicates the best tradeable expression for novel signals, with regression testing before cache update
Category instruments
| Category | Primary Instruments |
|---|---|
| Commodity | CL=F (WTI), BZ=F (Brent), NG=F (Nat Gas) |
| Currency Peg | USDRUB=X, USDTRY=X, USDBRL=X, USDCNY=X |
| Sovereign Debt | TLT, EMB, IGLT.L, DX-Y.NYB |
| Geopolitical | GC=F (Gold), EWZ, country ETFs |
| Central Bank | FX pairs, TLT, IEF |
| Real Estate | VNQ, GBPUSD=X, country ETFs |
The per-category “avg P&L” column that stood here (commodity +32.8%, currency peg +16.8%, and so on) is the same uncontrolled exit-vs-hold measurement corrected at the top of this page, so it has been removed rather than restated. Each of those figures is an average against no null; where a base rate has since been computed, the edge disappeared — the commodity strangle reads 39.3% win rate against a 50.6% base rate.
Implementation
The adversarial exit backtest is implemented in tests/backtest_adversarial_exit.py:
# Run the full backtest
python -u tests/backtest_adversarial_exit.py --workers 5 --label v10
# Key components:
# - Adversarial exit detector: detectors/adversarial_exit_detector.py
# - Instrument selector: shared/instrument_selector.py (3-layer with cache)
# - Signal store: shared/signal_store.py (635 signals, append-only)
The entry timing algorithm is implemented in shared/entry_timing.py:
from shared.entry_timing import get_entry_recommendation
rec = get_entry_recommendation(
category="sovereign_debt",
score=85,
direction="short",
pre_move_pct=0.0,
)
# Returns: EntryRecommendation(
# entry_type="momentum_confirm",
# momentum_days=2,
# stop_loss_pct=-6.0,
# position_size=0.75,
# hold_days=23,
# peak_day=25,
# )
All metrics derived from feed-driven backtest on real news data (2014-2026), validated against yfinance market prices. 303 signals backtested with adversarial LLM exit system. No hand-crafted test events.