52 KiB
Barrier functions
Audience: User
Answer a simple question: “If I pick a profit target and a stop, how often would a simulated path hit the target first?”
A barrier is just a price line that ends the story when touched — take-profit (bank a win) or stop-loss (cut a loss). mtdata walks many imaginary paths and counts the outcomes. Use this after you have a market picture and a sense of typical movement — not as a standalone “buy” signal.
Beginner walkthrough: SAMPLE-TRADE.md (one pair). Grid search lives in SAMPLE-TRADE-ADVANCED.md.
Dense terms: Barrier · Monte Carlo · GBM · Edge · Kelly · prob_tp_first · grid_style
Related: Glossary · Sample trade · CLI · Uncertainty · Volatility
Quick start
1) Probability for one TP/SL pair
Percent barriers are expressed in percent (for example, take_profit: 0.40 with unit: pct means 0.40%, not 40%). The same KV dialect is accepted by forecast_barrier_prob --barrier and labels_triple_barrier --barrier (commas or spaces between pairs, or JSON). ticks is the broker trade tick/point (0.1 pip on typical 5-digit FX), not conventional FX pips; use unit=pips for forex pip distances.
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon 12 --direction long --barrier 'kind=tp_sl,unit=pct,take_profit=0.40,stop_loss=0.60' --json
mtdata-cli labels_triple_barrier EURUSD --timeframe H1 --barrier 'kind=tp_sl,unit=pct,take_profit=0.40,stop_loss=0.60' --json
JSON is equivalent:
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon 12 --direction long --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.40,"stop_loss":0.60}' --json
Look for prob_tp_first, prob_sl_first, prob_no_hit, and
probability_edge in the output. The optimizer uses edge for its scored
objective; the single-pair probability response does not alias that field.
Defaults: --method mc_gbm_bb, --horizon 12, --direction long. The
default uses a Brownian-bridge correction so single-barrier touches between
simulated bar closes count. For a TP/SL pair, the bridge samples each barrier
independently; bridge_joint_first_passage: false marks the resulting
same-bar ordering as an approximation. Methods that evaluate only simulated
closes report intra_bar_hit_detection: simulated_bar_close and emit an
undercount warning. Close-only methods such as mc_gbm cannot observe dual
intrabar hits, so a non-default same_bar_policy is rejected with guidance to
use mc_gbm_bb. The default sl_first is recorded as
same_bar_policy_applied=false / same_bar_policy_reason=close_only_path.
Compact output keeps Wilson intervals, n_sims, history_bars_used, and a
concise history_window so simulation precision is visible. These intervals
measure finite Monte Carlo noise under the fitted path model; they do not
measure uncertainty about real-world market edge.
2) Search for “good” TP/SL levels
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --grid-style volatility --objective edge --json
What these numbers mean
You will see three probabilities on a take-profit / stop-loss query:
| Field | Plain idea |
|---|---|
prob_tp_first |
Share of paths that touched the win line first. |
prob_sl_first |
Share of paths that touched the loss line first. |
prob_no_hit |
Share of paths that hit neither before time ran out. |
With the default same_bar_policy=sl_first (or tp_first), prob_tp_first + prob_sl_first + prob_no_hit is about 1. Under neutral, same-bar ties sit in prob_same_bar / prob_unresolved instead of being assigned to TP or SL, so use prob_tp_first + prob_sl_first + prob_same_bar + prob_no_hit. A large prob_no_hit usually means the lines are far away or the horizon is short. These are simulation counts, not a broker guarantee.
Deeper detail (methods, grids, Kelly / EV objectives) starts below.
Core Concepts
What is a Barrier?
A barrier is a price level that, when touched, triggers an event:
- Take-Profit (TP): Profit-taking level
- Stop-Loss (SL): Risk-limiting level
The Barrier Problem
Given:
- Current price:
S₀ - TP level:
B_tp - SL level:
B_sl - Time horizon:
T(number of bars) - Trade direction: long/short
We want to compute:
P(TP first): Probability TP is hit before SLP(SL first): Probability SL is hit before TPP(no hit): Probability neither is hit by time TE[time to hit]: Expected time until resolution
Which tool models what
Four tools compute barrier outcomes, and they deliberately make different execution assumptions. Numbers are not interchangeable between them; pick the one that matches your question.
| Tool | Question it answers | Price path | Barrier touch test |
|---|---|---|---|
forecast_barrier_prob |
What are the odds for one TP/SL pair from here? | Simulated forward paths | Simulated bar closes, or a Brownian bridge for intra-bar hits |
forecast_barrier_optimize |
Which TP/SL pair looks best from here? | Simulated forward paths | Same as above, across a candidate grid |
labels_triple_barrier |
What would this rule have labeled historically? | Actual history | Raw intrabar high/low by default, or closes only |
strategy_validate |
What would trading this rule have returned historically? | Actual history | Raw intrabar high/low, non-overlapping trades |
The two forecast tools measure TP/SL distances from the executable entry
quote but score simulated paths against the executable exit quote (bid for
longs, ask for shorts), reported as simulation_reference_price. A barrier placed
inside the current spread is therefore already resolved before the first
simulated bar; both tools reject that geometry rather than reporting a
probability the path matrix cannot observe. Use --as-of, --start, or --end
for candle-close-anchored research with no spread embedded in the geometry.
The two historical tools anchor on the completed bar close and model no spread in
the barrier geometry, so their hit rates are systematically more optimistic than a
live-quoted forecast at the same distances. strategy_validate additionally
enforces one position at a time, which makes its trade count far lower than the
label count from labels_triple_barrier over the same history.
Both labels_triple_barrier and the forecast tools expose same_bar_policy for
bars that touch both barriers, but only when intrabar extremes are in play.
Close-only labeling cannot produce a both-touched bar, so the label output reports
labeling_spec.same_bar_policy_applied: false in that mode.
Available Methods & Algorithms
1. Monte Carlo GBM (mc_gbm)
Algorithm: Geometric Brownian Motion simulation
Mathematical Model:
Δ log S = mean(historical log-return) + σ Z
S_{t+1} = S_t * exp(Δ log S)
where:
- the drift is the mean log-return (no extra −½σ² Itô term; that term is already inside the historical mean)
σ= sample standard deviation of log-returnsZ= standard normal draw (antithetic pairs by default inforecast_barrier_prob)
How it works:
- Calibrate μ and σ from historical log-returns
- Generate N random paths using the formula above (with antithetic variance reduction by default for more stable estimates)
- Count paths that hit TP first vs SL first
Antithetic paths come in mirrored pairs, so they are not n_sims independent
draws and a plain p(1-p)/n interval would be wrong in both directions: too
narrow for prob_no_hit, too wide for the first-hit probabilities. Each reported
Wilson interval is therefore sized by that outcome's own design effect, and the
resulting effective counts are echoed under
probability_effective_sample_size with probability_ci_basis: "wilson_design_effect_adjusted". forecast_barrier_optimize draws independent
paths and reports wilson_independent_paths.
Strengths:
- Fast and stable
- Minimal assumptions
- Works with limited history
Weaknesses:
- Assumes constant volatility
- Ignores fat tails
- No regime awareness
When to use:
- Short horizons (< 20 bars)
- Stable market conditions
- Quick screening of many TP/SL combinations
- Baseline for comparison
Real-life example:
Scenario: EURUSD scalp on 5-minute chart, horizon = 12 bars (1 hour)
Why GBM: Short timeframe, relatively stable intraday volatility, need fast results for live trading
Command:
mtdata-cli forecast_barrier_prob EURUSD --timeframe M5 --horizon 12 --method mc_gbm --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.2,"stop_loss":0.15}'
2. Brownian Bridge GBM (mc_gbm_bb)
Algorithm: GBM with Brownian Bridge correction
Mathematical Model: The Brownian Bridge is a Brownian motion conditioned to return to a specific value at time T. For a single barrier, it improves short-horizon touch estimates by accounting for the path between endpoints. TP and SL bridge hits are sampled independently, so it does not provide an exact joint two-barrier first-passage ordering.
Key insight: If we know the log-price at the ends of a bar, the probability of hitting a log-barrier b = ln B between those closes is:
P(hit | X_t, X_{t+1}) = exp(-2 * (b - X_t)(b - X_{t+1}) / σ²)
where X = ln S and σ is the per-bar log-return standard deviation (Δt = 1 bar).
How it works:
- Generate GBM paths as normal
- Apply bridge correction between each time step
- Detect hits that occur "between" discrete simulation points
Strengths:
- Improves single-barrier touch detection for short horizons
- Captures single-barrier intra-bar touches
Weaknesses:
- Slightly slower than plain GBM
- Requires more history for stable σ
- Tight TP/SL can overstate same-bar ties because their bridge hits are independent
When to use:
- Very short horizons (< 10 bars)
- Tight TP/SL (near current price)
- Scalping strategies
- When precision matters more than speed
Real-life example:
Scenario: Crypto scalping on 1-minute BTC chart, TP = 0.1%, SL = 0.08%, horizon = 6 bars
Why Bridge: Tiny barriers, very short horizon - need to capture hits that occur between 1-minute marks
Command:
mtdata-cli forecast_barrier_prob BTCUSD --timeframe M1 --horizon 6 --method mc_gbm_bb --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.1,"stop_loss":0.08}'
3. HMM Monte Carlo (hmm_mc)
Algorithm: Hidden Markov Model with regime-switching
Mathematical Model:
State s_t ∈ {1, 2, ..., K} (hidden)
Return r_t ~ N(μ_{s_t}, σ_{s_t})
Transition: P(s_t = j | s_{t-1} = i) = A_{ij}
How it works:
- Fit a Gaussian HMM to log-returns (Baum-Welch)
- Use its transition matrix and filtered final state distribution
- Simulate state paths via Markov chain
- Sample returns conditional on current state
- Build price paths
If fitting cannot support the requested number of states, barrier probability
and optimization outputs expose sim_meta.requested_n_states and
sim_meta.fitted_n_states. Optimization also emits an HMM state-collapse
warning and reports effective_method: single_regime_gaussian_mc when every
simulation batch fits only one state.
Strengths:
- Captures volatility regimes (low/high vol)
- Adapts to changing market conditions
- More realistic than constant-volatility models
- Handles trending vs ranging markets
Weaknesses:
- Requires more history (200+ bars)
- Computationally intensive
- Needs careful initialization
When to use:
- Medium to long horizons (10-100 bars)
- Clearly regime-switching markets
- Swing/position trading
- When volatility clustering is evident
Real-life example:
Scenario: GBPUSD swing trade on 4H chart, horizon = 48 bars (8 days)
Why HMM: Markets show clear regimes - trending days vs consolidation periods
Command:
mtdata-cli forecast_barrier_prob GBPUSD --timeframe H4 --horizon 48 --method hmm_mc --barrier '{"kind":"tp_sl","unit":"pct","take_profit":1.5,"stop_loss":1.0}' --params "n_states=2 n_sims=5000"
4. GARCH Monte Carlo (garch)
Algorithm: Generalized Autoregressive Conditional Heteroskedasticity
Mathematical Model:
r_t = μ + ε_t
ε_t = σ_t * z_t, z_t ~ N(0,1)
σ_t² = ω + α * ε_{t-1}² + β * σ_{t-1}²
How it works:
- Fit GARCH(1,1) to scaled returns
- Forecast volatility path conditional on history
- Simulate returns using time-varying σ_t
- Build price paths
Strengths:
- Explicit volatility modeling
- Captures volatility clustering
- Widely studied and validated
- Good for risk forecasting
Weaknesses:
- Requires
archpackage - Needs substantial history (100+ bars)
- Assumes normal innovations
- Sensitive to parameter initialization
When to use:
- Volatility clustering is strong (high autocorrelation in squared returns)
- Risk management focus
- Options pricing or volatility trading
- When you need volatility forecasts, not just prices
Real-life example:
Scenario: SPY options hedge on daily chart, horizon = 20 days
Why GARCH: Volatility clustering is well-documented in equity indices; need accurate vol for options
Command:
mtdata-cli forecast_barrier_prob SPY --timeframe D1 --horizon 20 --method garch --barrier '{"kind":"tp_sl","unit":"pct","take_profit":3.0,"stop_loss":2.0}' --params "p=1 q=1"
5. Bootstrap (bootstrap)
Algorithm: Circular block bootstrap
How it works:
- Compute historical returns
- Sample contiguous blocks of returns (size =
block_size) - Concatenate blocks to create synthetic paths
- No parametric assumptions
Strengths:
- Non-parametric (no distribution assumptions)
- Preserves autocorrelation structure
- Captures fat tails and skew
- Works with any return distribution
Weaknesses:
- Limited to historical patterns
- Cannot extrapolate beyond observed volatility
- Requires long history for good coverage
When to use:
- Non-normal returns with complex patterns
- When you suspect model misspecification
- Validation of parametric models
- Markets with structural breaks
Real-life example:
Scenario: Emerging market FX (e.g., USDTRY), horizon = 24 bars
Why Bootstrap: Returns have fat tails, skew, and jumps that parametric models miss
Command:
mtdata-cli forecast_barrier_prob USDTRY --timeframe H1 --horizon 24 --method bootstrap --barrier '{"kind":"tp_sl","unit":"pct","take_profit":2.0,"stop_loss":1.5}' --params "block_size=5"
6. Heston Model (heston)
Algorithm: Stochastic volatility (Heston 1993)
Mathematical Model:
dS_t = μS_t dt + √v_t S_t dW_t^1
dv_t = κ(θ - v_t) dt + ξ√v_t dW_t^2
dW_t^1 dW_t^2 = ρ dt
Parameters:
κ: mean reversion speedθ: long-term varianceξ: volatility of volatilityρ: correlation between price and volv₀: initial variance
Strengths:
- Captures leverage effects (ρ)
- Stochastic volatility
- Closed-form option pricing available
- More realistic than GBM
Weaknesses:
- Complex parameter estimation
- Requires many assumptions
- Computationally expensive
- Hard to calibrate
When to use:
- Equity options trading
- When leverage effect is important
- Long-dated derivatives
- Academic/research contexts
Real-life example:
Scenario: AAPL options strategy, horizon = 60 days
Why Heston: Need stochastic vol + leverage effect for accurate option pricing
Command:
mtdata-cli forecast_barrier_prob AAPL --timeframe D1 --horizon 60 --method heston --barrier '{"kind":"tp_sl","unit":"pct","take_profit":10.0,"stop_loss":5.0}' --params "kappa=2.0 theta=0.04 xi=0.3 rho=-0.5" `kappa` / `theta` / `xi` / `v0` overrides use the **annual** QuantLib/literature convention (θ=0.04 ⇒ 20% annual vol) and are converted to the per-bar clock. Omit them to use per-bar auto-calibration. The payload `sim_meta.param_time_unit` reports which family was used.
7. Jump Diffusion (jump_diffusion)
Algorithm: Merton jump-diffusion
Mathematical Model:
dS_t = (μ - λk)S_t dt + σS_t dW_t + S_t dJ_t
J_t: compound Poisson process with log-normal jumps
Parameters:
jump_lambda: jump intensity (frequency)jump_mu: mean jump sizejump_sigma: jump size volatilityjump_threshold: outlier threshold for calibration
Strengths:
- Captures sudden price jumps
- Models tail events
- Better for crisis periods
- Accounts for news events
Weaknesses:
- Difficult to calibrate reliably
- Many parameters
- Overfits easily
- Requires long history
When to use:
- Earnings announcements
- Major news events
- Cryptocurrency markets
- Crisis periods
Real-life example:
Scenario: Trading around FOMC announcement, horizon = 4 hours
Why Jump Diffusion: Expecting discrete price jumps from news, not continuous movement
Command:
mtdata-cli forecast_barrier_prob EURUSD --timeframe M15 --horizon 16 --method jump_diffusion --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.5,"stop_loss":0.3}' --params "jump_lambda=0.5 jump_mu=0.001 jump_sigma=0.002"
8. Automatic Selection (auto)
Algorithm: Heuristic method selector based on data characteristics
Decision Tree:
Note: `garch` requires the `arch` package; auto falls back to `heston` if it is not available.
1. Insufficient history (< 10 bars or < 30 returns)?
→ mc_gbm_bb if horizon ≤ 12, else mc_gbm
2. Crypto or short timeframe (≤ 15 min)?
→ If heavy tails (kurtosis > 2, jump_ratio > 4): jump_diffusion
→ Otherwise continue
3. Heavy tails (kurtosis > 3.5, jump_ratio > 5)?
→ jump_diffusion
4. Volatility regime change (vol_ratio > 1.6 or < 0.65)?
→ hmm_mc (if enough history)
5. Strong volatility clustering (vol_corr > 0.3, r2 size ≥ 150)?
→ garch (if available) or heston
6. Moderate volatility clustering (vol_corr > 0.15)?
→ heston
7. Non-normal but no clear patterns (skew > 0.7, not jumpy)?
→ bootstrap
8. Short horizon and mild tails?
→ mc_gbm_bb
9. Default:
→ mc_gbm
Strengths:
- No need to manually select method
- Adapts to data characteristics
- Prevents obvious mistakes
Weaknesses:
- Black box (but reports reason)
- May not be optimal for all cases
- Still needs sufficient history
When to use:
- Initial exploration
- Production automation
- When unsure about market characteristics
- Multi-asset screening
Real-life example:
Scenario: Screening 20 currency pairs for trading opportunities
Why Auto: Don't want to manually analyze each pair's characteristics
Command:
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon 12 --method auto --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.5,"stop_loss":0.3}' # Returns: method_used: "hmm_mc", auto_reason: "auto: regime shift (volatility change)"
Optimization: Finding Optimal TP/SL
Grid Search (forecast_barrier_optimize)
Searches combinations of TP/SL to maximize an objective function.
With method=ensemble, every member simulator evaluates the same TP/SL grid.
The optimizer aggregates each candidate's metrics by ensemble_agg and optional
ensemble_weights, then selects the best aggregate candidate. Member metrics
for that exact TP/SL pair are returned under best.member_metrics; the ensemble
does not combine statistics from different member-specific optima.
Search Profiles: Control the compute budget with --search-profile:
| Profile | n_sims | n_trials | Grid density | Refine |
|---|---|---|---|---|
fast |
1,200 | 24 | 4 × 4 | off |
medium (default) |
4,000 | 63 | 7 × 9 | off |
long |
10,000 | 600 | 41 × 51 | on |
Use --search-profile fast for the fast profile. Explicit params values such
as n_sims and tp_steps override the profile values. Parameter names are
strict and method-aware: unknown keys fail before history is fetched. The
documented sims alias remains accepted. Pass command options such as
return_grid at the top level (--return-grid false), not inside params.
Grid Styles:
1. Fixed Grid (grid_style=fixed)
TP: [tp_min, tp_max] with tp_steps points
SL: [sl_min, sl_max] with sl_steps points
Total combinations: tp_steps × sl_steps
Use case: General purpose, known reasonable ranges
Example:
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --params "tp_min=0.25 tp_max=1.5 tp_steps=7 sl_min=0.25 sl_max=2.5 sl_steps=9"
2. Volatility Grid (grid_style=volatility)
Scales TP/SL based on recent volatility
Algorithm:
vol_per_bar = std(returns[-vol_window:])
vol_horizon = vol_per_bar * sqrt(horizon)
vol_pct = vol_horizon * 100
tp_start = max(vol_floor_pct, vol_pct * vol_min_mult)
tp_end = max(tp_start * 1.1, vol_pct * vol_max_mult)
sl_start = max(vol_floor_pct, vol_pct * vol_min_mult * 0.8)
sl_end = sl_start * vol_sl_multiplier
Use case: Adapts to current market volatility
Use vol_sl_multiplier to control the volatility-grid stop-loss multiplier.
Example:
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --grid-style volatility --params "vol_window=250 vol_min_mult=0.5 vol_max_mult=4.0 vol_sl_multiplier=1.8"
3. Ratio Grid (grid_style=ratio)
Fixes risk/reward ratio
Algorithm:
For each SL in [sl_min, sl_max]:
For each ratio in [ratio_min, ratio_max]:
TP = SL * ratio
Use case: When you want specific risk/reward profiles
Example:
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --grid-style ratio --params "ratio_min=1.5 ratio_max=3.0 ratio_steps=5 sl_min=0.3 sl_max=1.0 sl_steps=5"
4. Preset Grid (grid_style=preset)
Pre-configured ranges for trading styles
Presets:
scalp: TP 0.08-0.60%, SL 0.20-1.20%intraday: TP 0.25-1.50%, SL 0.25-2.50%swing: TP 0.60-3.50%, SL 0.50-4.50%position: TP 1.00-8.00%, SL 0.75-6.00%
Use case: Quick setup for standard trading styles
Note:
--presetimplies--grid-style presetwhen--grid-styleis omitted. Explicit--grid-style fixed|volatility|ratiostill rejects--preset.- Presets are stored in percentage terms.
- In
mode=ticks, the optimizer converts those preset percentages to tick-size distances using the current reference price. - That means named presets in tick mode are not portable across different price levels.
Example:
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --grid-style preset --preset swing
Objectives
Choose what to optimize. Each objective answers a different trading question:
Quick Reference Table
| Objective | Formula | Best For |
|---|---|---|
edge |
P(win) - P(loss) |
General purpose, consistent win rate |
prob_tp_first |
P(win) |
Maximize win rate only |
prob_resolve |
1 - P(no hit) |
Ensure trades complete |
kelly |
P(tp_first) - P(sl_first)/net_RR |
Unconditional Kelly edge proxy |
kelly_cond |
Kelly on resolved trades only | Position sizing (ignoring timeouts) |
ev |
P(tp_first)*net_TP - P(sl_first)*net_SL + P(no_hit)*timeout_MTM |
Maximize full-path profit per trade |
ev_cond |
EV on resolved trades only | Profit per trade (ignoring timeouts) |
ev_per_bar |
EV / mean_time_in_trade_all_paths |
Fast trades, capital turnover |
profit_factor |
Sum of winning realized payoffs / sum of losing realized payoffs, over resolved paths only | Risk/reward ratio focus |
min_loss_prob |
Minimize P(loss) |
Capital preservation |
utility |
mean(log1p(path_payoff * unit_to_return)); unit_to_return is 0.01 for pct, pip_size / entry for pips, and tick_size / entry for ticks |
Risk-averse trading |
Detailed Descriptions
edge — "How often do I win vs lose?"
- Measures raw probability advantage
- Edge = 0.20 means you win 20% more often than you lose
- Use when: You want consistent winners, regardless of payoff size
- Limitation: Ignores how much you win/lose
ev (Expected Value) — "What's my average profit per trade?"
- Accounts for both probability AND payoff size
- Includes the horizon mark-to-market payoff for paths that hit neither barrier
- Same-bar ties contribute zero gross payoff when
same_bar_policy=neutral; configured round-trip costs still apply - When spread, slippage, or commission inputs are supplied, EV is net of those costs
- EV = 0.15% means you expect to gain 0.15% per trade on average
- Use when: Payoff asymmetry matters (e.g., small wins, big losses)
- Limitation: Favorable timeout drift can dominate the barrier-hit edge; inspect
ev_unresolved,prob_no_hit, andphantom_profit_risk
ev_per_bar — "What's my profit per unit of time?"
- Normalizes EV by how long trades take
- Uses unconditional time-in-trade, so unresolved paths count at full horizon
- Favors fast trades over slow ones with same total EV
- Use when: Capital turnover matters (reinvesting profits)
- Limitation: May favor trades with higher transaction costs
kelly — "How strong is the barrier edge for sizing?"
- An unconditional two-barrier Kelly edge proxy; timeout probability shrinks the value
- Uses net reward/risk when trading costs are supplied
- Do not use the raw value directly as a lot or capital fraction
kelly_condis the standard two-outcome Kelly fraction after conditioning on TP/SL resolution; use fractional Kelly and an independent risk cap for sizing- Use when: Comparing barrier candidates with the same timeout semantics
- Limitation: Neither variant models horizon mark-to-market outcomes; default
evdoes, so Kelly and EV use different timeout payoff assumptions
prob_resolve — "Will my trade actually close?"
- Probability that TP or SL is hit within the horizon
- High value means trades complete; low value means many expire
- Use when: You need trades to close (e.g., day trading)
- Limitation: High resolve doesn't mean profitable
profit_factor — "What's my gains-to-losses ratio?"
- Profit factor > 1 means profitable; > 2 is very good
- Summed from each simulated path's realized payoff rather than from
P(win) * net_TP / P(loss) * net_SL, so it reflects gap-aware stop fills and thesame_bar_policyyou chose instead of assuming every stop fills exactly at the barrier - Uses net wins/net losses when trading costs are supplied
- Paths that hit neither barrier are excluded from both the numerator and the denominator
- Returns
nullwith aprofit_factor_notewhen a candidate produced no simulated losses, since the ratio is undefined - Because it ignores timeouts, this objective (like
min_loss_prob) applies a defaultmin_prob_resolvefloor of 0.20 so that a candidate resolving 2% of the time cannot win on a lossless handful of paths. Pass--params "min_prob_resolve=0"to opt out; the value in effect is reported atconstraints_applied.min_prob_resolvealongsideconstraints_applied.min_prob_resolve_source. - Use when: Comparing strategies by risk/reward
- Limitation: Doesn't account for trade frequency
min_loss_prob — "How do I avoid losses?"
- Minimizes probability of stop-loss being hit
- Use when: Capital preservation is priority
- Limitation: May result in tiny TP or trades that never resolve
utility — "What's my risk-adjusted outcome?"
- Logarithmic utility penalizes large losses more than it rewards equivalent gains
- Naturally avoids bets that could wipe you out
- Use when: Risk-averse trading, avoiding ruin
- Limitation: More theoretical than practical
Notes:
_condvariants (e.g.,ev_cond,kelly_cond) calculate metrics only on trades that resolved (hit TP or SL), ignoring timeouts, and remain cost-adjusted when trading costs are supplied.ev_per_baruses unconditional time-in-trade (t_hit_resolve_mean_all), counting unresolved paths at horizon expiry.t_hit_tp_medianandt_hit_sl_medianare conditional medians for paths that hit that barrier;t_hit_tp_median_condandt_hit_sl_median_condare aliases.edge_vs_breakevencompares the win rate conditional on TP/SL resolution with the matching two-outcome break-even rate. Full-path EV is kept separate because it includes timeout mark-to-market.
Execution-aware stop gaps
Set gap_aware_stops=true in params to value an SL at the first simulated
crossing price when that crossing is worse than the requested stop. TP remains
valued at the target, matching a resting limit-order premise. The result adds
realized_loss_mean and gap_aware_stops. This is particularly useful with
jump_diffusion, bootstrap, and other models capable of discrete gap moves.
Two-Stage Refinement
Problem: Coarse grid may miss optimal values
Solution:
- Run coarse grid search
- Zoom around best result
- Run fine grid in zoomed region
Parameters:
refine=true: Enable refinementrefine_radius=0.3: Search within ±30% of bestrefine_steps=5: Points per dimension in refinement
Example:
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --params "tp_min=0.25 tp_max=1.5 tp_steps=5 sl_min=0.25 sl_max=2.5 sl_steps=5 refine=true refine_radius=0.35 refine_steps=7"
Constraints
Filter candidates before ranking:
| Constraint | Description | Example |
|---|---|---|
min_prob_win |
Minimum tie-adjusted TP-first probability | 0.5 (50%) |
max_prob_no_hit |
Maximum no-hit probability | 0.2 (20%) |
min_prob_resolve |
Minimum probability that TP or SL is hit | 0.3 (30%) |
min_barrier_pct / min_barrier_pips / min_barrier_ticks |
Minimum TP and SL distance, in the unit matching mode |
2.0 |
min_barrier_multiplier |
Minimum TP and SL distance as a multiple of round-trip cost | 2.0 (default) |
max_median_time |
Maximum median bars to resolution, counting resolved paths only | 10 |
The minimum barrier distance is max(min_barrier_<unit>, min_barrier_multiplier × round-trip cost), where round-trip cost is the modeled total of spread,
slippage, and commission. Pass the min_barrier_* key that matches your mode:
min_barrier_pips with mode=pct is rejected rather than ignored, because a
floor in the wrong unit cannot be compared to the barrier grid. The resolved
values are echoed under constraints_applied.
max_median_time is evaluated against the median over resolved paths only, so a
candidate that resolves rarely but quickly can satisfy it. Pair it with
min_prob_resolve or max_prob_no_hit when you need trades to actually close.
Example:
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --params "tp_min=0.5 tp_max=2.0 tp_steps=5 sl_min=0.5 sl_max=2.0 sl_steps=5 min_prob_win=0.55 max_prob_no_hit=0.15 max_median_time=8"
Statistical Robustness
forecast_barrier_optimize can attach extra diagnostics for Monte Carlo quality. This is useful when you want to check whether the selected TP/SL pair is stable enough to trust for research or discretionary trading review.
Enable it with statistical_robustness=true in params or with the Python keyword arguments.
What it currently adds:
- Minimum simulation guidance based on the requested CI width
- Convergence diagnostics for the Monte Carlo success estimate
- Cross-seed stability checks for the selected barrier pair
- Optional bootstrap uncertainty for
prob_win,prob_loss,ev,edge,kelly, and related metrics - Optional power analysis
- Optional local sensitivity analysis around the selected
tpandsl - Drift-stress scenarios (enabled with statistical robustness by default)
- Independent-seed post-selection evaluation and full-search selection stability
- Optional held-out walk-forward validation with
enable_oos_validation=true
Every optimizer run also reports the selected candidate's EV standard error and
a Bonferroni-adjusted simulation-precision interval over the searched candidate
set. This is a convergence diagnostic only: changing n_sims can tighten the
interval, but cannot create evidence of market edge, so it does not control the
trade gate. Economic viability, costs, unresolved-path behavior, and explicit
validation diagnostics remain separate decisions.
Important: These checks do not make a trade valid by themselves. They are quality controls on the simulation and on the optimizer output.
Selection bias and the trade gate
The metrics on best are the maximum over every candidate the search evaluated,
so they are optimistically biased: some of the winning value is Monte Carlo luck
that happened to favor that TP/SL pair. Two diagnostics measure that bias, and
both can now close the trade gate:
post_selection_evaluationre-scores the selected pair on seeds the search never saw. Itsoptimismfield is the in-sample value minus the independent estimate. If the independent EV is negative,trade_gate_passedbecomesfalsewith the flagpost_selection_ev_negative, because the reported positive EV came from selection rather than edge.walk_forward_oos(enable withenable_oos_validation=true) re-runs the search on truncated history and measures what the chosen pair actually did next. Negativemean_realized_evsets the flagwalk_forward_ev_negativeand also closes the gate.
cross_seed_stability reports two distinct things, which are easy to conflate:
metricsholds the seed-to-seed spread of the selected pair, held fixed, so it measures estimator noise.metric_basisrecords this.selection_stabilityrecords how often each seed's own search picked the same pair. A lowselected_pair_frequencymeans the grid has many near-indistinguishable candidates, not that the metrics are noisy.
For signed metrics such as ev, edge, and kelly, a coefficient of variation
is meaningless when values straddle zero. Those metrics report cv: null and are
listed under sign_unstable_metrics with a reason: the sign is not reproducible.
That is a statement about missing edge, not about needing a larger n_sims.
Python Example
from mtdata.forecast.barriers_optimization import forecast_barrier_optimize
result = forecast_barrier_optimize(
symbol="EURUSD",
timeframe="H1",
horizon=12,
method="hmm_mc",
mode="pct",
grid_style="volatility",
objective="ev",
statistical_robustness=True,
target_ci_width=0.05,
n_seeds_stability=3,
enable_bootstrap=True,
n_bootstrap=500,
enable_power_analysis=True,
enable_sensitivity_analysis=True,
sensitivity_params=["tp", "sl"],
params={"n_sims": 5000, "seed": 42},
)
CLI Example
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --grid-style volatility --objective ev --params "statistical_robustness=true,target_ci_width=0.05,n_seeds_stability=3,enable_bootstrap=true,n_bootstrap=500,enable_power_analysis=true,n_sims=5000,seed=42" --json
Use Python when you want to pass sensitivity_params explicitly.
Parameters
| Parameter | Default | Meaning |
|---|---|---|
statistical_robustness |
False |
Enables the extra diagnostics block |
target_ci_width |
0.05 |
Requested width used for minimum-simulation guidance |
n_seeds_stability |
3 |
Number of seed re-runs requested for the selected barrier pair |
enable_convergence_check |
True |
Adds a non-overlapping batch-means precision diagnostic |
enable_bootstrap |
False |
Adds bootstrap_uncertainty |
n_bootstrap |
200 |
Number of bootstrap resamples |
enable_power_analysis |
False |
Adds power_analysis |
power_effect_size |
0.05 |
Effect size assumed in the power calculation |
enable_sensitivity_analysis |
False |
Adds local tp/sl sensitivity checks |
sensitivity_params |
["tp", "sl"] |
Parameters varied in the sensitivity pass |
enable_drift_stress |
True with robustness |
Stress the selected pair over scaled historical drift |
drift_stress_multipliers |
[0, 0.5, 1, 1.5] |
Drift scenarios to evaluate |
enable_oos_validation |
False |
Enable held-out walk-forward selection and realized validation |
oos_folds |
5 |
Number of walk-forward folds |
oos_n_sims |
1000 |
Maximum simulation paths per fold |
oos_holdout_bars |
250 |
Trailing history reserved for folds |
Output Shape
When enabled, the result can include:
{
"min_sims_recommended": 1537,
"statistical_robustness": {
"minimum_simulations": {
"recommended": 1537,
"used": 5000,
"target_ci_width": 0.05,
"confidence": 0.95
},
"convergence_diagnostic": {
"converged": true,
"current_estimate": 0.55,
"window_mean": 0.548,
"window_std": 0.008,
"max_change": 0.006
},
"cross_seed_stability": {
"stable": true,
"n_seeds": 3,
"seeds_attempted": 3,
"seeds_succeeded": 3,
"metrics": {
"prob_win": {
"mean": 0.55,
"std": 0.02,
"cv": 0.036
}
}
},
"bootstrap_uncertainty": {
"prob_win": {
"mean": 0.55,
"std": 0.018,
"ci_low": 0.51,
"ci_high": 0.59
}
},
"power_analysis": {
"power": 0.92,
"min_n_for_80_power": 3200
},
"sensitivity_analysis": {
"tp": {
"success": true,
"values_tested": 5
}
}
}
}
Only the sections you enable are returned. cross_seed_stability may also report an error if fewer than two seed re-runs succeed.
Practical Use
- For a quick research pass, start with
target_ci_width=0.10,n_seeds_stability=2, and leave bootstrap off. - For a fuller review, use
target_ci_width=0.05,n_seeds_stability=3, and turn on bootstrap. - Treat a wide batch-means CI, unstable cross-seed candidate selection, or poor held-out EV as a reason to increase
n_sims, simplify the setup, or reject it. - Sensitivity analysis is most useful after you already have a candidate worth inspecting; it is not a replacement for the main grid search.
Walk-forward validation supports fixed, ratio, and preset grids. Volatility grids are rejected in this mode so holdout volatility cannot leak into candidate generation. Each fold calibrates only on bars available before that fold, selects from the original request grid, and scores the following realized close path. This remains a close-only research validation; it is not an execution backtest with bid/ask or intrabar OHLC ordering.
Real-Life Trading Scenarios
Scenario 1: Scalping EURUSD
Context: 5-minute chart, quick trades, tight spreads
Requirements:
- Fast execution
- High win rate
- Short holding time
Method Selection:
# Use auto or mc_gbm_bb for precision
mtdata-cli forecast_barrier_optimize EURUSD --timeframe M5 --horizon 6 --method mc_gbm_bb --mode pct --grid-style preset --preset scalp --objective kelly_cond --params "refine=true refine_radius=0.25"
Interpretation:
- Look for
kelly_cond > 0(positive edge after costs) prob_resolve > 0.7(most trades close out)t_hit_resolve_median < 4(fast resolution)
Scenario 2: Swing Trading Gold
Context: 4H chart, multi-day holds, volatility spikes
Requirements:
- Handle regime changes
- Account for volatility clustering
- Wider stops
Method Selection:
# HMM captures regimes, volatility grid adapts to current vol
mtdata-cli forecast_barrier_optimize XAUUSD --timeframe H4 --horizon 60 --method hmm_mc --mode pct --grid-style volatility --params "vol_window=300 vol_min_mult=0.8 vol_max_mult=3.0 min_prob_win=0.5" --objective edge
Interpretation:
- Look for
edge > 0.1(10% advantage) - Check
prob_no_hit(avoid if too high) - Verify
time_to_tp_seconds.medianaligns with swing timeframe
Scenario 3: Post-Earnings Options
Context: AAPL daily chart, jump risk from earnings
Requirements:
- Model jumps
- Short horizon (1-5 days)
- High volatility expected
Method Selection:
# Jump diffusion captures earnings moves
mtdata-cli forecast_barrier_optimize AAPL --timeframe D1 --horizon 5 --method jump_diffusion --mode pct --params "jump_lambda=0.3 tp_min=3.0 tp_max=10.0 tp_steps=5 sl_min=2.0 sl_max=6.0 sl_steps=5" --objective ev
Interpretation:
- Focus on
ev(expected value) - Check
prob_same_bar(should be low) - Verify jump parameters via
model_summary
Scenario 4: Range-Bound Market
Context: EURUSD in tight range, mean reversion
Requirements:
- High probability of no hit
- Small barriers
- Quick resolution if hit
Method Selection:
# Bootstrap preserves range characteristics
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method bootstrap --mode pct --params "tp_min=0.2 tp_max=0.6 tp_steps=5 sl_min=0.2 sl_max=0.6 sl_steps=5 max_prob_no_hit=0.3" --objective prob_resolve
Interpretation:
- High
prob_resolve(trades close out) - Low
prob_no_hit - Balanced
prob_winandprob_loss
Scenario 5: Multi-Asset Screening
Context: Finding best setups across 20 pairs
Requirements:
- Automated selection
- Consistent comparison
- Fast processing
Method Selection:
# Auto method adapts to each pair
for pair in EURUSD GBPUSD USDJPY AUDUSD NZDUSD USDCAD USDCHF; do
mtdata-cli forecast_barrier_optimize $pair --timeframe H1 --horizon 12 --method auto --mode pct --grid-style volatility --objective edge --top-k 1
done
Interpretation:
- Rank pairs by
edgefrombestoutput - Check
auto_reasonto understand method choice - Filter by
prob_resolve > 0.6
Output Interpretation
TP/SL Probability Query
{
"success": true,
"method": "hmm_mc",
"prob_tp_first": 0.62,
"prob_sl_first": 0.28,
"prob_no_hit": 0.10,
"probability_edge": 0.34,
"time_to_tp_bars": {"mean": 6.2, "median": 5.0},
"time_to_sl_bars": {"mean": 4.8, "median": 4.0}
}
Interpretation:
prob_tp_first = 0.62: 62% chance TP hits firstprob_sl_first = 0.28: 28% chance SL hits firstprob_no_hit = 0.10: 10% chance neither hitsprobability_edge = 0.34: 34 percentage-point probability bias (TP prob - SL prob)- Decision: A positive value favors TP-first outcomes, but is not expected value and does not account for payoff asymmetry or trading costs
Optimization Output
{
"best": {
"tp": 0.85,
"sl": 0.62,
"rr": 1.37,
"prob_win": 0.58,
"prob_loss": 0.32,
"prob_no_hit": 0.10,
"prob_resolve": 0.90,
"edge": 0.26,
"kelly": 0.34,
"kelly_cond": 0.47,
"ev": 0.22,
"ev_cond": 0.32,
"ev_per_bar": 0.03,
"profit_factor": 2.45,
"utility": 0.18,
"t_hit_resolve_mean": 6.8,
"t_hit_resolve_median": 6.0
},
"auto_reason": "auto: regime shift (volatility change)"
}
Interpretation:
- TP/SL: 0.85% / 0.62% (RR = 1.37)
- Win prob: 58% (conditional on resolution: 58/(58+32) = 64%)
- Edge: 26% raw (TP prob − SL prob)
- Resolve prob: 90% of paths hit TP/SL within the horizon
- Kelly: 34% (raw), 47% conditional on resolution
- EV: 0.22% per trade; EV per bar: 0.03% based on unconditional time in trade
- Profit factor: 2.45 (wins/losses ratio)
- Utility: 0.18 (risk‑averse log utility)
- Resolve time: mean 6.8 bars, median 6 bars
- Method used: HMM (detected regime shift)
Decision: This is a good setup - positive edge, reasonable RR, fast resolution.
Common Pitfalls & Solutions
Pitfall 1: Insufficient History
Problem: Method fails or gives unreliable results
Solution:
# Check history length first
mtdata-cli data_fetch_candles EURUSD --timeframe H1 --limit 500
# If insufficient, use mc_gbm (or mc_gbm_bb for short horizons)
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon 10 --method mc_gbm --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.2,"stop_loss":0.15}'
Pitfall 2: Overfitting to Recent Volatility
Problem: Optimizer picks TP/SL that won't generalize
Solution:
# Use longer vol_window for stable estimates
--params "vol_window=500"
# Or bootstrap (non-parametric)
--method bootstrap
# Validate with rolling backtest
Pitfall 3: Ignoring No-Hit Probability
Problem: High edge but many trades never close
Solution:
# Add constraint
--params "max_prob_no_hit=0.2"
# Or optimize for resolve probability
--objective prob_resolve
Pitfall 4: Wrong Method for Market Regime
Problem: Using GBM in highly regime-switching market
Solution:
# Use auto to select appropriate method
--method auto
# Or manually check for regimes
mtdata-cli regime_detect EURUSD --timeframe H1 --method hmm --params "n_states=3"
Pitfall 5: Not Accounting for Slippage/Spread
Problem: Paper trading results don't match reality
Solution:
- On a live request, the optimizer automatically applies a valid current bid/ask spread when no spread parameter is supplied.
- Supply commission and slippage assumptions explicitly, including
0when that is intentional. Historical requests also require an explicit spread assumption. - Top-level
--commission-bps-per-sideand--slippage-bpsvalues are per fill side and are normalized to a two-sided round trip before optimization. Low-levelparamscost fields are already normalized totals. - The
trading_costs.completeandmissing_assumptionsfields describe the effective model. An incomplete model can support research output, but cannot pass the trade gate. spread_pct,spread_bps, andspread_pipsare equivalent representations of the effective normalized spread; non-FX instruments report pips as null. The same rule applies to commission and slippage fields. Original user values remain undertrading_costs.explicit_inputs, while live bid/ask fallback is identified byspread_source=live_bid_ask.- When live entry and exit quote sides differ, the spread is already embedded
by re-anchoring simulated paths to the executable exit quote. It remains in
cost_per_tradefor reporting but is excluded frompayoff_deduction, withspread_embedded_in_path_geometry=true, so it is not charged twice. tp_ticks/sl_ticksaretrade_tick_sizedistances only (broker tick/point, not FX pips). Useunit=pipsfor conventional forex pips. They do not subtract spread.
# Tick distances (no automatic spread adjustment)
mtdata-cli forecast_barrier_prob EURUSD --timeframe M5 --horizon 12 --method hmm_mc --barrier '{"kind":"tp_sl","unit":"ticks","take_profit":20,"stop_loss":15}'
# Complete explicit cost model (pip/bps inputs on forecast_barrier_optimize)
mtdata-cli forecast_barrier_optimize EURUSD --timeframe M5 --horizon 12 --params "spread_pips=1.2,slippage_pips=0.3,commission_bps=0"
Advanced Tips
1. Multi-Horizon Analysis
Run multiple horizons to understand time dynamics:
for H in 6 12 24 48; do
echo "Horizon $H bars:"
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon $H --method hmm_mc --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.5,"stop_loss":0.3}' --json | jq '{horizon: .horizon, probability_edge: .probability_edge, prob_resolve: (.prob_tp_first + .prob_sl_first)}'
done
2. Regime-Conditional Analysis
Detect regimes first, then optimize per regime:
# Detect current regime
mtdata-cli regime_detect EURUSD --timeframe H1 --method hmm --params "n_states=2"
# If high-vol regime, use wider barriers
# If low-vol regime, use tighter barriers
3. Denoising for Cleaner Signals
# Apply denoising before simulation
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --grid-style preset --preset swing --denoise lowpass_fft --denoise-params "cutoff_ratio=0.1,causality=zero_phase"
4. Parameter Sensitivity
Test stability of results:
# Vary n_sims
for N in 1000 2000 5000 10000; do
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon 12 --method hmm_mc --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.5,"stop_loss":0.3}' --params "n_sims=$N" --json | jq '.prob_tp_first'
done
5. Cross-Validation
Compare methods on same data:
for METHOD in mc_gbm hmm_mc bootstrap; do
echo "Method: $METHOD"
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon 12 --method $METHOD --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.5,"stop_loss":0.3}' --json | jq '{method: .method, probability_edge: .probability_edge, prob_resolve: (.prob_tp_first + .prob_sl_first)}'
done
Performance Considerations
Speed vs Accuracy Trade-off
| Method | Speed | Accuracy | History Needed |
|---|---|---|---|
| mc_gbm | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | Minimal |
| mc_gbm_bb | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Minimal |
| hmm_mc | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 200+ bars |
| garch | ⭐⭐ | ⭐⭐⭐⭐ | 100+ bars |
| bootstrap | ⭐⭐⭐ | ⭐⭐⭐⭐ | 400+ bars |
| heston | ⭐ | ⭐⭐⭐⭐ | 200+ bars |
| jump_diffusion | ⭐ | ⭐⭐⭐⭐ | 300+ bars |
Optimization Speed
- Fixed grid:
O(S × T × S)where S=sl_steps, T=tp_steps - Volatility grid: Similar, but adaptive ranges
- Refinement: Adds ~2× computation
- n_sims: Linear scaling (2× sims = 2× time)
Tips for speed:
- Start with
mc_gbmfor screening - Use
detail="compact"to limit grid output - Set
top_kto limit evaluations - Reduce
n_simsfor initial runs (1000), increase for final (5000+)
Summary Decision Tree
Start with: --method auto --horizon [your_horizon]
If horizon < 10:
→ Consider mc_gbm_bb for precision
If horizon > 50:
→ Consider hmm_mc or garch for regimes
If crypto/jumpy:
→ Consider jump_diffusion
If non-normal/fat tails:
→ Consider bootstrap
If optimizing:
→ Use grid-style volatility for adaptive ranges
→ Use refine=true for precision
→ Set constraints (min_prob_win, max_prob_no_hit)
Validate:
→ Check prob_no_hit < 0.2
→ Check probability_edge > 0 for probability responses, or best.edge > 0 for optimizer responses
→ Check median time fits your style
→ Run cross-validation across methods
Quick Reference
Most Common Commands
Quick check:
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon 12 --method auto --barrier '{"kind":"tp_sl","unit":"pct","take_profit":0.5,"stop_loss":0.3}'
Find optimal:
mtdata-cli forecast_barrier_optimize EURUSD --timeframe H1 --horizon 12 --method hmm_mc --mode pct --grid-style volatility --params "refine=true" --objective edge
Closed-form check:
mtdata-cli forecast_barrier_prob EURUSD --timeframe H1 --horizon 12 --method closed_form --direction long --barrier '{"kind":"single_price","level":1.1000}'
For a current request, barrier distance starts at the executable entry side
(ask for a long, bid for a short). Simulated exit-price paths start at the
opposite executable side (bid for a long, ask for a short), so the current
spread is present before a TP or SL can be reached. The response discloses both
anchors. The closed-form single-barrier calculation remains entry-side anchored.
last_price_close retains the model-data anchor. Requests with
--as-of, --start, or --end remain candle-close-anchored historical
research and are labeled analysis_mode=historical_research.
Further Reading
- FORECAST.md - General forecasting documentation
- SAMPLE-TRADE.md - Example trading workflows
- TROUBLESHOOTING.md - Troubleshooting
Last updated: 2026-01-01