config.example.yaml
Rama main · réplica de lectura del repositorio original.
# Copy to config.yaml and adjust. Behavior/config only — NO secrets (those live in .env).
# paper | live (live also requires i_understand_live_trading: true)
mode: paper
i_understand_live_trading: false
# Which execution broker to use: paper | robinhood_mcp | robinstocks | alpaca
broker: robinhood_mcp # this edition trades via Robinhood
# If the chosen broker can't trade options, fall back to this one (or null to stay read-only).
broker_fallback: paper
# Market data provider: robinhood | alpaca | paper (see README > Optional integrations)
market_data: robinhood # default: pull all data from the SAME Robinhood login (no Alpaca needed)
# Simulated buying power the paper broker reports — set to your REAL account size so the
# paper soak sizes positions realistically (default 100000 is a large sandbox balance).
paper_buying_power: 100000
# Seed fake demo positions into the paper broker. Leave true for local dev; set FALSE on a real
# paper soak — the demo fixtures otherwise inject phantom collateral that blocks entry sizing
# and show up as fake positions/P&L on the dashboard.
paper_seed_positions: true
# Loop cadences (seconds)
poll_interval_seconds: 60 # monitor loop during market hours
poll_interval_closed_seconds: 300 # monitor loop when market closed
reconcile_interval_seconds: 300
approval_timeout_seconds: 900 # how long an approval request stays valid
max_quote_age_seconds: 10 # reject quotes older than this when executing
auto_trip_after_errors: 0 # auto-engage kill switch after N consecutive errors (0=off)
execution:
limit_buffer_pct: 0.02 # buy-to-close limit = mid * (1 + buffer)
slippage_cap_pct: 0.05 # never pay more than ask * (1 + cap)
fill_timeout_seconds: 60
reprice_after_seconds: 20
robinhood:
# Brokerage account that automated closes route through. MUST be agentic_allowed=true
# and option_level_2+ (check via the get_accounts MCP tool). Required for live RH MCP
# trading — leave blank and the RH broker stays read-only and falls back.
account_number: "" # your Agentic account number
# CSP auto-entry scanner (the "opening" side). OFF by default. Auto-enters sell-to-open
# cash-secured puts when a watchlist contract meets criteria — behind the same live-arming
# gate + kill switch as closes, plus the sizing caps and a real-time-feed requirement below.
entry:
enabled: false # master switch; scanner does not run unless true
watchlist: [] # tickers to scan, e.g. ["MSFT","NVDA","AMD"]
feed: indicative # ONLY used when market_data=alpaca (set 'opra' then). Ignored for robinhood.
scan_interval_seconds: 300
earnings_gate: true # skip a name whose earnings fall within (dte_max + exclude_earnings_days)
# days — never hold a short put through earnings. Enforced only when an
# earnings source is live (RH MCP + ROBINHOOD_MCP_TOKEN); else no-op.
criteria: # CSP (cash-secured put) screening
delta_min: 0.10 # short-put |delta| band. Measured on real option prints (docs/backtests.md):
delta_max: 0.20 # 0.10-0.20 beat 0.18-0.28 under both fair and worst fills; assignments 12% vs 19%.
dte_min: 7 # weeklies (7-14 DTE) for weekly income
dte_max: 14
min_annualized_yield: 0.20 # (premium/strike)/dte*365 floor (0.20 = 20%)
min_open_interest: 100
min_volume: 10
max_spread_pct: 0.10 # bid-ask spread as fraction of mid
exclude_earnings_days: 7 # (not yet enforced — needs an earnings calendar feed)
# Variance-risk-premium gate: only sell a put whose IV is at least this multiple of the name's
# 20-day realized vol. Pre-registered backtest on real prints: +1.90%/trade vs +1.20% ungated,
# assignments 13% -> 10%, ~1/3 fewer entries; the trades it removes are the loss tail. Set null to disable.
min_iv_rv_ratio: 1.3
cc_criteria: # covered-call screening (strike floor = cost basis, applied at runtime)
delta_min: 0.20
delta_max: 0.30
dte_min: 7
dte_max: 14
min_annualized_yield: 0.20
min_open_interest: 100
min_volume: 10
max_spread_pct: 0.10
exclude_earnings_days: 7
# Assignment clock (opt-in). Normally calls are never sold below cost basis. Set a number of
# days and shares that are still under water that long after assignment may be written
# against BELOW basis, inside cc_otm_band above spot, so the capital turns over instead of
# sitting (measured on volatile names: ~35 days per cycle vs ~100 with the basis floor).
# Works per ticker too (per_ticker: {SMR: {cc_below_basis_after_days: 42}}).
cc_below_basis_after_days: null
cc_otm_band: [0.05, 0.10]
sizing:
max_position_size_pct: 0.10 # cap each CSP at 10% of account value
max_concurrent_positions: 5 # max open CSPs
max_concurrent_cc: 10 # max open covered calls (covered, lower risk)
total_bp_utilization_target: 0.50 # never commit more than 50% of buying power total
buying_power_reserve_pct: 0.10 # always keep 10% of buying power unused
# Per-ticker criteria overrides — merged over criteria/cc_criteria. Tune names you know well:
per_ticker: {} # e.g. {"NVDA": {"delta_max": 0.22}, "KO": {"dte_max": 21}}
# Capital-aware tiers (optional). Names you'd like to trade once the account can afford ONE
# contract under the per-name cap (max_pct_per_underlying, else max_position_size_pct).
# min_collateral = strike * 100 snapshot; live prices refresh it daily. The bot only PROPOSES:
# the Tuning tab lists them under "Ready to add" and a tap adds the name with its overrides.
watchlist_tiers: {} # e.g. {"KO": {min_collateral: 7000, per_ticker: {min_annualized_yield: 0.20}}}
# --- Market-regime read (systemic vs idiosyncratic) ---
macro:
enabled: true
symbols: ["SPY", "QQQ"] # index-ETF proxies (VIX-proper isn't reachable via Alpaca yet)
risk_off_vol: 0.30 # SPY 20d realized vol >= this -> risk_off (VIX~30 proxy)
risk_off_drawdown: 0.10 # SPY 20d drawdown >= 10% -> risk_off
systemic_drawdown: 0.05 # market 20d dd >= 5% -> a stock's drop reads "systemic"
hard_gate: false # true = pause ALL new entries while risk_off (default off: flags only)
lookback_days: 330 # calendar days of index bars (>= 300 so the 200-day average exists)
skip_confirmed_downtrend: false # opt-in: pause NEW puts once SPY has closed below its 200-day
downtrend_confirm_days: 5 # ...for this many straight sessions (flags only when off)
# --- AI trade-analyst (advisory) ---
# Reviews each screened + sized CSP candidate before submission and records a structured verdict.
# It can only flag/skip, NEVER widen risk. Needs ANTHROPIC_API_KEY (.env) + the `ai` extra.
ai:
enabled: false # turn on once ANTHROPIC_API_KEY is set
model: claude-opus-4-8
mode: advisory # advisory = annotate only; veto = skip the trade on a "skip" verdict
max_candidates_per_scan: 3 # cap Claude calls per scan (cost control)
effort: medium # low | medium | high | xhigh | max
# --- Roll defense (tested short puts) ---
# When a put is tested near expiry, roll it OUT (and down a strike) for a NET CREDIT instead of
# realizing a loss or accepting a bad-basis assignment. Only rolls for a credit; else it leaves the
# position to ride to assignment. Off by default.
roll:
enabled: false
roll_dte: 3 # only consider rolling within this many days of expiry
roll_delta: 0.45 # ...and only when the put is tested (|delta| >= this)
target_dte_min: 7 # roll out to a new expiry in this window
target_dte_max: 14
min_net_credit: 0.0 # require net credit >= this (0 = never pay to roll)
max_target_delta: 0.35 # don't roll into an even more-tested put
# --- Gains sweep / tax reserve ---
# Each week, a share of NET realized gains (losses carry forward) is moved into a symbol of your
# choice by a small market order inside regular hours: SGOV (T-bills) to set aside taxes, or an
# index fund to reinvest premium. The holding is walled off from trading capital, never written
# against, never sold by the bot. One ledger row per week; run with dry_run: true for a cycle first.
tax_reserve:
enabled: false
pct: 0.20 # share of the week's net realized gains
symbol: SGOV # any Robinhood-tradable stock or ETF
weekday: 4 # 0=Mon .. 4=Fri
hour: 15 # ET, inside regular hours (share market orders only fill then)
minute: 40
min_order_dollars: 5 # smaller amounts roll into the next week
dry_run: true # log + ledger the intended order, place nothing
# --- Scheduled push reports (hands-off heartbeat) ---
# For when you can't watch during the day: a daily digest (so 'no news' means 'all fine', not
# 'it died') + a weekly performance rollup. Delivered via notify.provider (Pushover). Times ET.
reporting:
enabled: true
daily_digest_hour: 16 # 24h ET; 16:15 = just after the close
daily_digest_minute: 15
weekly_report_weekday: 4 # 0=Mon .. 6=Sun; 4 = Friday
notify:
provider: pushover # none | ntfy | pushover (set PUSHOVER_TOKEN/PUSHOVER_USER in .env)
ntfy_topic: ""
tunnel:
provider: none # none | cloudflared | ngrok
hostname: "" # public host for one-tap approval links (e.g. agentic.example.com)
web:
enabled: true # run the FastAPI control/webhook API
host: 0.0.0.0
port: 8000
signal_ttl_seconds: 600 # how long a queued TradingView signal stays actionable
# Rules evaluated per position each cycle. requires_approval is the hybrid-autonomy flag:
# false -> auto-execute (use only for safe, deterministic rules)
# true -> push a notification and wait for one-tap approval
# Loss circuit breaker: freezes NEW entries when realized losses pile up (never force-closes;
# the monitor keeps managing open positions). Keys on realized P&L, not unrealized marks.
risk:
loss_breaker_enabled: true
lookback_days: 7
max_realized_loss_pct: 0.10 # halt new entries if 7d realized P&L <= -10% of account (0 = off)
max_consecutive_losses: 4 # halt new entries after 4 straight realized losers (0 = off)
rules:
- name: profit-target
rule_type: PROFIT_TARGET
enabled: true
requires_approval: false
params:
profit_pct: 0.5 # fixed: close once 50% of the credit is captured
# Trailing mode: let winners run past the arm level, then close on a pullback from the peak.
# trailing: true # arm at profit_pct, then exit when profit falls trail_gap from its peak
# trail_gap: 0.20 # give-back from the high-water mark (0.20 = 20 points of captured %)
- name: stop-loss # cap losers / avoid deep assignment (either trigger, whichever first)
rule_type: STOP_LOSS
enabled: true
requires_approval: false
params:
loss_mult: 2.0 # close when cost-to-close >= 2x credit (~ -100% of credit)
delta_stop: 0.5 # close when |delta| >= 0.50 (strike being breached)
- name: dte-close
rule_type: DTE
enabled: true
requires_approval: false
params:
dte_threshold: 2 # weeklies: close/let-expire near expiry (was 21 for monthlies)
action: close # alert | close
- name: tv-signal
rule_type: SIGNAL
enabled: true
requires_approval: true
params:
match: underlying # underlying | occ