Tradability Analysis: Entry, Hold, Exit

Market Synthesis is validated not just on detection accuracy but on tradability β€” can a fund manager actually profit from these signals?

This page documents the complete trading algorithm derived from backtesting 303 signals (2014-2026) against real market data via yfinance, using an adversarial LLM-based exit system.

Key Finding: Adversarial Exit Beats Mechanical Rules

Metric Adversarial Exit Hold-to-time Delta
Mean P&L per trade +7.98% +4.86% +3.11%
Worst single trade -19.3% -57.3% +38.0%
Estimated annual return (10% alloc) ~22% p.a. ~14% p.a. +8% p.a.

The adversarial exit system uses a weekly LLM check: β€œis the narrative-reality gap still open?” It holds winners longer (gap still exploitable) and cuts losers earlier (gap closed or reversed), producing better returns with dramatically lower tail risk.

Per-Category Results (303 Signals Backtested)

Category N Exit P&L Hold P&L Alpha Exits Triggered
Commodity 45 +32.8% +21.8% +11.0% 0
Currency Peg 24 +16.8% +2.9% +13.9% 1
Sovereign Debt 111 +3.2% +2.0% +1.2% 11
Geopolitical 35 +3.2% +3.6% -0.3% 14
Central Bank 67 +0.9% +1.3% -0.4% 12
Real Estate 21 +0.6% -0.5% +1.1% 7

Commodity and currency peg are the strongest performers β€” classic Soros-style trades where structural supply/demand or reserve depletion creates clear mispricings.

Entry Timing Algorithm

Entry timing is category-specific. Fast-repricing events need immediate entry; slow-building events benefit from confirmation.

graph TD DETECT["πŸ” Event Detected
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:

Position Management Algorithm

graph TD ENTRY["πŸ“ ENTRY
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?”

graph TD ENTRY["πŸ“ Position Entered"] --> WEEK{"Weekly Check:
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:

  1. The original signal (thesis, category, direction, gap summary)
  2. Current news headlines from that week
  3. Current price data for the instrument

It then decides whether the gap is still exploitable. This produces two key behaviors:

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

Why adversarial exit beats mechanical rules

Mechanical exits (fixed hold period + stop-loss) fail in two ways:

  1. Exit too early on winners where the thesis is still playing out (commodity supply disruptions can last months)
  2. Exit too late on losers where the gap has already closed (central bank successfully defended)

The LLM exit check solves both: it understands whether the thesis is still valid, not just whether price moved.

Instrument Selection

The system uses a 3-layer instrument selector:

  1. Disk cache β€” previously validated ticker assignments (living map, updated as results come in)
  2. Static registry β€” category Γ— region β†’ default instruments (e.g., sovereign_debt + Europe β†’ EUFN, TLT)
  3. LLM fallback β€” Sonnet adjudicates the best tradeable expression for novel signals, with regression testing before cache update

Category instruments (top performers)

Category Primary Instruments Avg P&L
Commodity CL=F (WTI), BZ=F (Brent), NG=F (Nat Gas) +32.8%
Currency Peg USDRUB=X, USDTRY=X, USDBRL=X, USDCNY=X +16.8%
Sovereign Debt TLT, EMB, IGLT.L, DX-Y.NYB +3.2%
Geopolitical GC=F (Gold), EWZ, country ETFs +3.2%
Central Bank FX pairs, TLT, IEF +0.9%
Real Estate VNQ, GBPUSD=X, country ETFs +0.6%

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.