| Symbol | Strategy | Entry | Current | P&L | Stop | Status |
|---|---|---|---|---|---|---|
| NVDA | B20 RTH | $892.40 | $908.15 | +$315 | $885.00 | TP1 HIT |
| META | GRAIL STR | $512.80 | $519.40 | +$264 | $508.00 | TRAILING |
| SMCI | B20 RTH | $48.20 | $50.15 | +$195 | $47.50 | OPEN |
| TSLA | GRAIL LAX | $182.60 | $180.40 | -$88 | $179.00 | AT RISK |
| Filter | Input | Passed | Rejected | Rate |
|---|---|---|---|---|
| WatchListScanner CSV | 1,200 | 1,200 | 0 | 100% |
| outstanding_shares | 1,200 | 1,050 | 150 | 87.5% |
| price_snapshot | 1,050 | 980 | 70 | 93.3% |
| min_volume | 980 | 890 | 90 | 90.8% |
| locale_us | 890 | 847 | 43 | 95.2% |
| filtered_universe (view) | — | 847 | — | FINAL |
| Symbol | Side | Qty | Entry | Current | P&L $ | P&L % | State |
|---|---|---|---|---|---|---|---|
| ⟳ Loading live positions from Alpaca… | |||||||
| Symbol | Side | Qty | Price | Type | Status | Time |
|---|---|---|---|---|---|---|
| ⟳ Loading orders from Alpaca… | ||||||
| Symbol | Strategy | Profile | Timeframe | Confidence | Volume | Price | Validator | Risk | Status |
|---|---|---|---|---|---|---|---|---|---|
| NVDA | Breakout20 | RTH Core | 15m | 0.89 | $2.1M | $892.40 | PASS | CLEAR | APPROVED |
| META | GRAIL | Strict | 15m | 0.82 | $1.8M | $512.80 | PASS | CLEAR | APPROVED |
| SMCI | Breakout20 | RTH Core | 15m | 0.77 | $0.9M | $48.20 | PASS | CLEAR | APPROVED |
| TSLA | GRAIL | Lax | 15m | 0.71 | $4.2M | $182.60 | PASS | CLEAR | APPROVED |
| AMZN | Breakout20 | RTH Core | 15m | 0.74 | $3.2M | $188.50 | CHECK | CLEAR | VALIDATING |
| GME | Breakout20 | Microcap | 5m | 0.44 | $0.3M | $24.10 | FAIL | REJECTED | REJECTED |
| MSTR | GRAIL | Lax | 1h | 0.51 | $1.1M | $178.40 | FAIL | N/A | REJECTED |
| Exit Type | Trigger | Hits Today | P&L |
|---|---|---|---|
| TP1 | First profit target hit | 4 | +$740 |
| TP2 | Second profit target hit | 2 | +$484 |
| Trail | Trailing stop post-TP1 | 2 | +$386 |
| Stop Loss | Hard stop hit | 2 | -$200 |
| Time Stop | Held too long, no progress | 1 | -$80 |
| Failure Exit | Broker/system error recovery | 0 | — |
| System | Module | Blocks | Status |
|---|---|---|---|
| Kill Switch | kill_switch.py | All new entries | OFF — Entries OK |
| Daily Risk Guard | risk_manager.py | After loss threshold | CLEAR (31%) |
| Entry Validation | entry_validation.py | Kill + daily loss gate | PASSING |
| Panic Close | panic_close.py | Emergency market-sell ALL | STANDBY |
| Runner Lock | runner_lock.py | Duplicate orchestrators | LOCKED (1) |
| Broker Health Gate | broker_health.py | Processing if unhealthy | HEALTHY |
| V1 Safety Config | v1_safety_config.py | Hard-coded v1 limits | ACTIVE |
| File | Purpose |
|---|---|
| market_data/config.yaml | Tickers, live universes, health thresholds, DB DSN |
| trading/config.yaml | Alpaca base URL, paper flag, default symbol |
| bot/config.yaml | Discord channel IDs and bot settings |
| config/strategies/profiles.yaml | Strategy profile registry |
| breakout20/rth_core_v1_3.yaml | RTH Core scanner gate config |
| breakout20/premarket_open_drive_v1_3.yaml | Premarket scanner gates |
| breakout20/microcap_event_v1_3.yaml | Microcap event gates |
| grail/strict.yaml | GRAIL strict profile gates |
| grail/lax.yaml | GRAIL lax profile gates |
# No live loop: ./run.sh --no-live-loop
# Custom interval: ./run.sh --live-interval 180
# → http://localhost:8500/docs
.\venv\Scripts\python.exe -m ruff check .
# OraCore v3 — .env
# Generated from API Keys Vault
# BROKER
ALPACA_API_KEY_ID=your_alpaca_key
ALPACA_API_SECRET_KEY=your_alpaca_secret
ALPACA_ENDPOINT=https://paper-api.alpaca.markets
ALPACA_PAPER=true
WEBULL_APP_KEY=
WEBULL_APP_SECRET=
WEBULL_ACCOUNT_ID=
# MARKET DATA
MASSIVE_API_KEY_SSLABS=your_polygon_key
MASSIVE_II_API_KEY=your_polygon_key_2
FINNHUB_API_KEY=
IEX_API_KEY=
# AI
OPENAI_API_KEY=your_openai_key
GITHUB_TOKEN=your_github_pat
ANTHROPIC_API_KEY=
# DATABASE
DATABASE_URL=your_postgres_url
DATABASE_DSN=your_postgres_dsn
# NOTIFICATIONS
DISCORD_API_KEY_Alert-v1=your_discord_token
TELEGRAM_BOT_TOKEN=
TWILIO_AUTH_TOKEN=
OraCore v3 requires Python 3.11+, PostgreSQL, and your API keys configured. Follow these steps exactly:
- Clone the repo and run
pip install -r requirements.txt - Copy
.env.example → .envand fill in all REQUIRED keys (Alpaca, Polygon, PostgreSQL) - Run DB migrations:
psql -f database/init.sql - Run the market data ingest:
./run.sh - Start the trading engine:
python -m trading.runtime.run_breakout20_manager
REQUIRED to run: ALPACA_API_KEY_ID, ALPACA_API_SECRET_KEY, MASSIVE_API_KEY_SSLABS (Polygon), DATABASE_URL
REQUIRED for Knowledge Brain: OPENAI_API_KEY (for LLM harvest), GITHUB_TOKEN (for GitHub harvest)
OPTIONAL: Discord (alerts only), Telegram, Webull (alternate broker), IEX/Finnhub (fallback data)
Webull provides a developer API through its open platform. Here is the full connection flow:
- Go to developer.webull.com and create a developer account
- Create a new App → get your App Key and App Secret
- Set
WEBULL_APP_KEYandWEBULL_APP_SECRETin your .env - Install the SDK:
pip install webull - Authenticate:
wb.login(app_key, app_secret)
OraCore runs in paper mode by default. Switching to live requires 3 changes:
- Set
ALPACA_PAPER=falsein .env - Change
ALPACA_ENDPOINTtohttps://api.alpaca.markets - Replace paper API keys with live API keys from Alpaca dashboard
Breakout20 identifies momentum breakouts on 15-minute bars. It fires when a symbol breaks above its 20-period high with above-average volume confirmation.
Gates (in order):
- Price closes above 20-bar high
- Volume is ≥ 1.5x the 20-bar average volume
- ATR spread is within acceptable range (no gap-and-go traps)
- VWAP is below current price (trend confirmation)
- Confidence score ≥ 0.65 (else REJECTED)
GRAIL is a multi-condition strategy combining trend structure, volume, and momentum. Fires on 15m bar close when multiple conditions align simultaneously.
Gates include: EMA alignment, RSI confirmation, VWAP position, volume surge, and structural support/resistance validation.
Profiles: Strict (all gates must pass, production), Lax (relaxed gates, shadow mode only for missed-signal analysis).
Production: Signals from this profile flow through the full pipeline and result in real (or paper) orders being placed.
Shadow: Signals are generated and logged, but NEVER submitted to the broker. Used to measure what a strategy WOULD have done without risking capital.
Diagnostic: Even looser — records raw scanner hits with no filtering, for pure analysis.
OraCore uses ATR-based position sizing. The risk per trade is a fixed dollar amount ($R). Shares = $R ÷ (Entry Price − Stop Price).
# Example: NVDA R = $200 # risk per trade entry = $892.40 stop = $885.00 stop_distance = $7.40 shares = int(200 / 7.40) = 27 sharesThis ensures every trade risks exactly $R regardless of the stock's price or volatility.
TP1: Sell 50% of position at first profit target (typically 1.5R–2R). Activates trailing stop on remainder.
TP2: Exit remaining 50% at second target (typically 3R–4R).
Trailing Stop: Moves stop up as price advances post-TP1. Locks in profits. Uses ATR multiplier to avoid noise.
Time Stop: Exits position if held past a time threshold (e.g. 2 hours) with no progress toward TP1.
Hard Stop: Always active. Market-sell triggered if price hits stop level.
The Kill Switch is the master entry blocker. When activated, NO new signal can result in a trade entry — but all existing positions continue to be managed normally (exits, stop adjustments, reconciliation).
When to use it: Major news events, earnings announcements, unusual market volatility, system debugging, or anytime you want to let existing trades run but take no new ones.
Activate it with the ☠ KILL SWITCH button in the top navigation bar.
The Daily Risk Guard tracks cumulative realized losses for the current trading day. Once losses reach the configured threshold ($1,000 by default), the guard automatically blocks all new entries for the remainder of the session.
The limit resets at midnight UTC (start of next trading day).
# config in risk_manager.py DAILY_LOSS_LIMIT = 1000 # USD # Change this to adjust your daily stop-outPanic Close sends market-sell orders for ALL open positions simultaneously. It bypasses all normal exit logic and submits orders immediately.
Access via: Trading page → Panic Close All button (red, top right).
The Runner Lock prevents more than one orchestrator process from running at the same time. If you accidentally start a second instance, it immediately detects the lock file and exits — preventing duplicate bar processing and double-order submissions.
Lock file location: .runner.lock in the project root.
.runner.lock manually to restart.The ingest system has 3 phases that run in sequence on startup:
- Flatfile Catch-Up: Downloads bulk historical data from Polygon.io flat files (S3-backed). Fills the last 7 days of candles for all 847 symbols across all 6 timeframes. Runs once on startup if data is stale.
- REST Delta Pass: Fetches any missing bars since the flatfile cutoff using the Polygon REST API. Fills gaps precisely.
- Live Ingest Loop: During market hours (9:30 AM–4:00 PM ET), polls Polygon REST every N seconds (default 60s) to fetch the latest completed bars.
The universe starts from a WatchListScanner (WLS) CSV file and is then filtered through 4 gates before being stored in the filtered_universe PostgreSQL view:
- outstanding_shares: excludes micro/nano-cap shells
- price_snapshot: excludes symbols with no recent trade data
- min_volume: minimum daily dollar volume threshold
- locale_us: US-listed securities only
The resulting 847-symbol universe is what the scanners run against on every bar close.
Each symbol is health-checked before the scanner runs. A symbol is HEALTHY if it has complete candle data for the required timeframes with no gaps exceeding a threshold.
A symbol is WARNING if it has minor gaps (e.g., a few missing 5m bars). It still scans but results may be less reliable.
Health results are cached in .cache/shortfalls.csv and shown on the Market Data page health distribution chart.
The harvest pipeline ingests content from 5 sources and chunks it into 512-character segments (64-char overlap) before embedding into ChromaDB:
- GitHub — README.md files from relevant repos (requires GITHUB_TOKEN, free)
- LLM — GPT-4o generates synthetic role-specific content (requires OPENAI_API_KEY)
- Web — DuckDuckGo search + scrape for each role query
- YouTube — Transcript extraction from relevant videos
- PDF — Upload documents via the CLI for ingestion
The REST API (FastAPI on :8500) exposes semantic search over all 48,291 chunks. Queries are embedded using all-MiniLM-L6-v2 and matched against stored vectors via cosine similarity.
GET http://localhost:8500/search?q=how+to+underwrite+high+risk+policies&role=Underwriter&top_k=5The role filter restricts results to chunks from a specific role's knowledge collection.
The candidate scorer takes a resume or profile text, embeds it, and compares it against the knowledge chunks for a target role. It returns a match score (0–100) indicating how well-aligned the candidate is to the role's knowledge profile.
POST http://localhost:8500/score Body: {"text": "resume text here", "role": "Underwriter"}Returns: {"score": 74.2, "top_matches": [...], "gaps": [...]}
All timestamps stored in PostgreSQL are in UTC. Eastern Time (ET) is only derived at session-logic boundaries (e.g., checking if a bar falls within RTH hours). This eliminates DST ambiguity that would otherwise cause the system to misfire on clock change days (March and November).
OraCore uses two separate idempotency layers to prevent duplicate orders on restarts:
- Layer 1 — Signal Claim: A signal is "claimed" with a unique key before any order logic runs. If the same signal fires again, it's ignored.
- Layer 2 — trade_orders table: Each order written to DB before broker submission. On restart, existing orders are reconciled against Alpaca fills before any new entries.
When multiple signals are approved simultaneously and only N slots are available, OraCore selects in this exact order: confidence → fired_at → dollar_volume → signal_id
This deterministic ordering ensures that a system restart after a crash produces exactly the same selection result as if it had never crashed — critical for avoiding phantom position divergence.
- Breakout +10 / +20 logic
- Volume expansion detection
- Multi-timeframe review
- Momentum & compression
- Clean setup filtering
- Market data ingestion health
- Candle & signal storage
- Watchlist tracking
- PostgreSQL / TimescaleDB migration
- Data integrity checks
- Reject weak setups
- Flag stale moves
- Identify overextended names
- Separate structure from hype
- Prevent forced trades
- Discord / SMS / email routing
- Trade-card formatting
- Scanner event routing
- Alert deduplication
- Confidence tagging
- Signal classification
- Catalyst confirmation
- Primary-source verification
- Avoid social-media noise
- Document assumptions clearly
- No public formula exposure
- No private scanner logic leaks
- Strategy compartmentalized
- Fortress always protected
- Proprietary IP never shared
- Use real sources when research is required — no guessing
- Confirm catalysts before treating them as actionable
- Separate primary-source confirmation from social-media noise
- Avoid forcing names — only clean structure enters the pipeline
- Preserve scanner integrity at all times
- Document assumptions clearly in every review
- Keep Earl's system logic protected — never expose proprietary logic
- Setup is clean — structure is clear, not messy or overextended
- Catalyst confirmed from primary source — not social media
- Volume is above average with genuine expansion
- I have a defined entry zone — I am not chasing
- Stop loss is set and I accept that full risk before entering
- Risk/reward is at least 2:1 or better
- Position size is calculated — I know exactly how many shares
- Emotional state is neutral/focused — not FOMO, not revenge trading
- Timeframe confluence confirmed — higher timeframe agrees
- Journal is ready — I will log this trade after close
| Workflow Stage | 🟣 Oraca Role | 🔷 Matrix Role |
|---|---|---|
| Market Scan | Finds and filters potential setups | Converts setup type into student-readable lesson/checklist |
| Signal Review | Checks data, volume, structure, and catalyst quality | Checks whether the trade fits discipline rules |
| Alert Creation | Builds clean alert card with Oraca verdict | Adds checklist, risk reminder, and journal prompt |
| Post-Trade Review | Logs signal behavior and outcome | Turns result into a lesson or drill |
| Strategy Refinement | Flags scanner weaknesses | Updates training material and rules |