Docs Pricing Get live access

Guardrails

EdgeProof is designed so that the system is hard to break, not just hard to misconfigure. Guardrails are structural. They apply before any order reaches the broker.

Position sizing

Every position is sized by a dollar budget:

laneFraction = min(isLotto ? TRADING_LOTTO_SIZE_FRACTION : 1,
                    isStarter ? TRADING_STARTER_SIZE_FRACTION : 1)
laneBudget = positionSizeUsd × laneFraction
qty = floor(laneBudget / (marketPrice × 100))
qty = max(qty, 1)  // always at least 1 contract if budget allows
qty = min(qty, TRADING_MAX_CONTRACTS_PER_TRADE)  // only when set (off by default)

TRADING_POSITION_SIZE_USD defaults to $500. At $5.00 per contract ($500 notional value), that's 1 contract. At $2.50, that's 2 contracts.

Lotto and starter signals are not sized at the full rate. A lotto gets a quarter of the budget (TRADING_LOTTO_SIZE_FRACTION, default 0.25) and is skipped with a budget-cap error if even 1 contract doesn't fit that lane: a cheap lotto contract must never buy full-budget size. A starter gets a third of the budget (TRADING_STARTER_SIZE_FRACTION, default 0.33), but always gets at least 1 contract as long as the price fits the full (non-lane) budget, so a light entry priced out of its lane still gets filled at minimum size. Both lanes compose with source-weighted sizing below: the source multiplier scales the budget first, then the lane fraction applies. When a signal is both a lotto and a starter, the smaller lane governs sizing.

TRADING_MAX_CONTRACTS_PER_TRADE is off by default (0): the dollar budget is the sizing control, and a $1 contract is the same exposure as a $6 one. Set a positive value to add a liquidity ceiling: very cheap contracts can otherwise mean order sizes larger than the option's book comfortably absorbs.

Per-underlying cap

TRADING_MAX_CONTRACTS_PER_UNDERLYING (default: 2) limits how many contracts EdgeProof will hold across all strikes and expiries for one underlying ticker. If SPY already has 2 contracts, new SPY signals are skipped until a position closes.

Signal staleness

TRADING_MAX_SIGNAL_AGE_MINUTES (default: 60): entry signals older than this are discarded without attempting an order. Options pricing can move significantly in an hour; a stale entry is often worse than no entry. Channel exits use the same window differently: they degrade rather than drop; see "Channel-driven exits" below.

Price ceiling (hard cap)

TRADING_HARD_CAP_PCT (default: 10%): if the current market price is more than 10% above the signal's recommended entry, the order is skipped. This prevents chasing momentum on late signals.

Entry price requirement

Both the entry sanity ratio and the price ceiling above key off the signal's stated entry price: without one, they have nothing to anchor to and silently disable themselves. TRADING_REQUIRE_ENTRY_PRICE (default: true) skips any entry signal with no positive entry price before it reaches those checks, so a watchlist-style message ("keep an eye on SPY 500c") can't execute at market. Set to false to disable this guard.

Risk filters (you choose your appetite)

These are not the author's personal limits baked into the product; they are yours to set. edgeproof configure asks about the common ones; all are configurable via env. Defaults are conservative, so you opt in to riskier signal types rather than having them forced on you.

Variable Default Effect
TRADING_ALLOW_0DTE false When false, skip options expiring the same trading day
TRADING_ALLOW_LOTTOS false When false, skip lotto / long-shot signals (e.g. very low premium)
TRADING_ALLOW_LOW_CONVICTION false When false, skip signals the extractor marks low-conviction (and not a starter)
TRADING_MAX_OPEN_POSITIONS 10 Global cap on concurrent open positions across all underlyings. At the cap, new entries are skipped; add-ons to existing positions are still allowed
TRADING_ADD_ON_CAP 3 Maximum number of add-on entries allowed on a single existing position
TRADING_ENTRY_SANITY_RATIO 3 Reject an entry whose live price is more than this multiple above (or below 1/ratio) the signal's stated entry (guards against fat-finger prices and mismatched contracts)

Exit rules (per position)

Configured via POSITIONS_DEFAULT_* vars. Defaults:

Rule Default Meaning
Take-profit +75% Close when position value is 75% above entry
Stop-loss −50% Close when position value is 50% below entry
Trailing stop −40% from peak Close if price falls 40% from the highest mark
Trailing activation +30% Trailing stop only activates after a +30% gain
Max hold 150h Force-close after 150 hours regardless of P&L

