Discovery finds premarket squeezes far too late. Across 53 trades by a
professional small-cap momentum trader, our scanner had found only 4 of his
names before his entry, and 27 of his 28 entries were premarket (07:00-09:30
ET). KITT on 2026-09-25 ran $3.50 -> $8.39 at 09:00-09:01 ET (prior close
$0.49); the loop first "Discovered KITT via manual" at 09:35. The existing
sources (Alpaca movers / most-actives screeners) only reflect the regular
session, so a premarket squeeze is invisible until the open.
research/ingestion/snapshot_scan.py sweeps every active, tradable, listed US
equity (~11.9k names; OTC, warrants/units/rights and DISCOVER_EXCLUDE dropped;
asset list cached per ET day) through GET /v2/stocks/snapshots in 2,000-symbol
batches. The endpoint is bound by URL length rather than symbol count (8,000
worked; ~12.7k returned 414). A name qualifies when:
- the LATEST TRADE price is inside WATCHER_PRICE_MIN/MAX (never the prior
close, which is routinely < $1 on these names),
- % change vs the prior close >= DISCOVER_SNAPSHOT_MIN_PCT (20),
- it is trading now: last trade <= 180s old, latest minute bar <= 300s old
with >= 5,000 shares, and >= 100,000 shares since 04:00 ET (summed from
minute bars for the short list, one extra request).
Qualifiers are ranked by % change, capped per sweep, and announced once per
day. dailyBar OHLC ignores extended-hours prints (KITT's dailyBar high was
$3.24 against a premarket high of $8.39), so in premarket it can still be
yesterday's bar. The prior close is prevDailyBar.c only when dailyBar is dated
today, otherwise dailyBar.c.
run_live_paper.py runs it on its own daemon thread (04:00-16:00 ET weekdays,
every 45s), using the plan-019 pattern: its own DB connection, the thread-safe
Watchlist and the generic interval driver. Each new name emits
symbol_discovered with source="snapshot_scan" and price/prev_close/pct/minute
and cumulative volume in symbol_data, so first-sighting latency can be
measured. It gets a daily-history backfill and joins the watchlist, so the
normal new-symbol ingest path gives it its first full-session pull. This is
discovery only; no trading gate changes.
Gated by DISCOVER_SNAPSHOT_SCAN_ENABLED (default 1); knobs in .env.example.
Live read-only sweep at 13:10 ET: 11,871 names in 8 requests / 6.3s cold
(7 requests / 3.5s warm); top hits KITT +376%, MSGY +262%, INLF +60%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
255 lines
17 KiB
Bash
255 lines
17 KiB
Bash
# ============================================================
|
|
# Momentum — environment configuration (copy to .env and fill)
|
|
# Regenerated 2026-07-09 from the live config. Values kept here
|
|
# are the current tuned defaults; <REDACTED> = secret, fill from
|
|
# your protected store. See deploy/docker/README.md for which
|
|
# layer (compose vs .env) each group belongs in.
|
|
# ============================================================
|
|
|
|
TOKEN_ENCRYPTION_KEY=<REDACTED>
|
|
|
|
DASHBOARD_PORT=8765
|
|
# Dashboard POST auth + operator command bus (the :8765 kill switch).
|
|
# DASHBOARD_TOKEN is REQUIRED-when-commands-on: /api/command returns 503 unless
|
|
# it is set (commands never run unauthenticated); it also gates approve/reject/
|
|
# exit POSTs. Empty = read-only dashboard, no command bus. Keep it out of logs.
|
|
DASHBOARD_TOKEN=
|
|
# Enable the operator command bus (flatten-all / flatten-symbol / pause / resume).
|
|
# Needs DASHBOARD_TOKEN set too; the loop consumes commands on its 5s guard.
|
|
COMMAND_BUS_ENABLED=0
|
|
#curl.exe -H "APCA-API-KEY-ID: $key" -H "APCA-API-SECRET-KEY: $secret" https://paper-api.alpaca.markets/v2/account
|
|
# Alpaca API (primary data provider)
|
|
ALPACA_API_KEY=<REDACTED>
|
|
ALPACA_SECRET_KEY=<REDACTED>
|
|
ALPACA_BASE_URL=https://paper-api.alpaca.markets/v2
|
|
|
|
# Polygon (optional fallback)
|
|
POLYGON_API_KEY=<REDACTED>
|
|
|
|
# Additional news and filing sources
|
|
FINNHUB_API_KEY=<REDACTED>
|
|
FINNHUB_EMAIL=fez5587@gmail.com
|
|
SEC_API_KEY=<REDACTED>
|
|
DATABENTO_API_KEY=<REDACTED>
|
|
|
|
# Data directory for parquet files and DuckDB
|
|
DATA_DIR=./data
|
|
# Boot warns (log + one Telegram note) if data/learned_params.json is older than
|
|
# this many days — a stale file means the nightly tuner stopped running. Never blocks boot.
|
|
LEARNED_PARAMS_MAX_AGE_DAYS=3
|
|
|
|
DISCORD_WEBHOOK_URL=<REDACTED>
|
|
DISCORD_TRADING_WEBHOOK_URL=<REDACTED>
|
|
DISCORD_DEVELOPER_WEBHOOK_URL=<REDACTED>
|
|
|
|
# Telegram notifier (bot @traitor_momentum_bot) — gated; no-op if unset
|
|
TELEGRAM_BOT_TOKEN=<REDACTED>
|
|
TELEGRAM_CHAT_ID='2082795871'
|
|
# Dead-man's switch (qabot container): the loop's own notifier dies WITH the
|
|
# loop, so the always-up qabot watches max(events.timestamp) from outside.
|
|
DEADMAN_STALE_MIN=5 # alert when no events for this many minutes during market hours
|
|
DEADMAN_DAILY_PING=1 # nightly OK-ping after 20:30 ET — proves the watchdog itself is alive
|
|
|
|
# Nightly pg_dump (db-backup compose service, 20:10 ET Mon-Fri) — the only
|
|
# automated backup of the production Postgres. See deploy/docker/backup_db.sh.
|
|
BACKUP_DIR=/data/backups # inside the db-backup container (app-data volume)
|
|
BACKUP_KEEP_DAYS=14 # prune dumps older than this many days
|
|
|
|
#Market Data these are free plans so work within days/months capacity.
|
|
Alpha_Vantage_Api_Key=<REDACTED>
|
|
Twelve_Data_Api_Key=<REDACTED>
|
|
Marketstack_Api_Key=<REDACTED>
|
|
Financial_Modeling_Prep_Api_Key=<REDACTED>
|
|
EOD_Historical_Data_Api_Key=<REDACTED>
|
|
|
|
#News Data these are free plans so work within days/months capacity.
|
|
NewsAPI_Api_Key=<REDACTED>
|
|
NewsData.io_Api_Key=<REDACTED>
|
|
Marketaux_Api_Key=<REDACTED>
|
|
MediaStack_Api_Key=<REDACTED>
|
|
|
|
#ngrok key
|
|
ngrok_key=<REDACTED>
|
|
|
|
WATCHER_ENABLED=1
|
|
# WATCHER_MIN_BARS / WATCHER_READY_SCORE are left to the nightly tuner
|
|
# (data/learned_params.json). Uncomment to pin a manual override.
|
|
# WATCHER_MIN_BARS=5
|
|
# --- faster loop cadence: react sooner once a setup forms (helps at the margin;
|
|
# the real lateness is the setup design — see learn_params.py) ---
|
|
LIVE_BARS_INTERVAL_SECONDS=20
|
|
# Live ingest only re-pulls the last few minutes (closed bars never change); the
|
|
# first pass still covers the whole session so the opening range is present.
|
|
# This is what fixed the ~46s ingest that froze the single-threaded loop.
|
|
LIVE_BARS_REFRESH_MINUTES=20
|
|
WATCHER_INTERVAL_SECONDS=15
|
|
DISCOVER_INTERVAL_SECONDS=120
|
|
# --- whole-market premarket squeeze scan (research/ingestion/snapshot_scan.py) ---
|
|
# The movers/most-actives screeners only see the regular session, so premarket
|
|
# squeezes were invisible until 09:30 (KITT 2026-09-25 ran at 09:00, found 09:35).
|
|
# This sweeps every listed, tradable US equity through Alpaca snapshots
|
|
# 04:00-16:00 ET on its own thread and adds qualifying names to the watchlist
|
|
# (symbol_discovered source=snapshot_scan). Discovery only; all trading gates apply.
|
|
# Price band = WATCHER_PRICE_MIN/MAX on the LATEST trade (not the prior close).
|
|
DISCOVER_SNAPSHOT_SCAN_ENABLED=1
|
|
DISCOVER_SNAPSHOT_INTERVAL_SECONDS=45 # one full sweep (~7 requests)
|
|
DISCOVER_SNAPSHOT_MIN_PCT=20 # % change vs prior close to qualify
|
|
DISCOVER_SNAPSHOT_MAX_PER_SWEEP=5 # new names injected per sweep (top by %)
|
|
DISCOVER_SNAPSHOT_MIN_BAR_VOLUME=5000 # shares in the latest minute bar
|
|
DISCOVER_SNAPSHOT_MIN_CUM_VOLUME=100000 # shares traded today since 04:00 ET
|
|
DISCOVER_SNAPSHOT_MAX_TRADE_AGE_SECONDS=180
|
|
DISCOVER_SNAPSHOT_MAX_BAR_AGE_SECONDS=300
|
|
DISCOVER_SNAPSHOT_BATCH=2000 # symbols per request (URL-length bound; 414 at ~12.7k)
|
|
# Active setups. The backtest sweep found the opening-range breakout is the only
|
|
# profitable one (others lose and fire later); empty = all setups. See learn_params.py.
|
|
STRATEGY_SETUPS=opening_range_break
|
|
|
|
# --- news / catalyst ranking ------------------------------------------------
|
|
# A fresh catalyst (PR/earnings/FDA/offering) is WHY a small-cap gaps and runs.
|
|
# These RSS feeds are ingested each discover pass; tickers with recent news are
|
|
# (1) folded into the watch universe (caught earlier) and (2) boosted up the
|
|
# armed-trigger ranking. Format: name=url,name=url. Empty = news disabled.
|
|
NEWS_FEEDS=prnewswire=https://www.prnewswire.com/rss/news-releases-list.rss,businesswire=https://feed.businesswire.com/rss/home/?rss=G1QFDERJXkJeGVtRWQ==,globenewswire=https://www.globenewswire.com/RssFeed/orgclass/1/feedTitle/GlobeNewswire%20-%20News%20about%20Public%20Companies
|
|
NEWS_RANK_BOOST=2.0 # multiply a catalyst-backed gapper's rank score
|
|
NEWS_LOOKBACK_HOURS=8 # how fresh the catalyst must be
|
|
# Manual per-symbol priority nudge (a SMALL boost to names you want watched).
|
|
# Keep modest (1.1-1.3) so it doesn't override the gap/volume logic. Empty = off.
|
|
# e.g. SYMBOL_WEIGHTS=SOFI:1.2,RIVN:1.15
|
|
SYMBOL_WEIGHTS=
|
|
|
|
# --- fast armed-trigger path (runtime/triggers.py) ---------------------------
|
|
# Keep the N most-promising gappers "armed" with a pre-computed opening-range
|
|
# trigger/stop and FIRE the instant live price crosses (a marketable order that
|
|
# actually fills on a runner) — instead of waiting for a 1-min bar to close and
|
|
# resting a limit that never fills. This is what catches the move in time.
|
|
TRIGGER_ENABLED=1
|
|
TRIGGER_MAX_ARMED=10 # how many gappers to keep ready at once (batched price fetch scales fine)
|
|
TRIGGER_INTERVAL_SECONDS=4 # how often to check live prices / fire
|
|
TRIGGER_ARM_INTERVAL_SECONDS=20 # how often to re-rank + recompute triggers
|
|
TRIGGER_GAP_MIN=10.0 # aligned with TRADING_MIN_ENTRY_GAP_PCT (population gate) 2026-07-03
|
|
TRIGGER_RVOL_MIN=2.0 # min relative volume to be eligible to fire
|
|
# Entry-quality filters (diagnostic + out-of-sample verified, verify-entry-filters):
|
|
# capping price at $5 and skipping blow-off gaps (>60%) ~DOUBLED held-out
|
|
# expectancy (+0.76 vs +0.39R, win 86% vs 71%). $10-20 stocks were net losers;
|
|
# >150% gaps fade. Fewer, much higher-quality trades.
|
|
TRIGGER_GAP_MAX=35 # 35-150% band is dead (+0.02R year-scale) — cap earns its keep there
|
|
# mega-gapper exception (2026-07-06, the LHSW/NXTS fix): re-admit REAL >150% squeezes.
|
|
# Year replay n=2,879 w/ split artifacts removed: >150% & rvol>=5 ran +0.63R, 8/14 months >=0.
|
|
TRIGGER_GAP_SQUEEZE_MIN=150
|
|
TRIGGER_GAP_SQUEEZE_RVOL=5.0
|
|
WATCHER_PRICE_MIN=2 # raised $1->$2 2026-07-09: $1-2 bucket -0.031R over 609 unbiased entries
|
|
TRADING_PRICE_MIN=2.0
|
|
TRADING_PRICE_MAX=20.0
|
|
WATCHER_PRICE_MAX=20 # RESTORED to the original $1-20 (2026-06-22). Was narrowed to 5 by a prior backtest ("$10-20 net losers") that the LIVE record contradicts + is n=2 noise; and $5 is why the bot missed NXTS ($9.21, today's +250% winner). Downside is now $500/trade-capped so the wider net is a bounded experiment — measure by price band on the clean sample before re-narrowing.
|
|
ORB_BARS=5 # opening range = first N regular-hours minutes
|
|
TRADING_TRIGGER_SLIP_PCT=0.004 # cap chase above the trigger on the fill
|
|
WATCHER_STATIC_SYMBOLS=TSLA,NVDA,AMD,MSFT,AAPL
|
|
ACCOUNT_SYNC_ENABLED=0
|
|
ORDER_SYNC_ENABLED=0
|
|
# --- LIVE PAPER TRADING (enabled for market open) ---
|
|
ALPACA_PAPER_SYNC_ENABLED=1
|
|
TRADING_EXECUTION_ENABLED=1
|
|
TRADING_AUTO_APPROVE=1 # 1 = auto-arm bracket orders; 0 = approve each in the dashboard
|
|
TRADING_MAX_DAILY_LOSS_PCT=0.05 # RE-ARMED 2026-06-22 (was 0.50 testing): circuit breaker halts entries + flattens at -5% equity (~-$4.5k on $90k). Caps a runaway day; per-trade risk is already $500-capped so ~all-stop-out ≈ -5%. Original design was 0.03 — tighten if you want a harder stop.
|
|
TRADING_RISK_PER_TRADE_PCT=0.01 # risk 1% of equity per trade
|
|
TRADING_MAX_RISK_DOLLARS=500 # HARD dollar-risk ceiling/trade: risk = min(1% equity, $500). On the ~$90k paper acct this caps the wide-stop ~$900-1k losers from the loss diagnosis at $500. 0=off (small live acct uses the % only)
|
|
TRADING_MAX_FRESH_ENTRIES_PER_DAY=10 # EXPERIMENT 2026-07-02 (owner-authorized): raised 5->10 real FILLED entries/day (behavior fix counts fills not submits). Data says all-day/more-trades is -EV (afternoon -0.94R); paper A/B only. C+ quality gate + re-entry-on-winners still in force.
|
|
TRADING_MAX_CONCURRENT_POSITIONS=10 # raised 6->10; the 60% gross-notional cap below still hard-bounds TOTAL exposure (so this = more, smaller positions, not more risk)
|
|
TRADING_REQUIRE_ABOVE_VWAP=1 # VWAP entry gate: only take breakouts trading >= session VWAP. The ONE validated selection signal (above-VWAP reaches +1R ~1.5x as often, n=3,110 from the labeler). 0=shadow-only (logs below-VWAP signals as rule_type=vwap_below but still enters). Set 0 to revert to pre-gate behavior.
|
|
TRADING_UNIFIED_ENTRY=1 # run the fast path's anti-chase gates (day-extension: don't enter a name whose breakout level is >30% above the session open; halt) on the LIVE auto path too. Closes the gap where those guards only ran on the rarely-used fast trigger path. Fail-open on missing data. 0=pre-unification (auto path skips these gates).
|
|
TRADING_GATE_RECEIPTS=1 # emit one entry_gate_evaluated event per gate evaluation (both paths) with the full pass/reject gate map — turns 'why didn't it trade X?' into a query. High-volume diagnostic, purged after 14 days like other observability spam. 0=off (no receipts).
|
|
TRADING_ENTRY_FILL_MODEL=resting # auto-path fill: 'resting' = rest a limit AT the breakout level (fills only on a pullback, misses runners that gap up). 'marketable' = limit a hair above the trigger so it fills as price breaks UP through it (catches runners, the fast path's behavior; the limit still caps the fill so no gap-chase). VALIDATED LIVE 2026-06-24 open (NOK/SOFI/SSPC filled near-trigger, no chase) then reverted to resting (safe default). Flip to 'marketable' to use it.
|
|
# Account-aware sizing: positions size off REAL broker equity (auto — so a $300
|
|
# live account sizes for $300, not the $100k default). One position's value is
|
|
# capped to this fraction of equity so N positions fit buying power (~1/N).
|
|
# 0.16 so 6 positions fit (6 x 16% ~ full account) — esp. the $300 cash account.
|
|
TRADING_MAX_POSITION_PCT=0.16
|
|
# Liquidity cap (shares <= this % of the symbol's day volume) — OFF at small
|
|
# size; enable (e.g. 0.005) only when scaling to real money to avoid slippage.
|
|
TRADING_LIQUIDITY_MAX_VOLUME_PCT=0.025
|
|
# End-of-day auto-flatten: close everything ~5 min before the bell so the book
|
|
# never carries overnight gap risk (a day-trading strategy shouldn't hold).
|
|
TRADING_EOD_FLATTEN_ENABLED=1
|
|
TRADING_EOD_FLATTEN_TIME=15:55
|
|
# The process is one trading session: after a SUCCESSFUL EOD flatten the loop
|
|
# exits 0 and compose restarts it fresh, so memory-only daily state (entry cap,
|
|
# benches, session-closed flags) can never go stale across a date rollover.
|
|
TRADING_EXIT_AFTER_EOD_FLATTEN=1
|
|
TRADING_MAX_ORDERS_PER_TICK=1
|
|
TRADING_ENTRY_ORDER_TYPE=limit # rest at the breakout trigger
|
|
# --- managed exit (swept + out-of-sample verified: trailLow_a1) -------------
|
|
# The exit sweep over 187 sessions with a held-out test split found that
|
|
# trailing the stop under the prior bar's low (after +1R) nearly DOUBLES the
|
|
# out-of-sample expectancy vs the old fixed +2R target (+0.128R vs +0.067R,
|
|
# win 49% vs 38%) and is robust, not overfit. So: a far backstop target and let
|
|
# the trailing stop do the work. See sweep_exits.py / strategy/exits.py.
|
|
TRADING_REWARD_MULTIPLE=10.0 # far take-profit backstop; the trail is the real exit
|
|
TRADING_EXIT_TRAIL_MODE=prior_low # trail the stop under each prior bar's low
|
|
TRADING_EXIT_TRAIL_AFTER_R=1.0 # start trailing once the trade is +1R in profit
|
|
# Profit-lock checkpoints (percentage of ENTRY): once a trade has been up gain%,
|
|
# the stop is pinned so it can't close for less than lock% — you always keep a
|
|
# minimum gain once it's working, and a fade back down sells you out in profit.
|
|
# Format: gain:lock,gain:lock (whole %). Tunable; raise/lower to taste.
|
|
TRADING_EXIT_PROFIT_TIERS=8:3,15:9,25:18,40:30
|
|
TRADING_EXIT_BREAKEVEN_R=1.0 # ON: lock breakeven once +1R (forward safeguard)
|
|
TRADING_EXIT_TRAIL_R_STEP=0.25 # 0.25R STEP-TRAIL from the buy price (2026-06-24, supersedes a standalone +2% breakeven): once +0.25R is reached the stop moves to entry (breakeven), +0.5R->+0.25R, +0.75R->+0.5R, ... It locks a small green pop from round-tripping to -1R (the SOFI +$281->-$118 case) AND keeps climbing under a runner. Coexists with the prior-low trail (stop = max of all). Tradeoff: scratches some winners at breakeven on noise (sim ~+0.05R net). Widen to 0.5 if too tight.
|
|
# TRADING_EXIT_SCALE_OUT_R / _PCT # (off) scaling didn't help out-of-sample
|
|
# TRADING_EXIT_FIRST_RED=0 # (off) first-red exits were NEGATIVE expectancy here
|
|
# How far price may dip below the entry trigger before we CANCEL an unfilled
|
|
# entry. 0.0 (the old default) cancels on any sub-cent wobble -> a breakout that
|
|
# oscillates around the level gets cancelled before it can fill = pure churn,
|
|
# never holds a trade. Give it room; the bracket STOP (at the opening-range low)
|
|
# is the real protection, and risk is capped at 1% per trade by sizing.
|
|
TRADING_ENTRY_INVALIDATE_PCT=0.015
|
|
TRADING_ENTRY_TIMEOUT_BARS=2 # cancel an entry still unfilled after ~2 min
|
|
|
|
# ---- Database (PostgreSQL) — single datastore via local port-proxy to the LAN instance
|
|
DATABASE_URL=<REDACTED> # direct to the LAN host; storage/db_pg.py auto-falls-back between 192.168.1.5 and the 127.0.0.1 netsh tunnel (whichever the WSL net can reach — it flips across reboots).
|
|
|
|
# anti-thrash: bench a symbol after it backs out (unfilled entry)
|
|
TRADING_BACKOUT_COOLDOWN_SECONDS=180
|
|
TRADING_MAX_BACKOUTS_PER_SYMBOL=3
|
|
|
|
# one round-trip per symbol per day: no re-entry after a position closes
|
|
TRADING_BLOCK_REENTRY_AFTER_EXIT=1
|
|
|
|
# liquidity gate: skip breakouts on thin names (recent 5-bar $-volume floor)
|
|
TRIGGER_MIN_DOLLAR_VOL=8000
|
|
|
|
# only bench re-entry after a real losing exit (>1%), not a scratch/win
|
|
TRADING_REENTRY_MIN_LOSS_PCT=0.01
|
|
TRADING_MAX_GROSS_NOTIONAL_PCT=0.60
|
|
ALPACA_BACKFILL_FEED=sip
|
|
INGEST_STALE_SECONDS=180
|
|
INGEST_STALE_BLOCK=0
|
|
TRIGGER_PRICE_MAX_AGE=12
|
|
|
|
# ============================================================
|
|
# Local LLM news/catalyst enrichment (Ollama) — TEST SPOT
|
|
# Detected running at the Windows host (192.168.1.9:11434).
|
|
# Phase-1 advisory only; trading behaviour does NOT change. If Ollama is down or
|
|
# the model is missing it degrades to a no-op on its own thread (never impedes).
|
|
# ============================================================
|
|
OLLAMA_ENABLED=1
|
|
OLLAMA_HOST=http://192.168.1.5:30068 # direct to host (WSL tunnel flips; client also falls back)
|
|
OLLAMA_MODEL=qwen2.5:7b-instruct
|
|
OLLAMA_TIMEOUT_SECONDS=60
|
|
NEWS_ENRICH_ENABLED=1
|
|
NEWS_ENRICH_INTERVAL_SECONDS=120
|
|
NEWS_ENRICH_LOOKBACK_HOURS=12
|
|
NEWS_ENRICH_BATCH_LIMIT=50
|
|
# Phase-2 automation stays OFF until you've watched a clean advisory run:
|
|
NEWS_CATALYST_SCORE_ENABLED=1
|
|
NEWS_DILUTION_VETO_ENABLED=1
|
|
OLLAMA_TEMPERATURE=0.1
|
|
|
|
TRADING_MIN_ENTRY_GAP_PCT=0.10 # POPULATION gate (year-long +EV boundary, n=3008/14mo): only real gappers (open vs prev close >= 10%) are tradeable; all rank x timing segments +0.05..+0.15R under the BE@+5% ratchet
|
|
TRADING_MIN_DAY_VOLUME=300000 # population volume floor (shares, consolidated SIP): thin names whipsaw; fail-open on missing data
|
|
TRADING_ENTRY_CONFIRM_BY_MINUTE=0 # OFF 2026-07-03: year-long replay (n=3008, 14mo) REFUTED the 15-min gate — late crosses beat early 12/14 months; population is the real +EV boundary (see below)
|
|
TRADING_MIN_QUALITY_SCORE=0.60 # C or higher (owner 2026-07-07 after F-tier experiment; grade map: A>=.9 B>=.75 C>=.6 D>=.4). Old 0.50 was mislabeled C+ — actually mid-D
|
|
|
|
# YouTube claim-extraction pipeline
|
|
YOUTUBE_API_KEY=<REDACTED>
|
|
YT_WATCHLIST=
|
|
ALPACA_DATA_FEED=sip # Algo Trader Plus (paid 2026-07-02, entitlement verified 07-03): real-time consolidated tape. Flip back to iex if the plan lapses — the ratio estimator self-reactivates.
|