DIP-Swing · MTF Strategy

Buy the Dip at EMA Support

Screen every US common stock every hour during market hours via TradingView. Find quality stocks (≥$2B market cap, ≥500K avg volume) that drop ≥8% intraday and land on a major EMA/SMA support level. Enter for a mean-reversion bounce.

The Setup
Strategy Overview
Core Idea

A quality stock drops ≥8% in a single session — earnings miss, sector rotation, macro news. If the drop lands the price on a major EMA level, institutional buyers are likely to step in. We trade the bounce.

Why EMA Support?

Exponential moving averages are self-fulfilling at scale — fund managers and algos watch the same levels. A stock holding at D EMA50 or W EMA20 on a big dip day has strong buyers defending that price.

Support Levels Scanned (11 levels across 5 timeframes)
M EMA20 — Monthly EMA20 M EMA50 — Monthly EMA50 W SMA200 — Weekly SMA200 W EMA20 — Weekly EMA20 W EMA50 — Weekly EMA50 D SMA200 — Daily SMA200 D EMA20 — Daily EMA20 D EMA50 — Daily EMA50 H EMA20 — Hourly EMA20 H EMA50 — Hourly EMA50 H SMA200 — Hourly SMA200

Price within ±3% of any level counts as "at support." Monthly and Weekly levels carry more weight — they represent institutional positioning, not short-term trader memory. 5 years of daily + 30 days of hourly history is downloaded per candidate to compute all levels. H SMA200 requires 200 hours (~8 weeks of market hours) — only fires on liquid stocks with full history.

Support Levels — how to size
1
One level — watchlist only, wait for H price to hold above hourly EMA20
2
Two levels — valid entry, half size; add on H Breakout confirmation
3+
Three+ levels (cluster) — highest conviction, full size

A M EMA20 or W SMA200 hit alone outweighs two daily-only hits — timeframe rank matters more than raw count. H Breakout (1HR price closing above hourly EMA20 after the dip) confirms buyers have stepped in.

Trade Criteria
Entry Checklist
Must Have ✓
  • Down ≥ 8% intraday
    TradingView screener filters this automatically — a real dip, not a grind
  • Market cap ≥ $2B
    Institutional-grade stock with enough buyers to bounce
  • Price at EMA/SMA support ±3%
    At least one of: M EMA20/50, W SMA200, W EMA20/50, D SMA200, D EMA20/50, H EMA20/50, H SMA200
  • Avg volume ≥ 500K/day
    Enough liquidity for clean entry and exit
  • Price ≥ $5
    No penny stocks — real companies only
Prefer / Avoid
  • +
    H Breakout confirmation
    1HR price closing above hourly EMA20 after the dip — confirms buyers have stepped in
  • +
    Multiple EMA levels (2–3+)
    Cluster of support = stronger floor and higher conviction size
  • +
    Dip on high relative volume
    Panic selling on vol spike → buyers absorb at support
  • +
    Sector leader or well-known brand
    NVDA, AAPL, GOOGL-type dips recover faster
  • Avoid: earnings-driven accounting fraud
    Revenue miss is recoverable — restatement is not
  • Avoid: FDA rejection, legal action
    Binary events break support; no EMA holds these
Exit Plan
Target (Take Profit)
+5% to +15% recovery. Scale out at prior day close or next resistance EMA.
Stop Loss
Close below the support EMA on a daily candle. No averaging down into a broken level.
Hold Time
Typically 1–5 days. These are short-term mean-reversion trades, not swing holds.
Automation
How It Runs Every Day
Scan Pipeline (each run)
TradingView Screener
5yr daily + 30d hourly
11-level support check
dip_signals.json
signals_history.json

Step 1: TradingView screener API filters all US common stocks server-side — returns only stocks down ≥8%, mkt cap ≥$2B, avg volume ≥500K — in ~3 seconds. Step 2: only the 10–30 qualifying candidates get 5yr daily + 30d hourly data downloaded. Step 3: 11-level EMA/SMA check across 5 timeframes (M/W/D/H). Results written to signals_history.json — one entry per day, latest scan overwrites — powering the Live Signals page and the full Past Signals archive. New signals vs the prior run trigger an email alert automatically.