These are defaults applied to every position. Individual signals can set tighter rules via the signal text (EdgeProof's LLM extraction respects explicit targets when present).

Learned exit calibration (opt-in)

TRADING_SOURCE_EXIT_CALIBRATION (default: false): once enabled, EdgeProof derives a tighter trailing-stop activation per source (how early the trail arms) from that source's own median realized winner exit, and tightens the trailing-stop width itself by a fixed fraction of the global default. It is tighten-only: the global trailing-stop percent and activation percent above are the ceiling, never the floor, and take-profit/stop-loss are never touched. Calibrated values never tighten below a floor of 10% or half your configured activation, whichever is higher. This bounds the self-reinforcing ratchet, since the median that drives calibration is itself measured from EdgeProof's own prior exits. Calibration shares its gate with source-weighted sizing: TRADING_SOURCE_WEIGHTING_MIN_TRIPS (default 10) closed round trips are required for a source before its trailing settings tighten at all, and a source with no realized winners yet is left at the plain global defaults. As a source's ledger history accumulates, the derived activation adapts with it. Like the DTE-bucketed exit defaults it composes with, calibration is computed once at position-discovery time and frozen for the life of the hold.

Kill switch

edgeproof halt    # pause entries, keep managing exits
edgeproof flatten # close everything, then halt

halt stops new trade entries within one monitor cycle. Open positions continue to be managed (exits still fire). No restart required to resume:

edgeproof resume

flatten places market orders to close all open positions, then halts. Use this before going offline for an extended period.

Trading hours

EdgeProof checks that the Alpaca clock says the market is open before placing entries. Exits may fire outside regular hours if the signal warrants it (e.g., pre-market stop-loss triggers via streaming). Use --bypass-trading-hours (supervisor dev mode only) to disable this check in testing.

Slippage control

TRADING_SLIPPAGE_PCT (default: 7%): limit orders are placed at midpoint × (1 + slippage) for buys. If the order doesn't fill, a retry at TRADING_RETRY_SLIPPAGE_PCT (default: 10%) is attempted after TRADING_RETRY_MIN_AGE_MINUTES.

Channel-driven exits

By default EdgeProof ignores what a channel says about closing. It opens positions from entry calls and closes them using your own rules: take profit, stop, trailing stop, time limit. That is the product's default position: the channel picks the trade, you decide when to leave it.

Setting TRADING_FOLLOW_CHANNEL_EXITS=true lets a channel also close or reduce positions. Some callers' real edge is their exit discipline, and this follows it. What it costs you is a share of the exit decision, so it is opt-in rather than on by default.

Resolution is account-level, not origin-level: a symbol-only exit call resolves against any long option position held on that underlying in your account, including one EdgeProof did not itself open. This is still strictly opt-in, still only ever reduces a position (never opens or adds), and still skips rather than guesses whenever more than one position on the underlying could match.

What it acts on

EdgeProof tracks each author's de-risk stage on a position (trimming, then runners, then flat) rather than reacting to each message in isolation:

Author's stage What EdgeProof does
Trimming ("taking a trim here", "half off") Sells the stated fraction, else TRADING_VAGUE_TRIM_FRACTION; tightens the trailing stop
Runners ("leave a few to run", "risk free now") Sells down to TRADING_RUNNER_CONTRACTS; floors the stop at breakeven while in profit
Flat ("all out", "stopped out") Closes the remainder

TRADING_VAGUE_TRIM_FRACTION=0 keeps sizeless TRIMS notify-only; a runners call still sells down to TRADING_RUNNER_CONTRACTS, and explicit closes still close. Set TRADING_FOLLOW_CHANNEL_EXITS=false if you want no author-driven selling at all.

Stages only move forward. A repeated or out-of-order message can never sell twice, and a missed message self-heals: the next one states where the author already is. The one exception is a repeat of a call whose sale was withheld (late or chased, below): that repeat is not a duplicate to ignore. It is the author trying again, and it retries the sale if the repost itself is fresh.

Most callers never state a size at the trimming stage: they write "taking a trim here", not "selling a third". A vague instruction has no number in it, so acting on it would mean executing a quantity the model invented, and EdgeProof never does that. Instead, TRADING_VAGUE_TRIM_FRACTION (e.g. 0.33) lets you decide the size once, in config: any sizeless trim sells that fraction of the position, and the alert notes the default was used. Left unset (0), sizeless trims execute nothing: you get a notification so you can act manually, and your rules stay in charge. A stated gain ("340c up 50%") is not a trim and EdgeProof does nothing with it.

Rules that still hold

Late or chased exits

Two situations withhold the sale on an otherwise-valid exit call while still recording the author's stage and tightening your stop, degrading gracefully instead of either blindly executing or silently dropping the message:

Either way the de-risk stage still moved forward, so EdgeProof remembers that a sale was withheld at that stage. The next stage the author posts (a trim followed later by "all out") is evaluated fresh against its own timestamp and price, and sells normally if that later call isn't itself late or chased. But a repost of the SAME stage (the author saying "all out" again) is not treated as a no-op duplicate: a withheld flat executes if the author re-posts while the signal is fresh; otherwise your own rules close the position. A repost that is itself late or chased is withheld again, the same as the first time, and stays eligible for the next repost. TRADING_MAX_SIGNAL_AGE_MINUTES and TRADING_EXIT_CHASE_PCT gate the retry exactly as they gate a first attempt.