Alwaysbackground
Web server running
serve_dip.py on port 8081 — auto-starts on login, auto-restarts if it crashes. Serves the live signals page and the /api/prices endpoint for live P&L.
launchd · KeepAlive
9:35 AMET · open
First scan — opening gap-downs
Catches stocks gapping down at open from overnight earnings, analyst downgrades, or macro news. Best time to find conviction dip setups — volume spikes confirm the level.
scan_dip.py
10:35 AMET
Second scan
Catches stocks that continued fading after the open. Opening buyers absorbed — if price still at support after an hour, the level is holding.
scan_dip.py
11:35 AMET
Midmorning scan
Picks up late-fading stocks and any new catalyst-driven drops from midmorning news flow. Signals page reflects the latest snapshot.
scan_dip.py
12:35 PMET · lunch
Midday scan
Low-volume lunch hour can push weak stocks to new lows while support holds. Quiet tape at EMA = cleaner setup.
scan_dip.py
1:35 PMET
Afternoon scan
European close (11 AM ET) often triggers renewed selling. This scan catches anything new shaking out in early afternoon.
scan_dip.py
2:35 PMET
Pre-close scan
Last chance before the final 30-minute push. Stocks still at support with 30 minutes to go may recover into close — or confirm the break.
scan_dip.py
3:35 PMET · close
Final scan — EOD snapshot + history update
Closing scan reflects final prices. Results written to signals_history.json as today's permanent entry. Use this to plan next-day entries — stocks that closed at support after a 10%+ drop are prime watchlist candidates for the open.
scan_dip.py signals_history.json
+synceach run
Robinhood performance sync
After every scan, open positions are synced from Robinhood — closed trades are marked with exit price and P&L, new positions are added to the log automatically.
sync_perf.py
3:35 PMET · Fri only
Weekly P&L reports emailed
Every Friday after market close: admin gets closed-trade P&L for the week and month, plus open positions with live unrealized gain/loss (monthly breakdown on the last Friday of each month). Each active customer with an email and broker credentials on file gets their own weekly report — their live account's week P&L, open positions, and recent copy-trade activity.
send_perf_report.py send_customer_reports.py
Continuousmarket hours
Live P&L + server always on
serve_dip.py runs on port 8081 with KeepAlive. Live prices for open positions refresh every 5 minutes in the browser. Buy and Close orders can be placed directly from the signals and performance pages via the Robinhood API.
launchd · KeepAlive
Run Manually
# Normal run (writes dip_signals.json) python3 src/scan_dip.py # Preview only — no file written python3 src/scan_dip.py --dry-run # Lower threshold to 5% python3 src/scan_dip.py --min-dip 5
Launchd Agents
# List running agents launchctl list | grep coswing # Restart web server launchctl stop com.coswing.dip_server launchctl start com.coswing.dip_server # View live scan log tail -f logs/dip_scan.log
com.coswing.dip.hourly — runs dip_scan.sh every market hour
com.coswing.dip_server — keeps serve_dip.py alive on port 8081
Files
src/scan_dip.py scanner + email
src/sync_perf.py Robinhood → perf_trades sync
src/send_perf_report.py admin weekly/monthly email report
src/send_customer_reports.py per-customer weekly email report
src/serve_dip.py web server · port 8081
scripts/dip_scan.sh hourly shell wrapper
website-dip/signals_history.json full archive
website-dip/dip_signals.json latest scan
website-dip/perf_trades.json trade log
src/copy_trade.py customer copy engine
src/copy_rh_orders.py direct RH trade detector
data/customers.json customer accounts
logs/dip_scan.log run log
Customer Accounts
Copy Engine
How Trades Are Mirrored

Every trade on the master Robinhood account is automatically mirrored to all active customer accounts, proportionally sized to their capital. Customers can use Robinhood or Alpaca (or both).

Platform Buy / Close
Click Buy or Close on the Robinhood page → master order placed → customer copies fire immediately in the same request.
Direct Robinhood Trade
Trade made in the Robinhood app directly → detected on the next hourly sync, or immediately via the ⎘ Copy RH Trades button on the Robinhood page.
Sizing Rule
qty = floor(capital ÷ max_trades ÷ price)
Each customer's capital and max-positions count is set in data/customers.json.
Extended Hours
Pre-market (7–9:30 AM ET) and after-hours (4–8 PM ET) trades are automatically placed as limit orders with extended_hours=true. Market orders are rejected by both Robinhood and Alpaca outside regular hours — the copy engine handles this automatically.
Copy Pipeline
Master trade fills
copy_trade.py
size per customer
Robinhood limit / Alpaca limit
data/copy_log.json

Direct RH trades (not placed via the platform) are detected by copy_rh_orders.py, which runs at the end of every hourly sync. Order state is tracked in data/rh_copy_seen.json — an order is marked done (never copied again) once it copies with no per-customer errors, or after 5 failed attempts; a transient failure (e.g. a bad broker API key) is retried automatically on the next hourly run instead of being silently dropped. Broker routing: customers with alpaca_key get Alpaca orders; customers with rh_token get Robinhood orders; customers with both get both simultaneously.