A simple tool for investors to find cash-flowing properties in Canada. Scans target cities, underwrites with a conservative cash-flow model, and ranks deals by margin of safety.
- Data sources: Realtor.ca (RapidAPI) or Redfin Canada (RapidAPI) – switch via
config.yamlor--source; usebothto merge and dedupe - Property types: Duplex, triplex, multi-unit, single-family with secondary suites (house-hack plays)
- Underwriting: Base case + stress test, margin-of-safety score, pass/fail thresholds
- Outputs: CSV, JSON, DuckDB (
listings_raw,deals_underwritten)
# Install
pip install -r requirements.txt
# Set RAPIDAPI_KEY in .env (or export RAPIDAPI_KEY)
# Run full pipeline (fetch from API → underwrite → report)
python -m real_deal.cli run
# Or step by step
python -m real_deal.cli fetch
python -m real_deal.cli underwrite
python -m real_deal.cli report| Command | Description |
|---|---|
python -m real_deal.cli fetch |
Fetch listings from API and store raw data |
python -m real_deal.cli fetch --source redfin |
Use Redfin Canada instead of Realtor.ca |
python -m real_deal.cli fetch --source both |
Fetch from both APIs, merge and dedupe by ID |
python -m real_deal.cli underwrite |
Underwrite stored listings and save results |
python -m real_deal.cli report |
Display ranked deals table |
python -m real_deal.cli run |
End-to-end: fetch → underwrite → report |
Edit config.yaml:
- data_source.connector:
realtor,redfin, orboth(default) – which API(s) to use (both merges and dedupes by listing ID) - max_price: 550000
- cities: Tier 1–3 Ontario cities
- keyword_filters: include/exclude for duplex, triplex, secondary suite, etc.
- underwriting: vacancy, management, maintenance, capex, insurance, utilities, closing costs, down payment, interest rate, amortization, property tax
- stress_test: rent haircut, interest rate bump, vacancy bump
- pass_fail: min cashflow, min DSCR, min cash-on-cash
- rent_estimation: tiered formula by city tier (base + per_bedroom × bedrooms); overridden by explicit rent in listing description
The engine uses a conservative income-property model: base-case cash flow plus a stress test, then ranks deals by a margin-of-safety score and applies pass/fail thresholds.
- Explicit rent: If the listing description contains a rent amount (e.g.
$2000/mo,Rent: $2400), that value is used. - Fallback (tiered): Otherwise rent is estimated as base + (per_bedroom × bedrooms) from
config.yaml, using the city’s tier. Tiers:tier_1(higher markets, e.g. Hamilton),tier_2,tier_3,bruce_county(smaller markets). Example: Hanover 3-bed in bruce_county uses base $1,000 + $500/bed = $2,500/mo.
-
NOI (Net Operating Income, annual)
- Gross potential income = monthly rent × 12, then reduced by vacancy (configurable, e.g. 6%).
- Operating expenses (all annual): management %, maintenance %, capex reserve %, property tax (price × rate), insurance, utilities, snow/lawn.
- NOI = GPI − vacancy − all op ex.
-
PITI (monthly)
- Principal + interest from a standard amortization (down payment %, interest rate, amortization years).
- Plus monthly property tax and monthly insurance.
-
Monthly cash flow
- Cash flow = (NOI ÷ 12) − PITI.
-
Metrics
- Cap rate = NOI ÷ purchase price.
- Cash-on-cash = annual cash flow ÷ (down payment + closing costs).
- DSCR = NOI ÷ annual debt service (lender-style coverage ratio).
A worse-case scenario is run with:
- Rent haircut (e.g. 7% lower rent)
- Higher vacancy (e.g. +2%)
- Higher interest rate (e.g. +1%)
Stress NOI and PITI are recomputed with these inputs; stress cash flow = (stress NOI ÷ 12) − stress PITI. A deal is expected to remain viable (or at least not deeply negative) under this scenario.
The score starts at 50 and adds points for:
- Stress cash flow > 0 (+25)
- Stress cash flow ≥ min threshold (+15)
- Cash-on-cash ≥ threshold (+5)
- DSCR ≥ threshold (+5)
Higher score = more cushion against rent, vacancy, or rate shocks.
A listing passes only if all of the following hold (thresholds in config.yaml):
- Base monthly cash flow ≥ min (e.g. $150)
- Stress cash flow ≥ 0
- Cash-on-cash ≥ min (e.g. 8%)
- DSCR ≥ min (e.g. 1.15)
The report shows reason_flags for each metric (PASS/FAIL and value).
All listing data comes from the API. No mock or hardcoded data.
- Sign up at RapidAPI
- Subscribe to either:
- Realtor.ca Scraper API (baqo271) – residential listings by bounding box
- Redfin Canada API (Apidojo) – more listings per city, search by region
- Add your API key to
.env:RAPIDAPI_KEY=your_key_here - Choose connector in
config.yaml:data_source.connector: "realtor"or"redfin" - Run:
Or step by step:
python -m real_deal.cli run
fetch→underwrite→report
The connector is designed so you can add an Apify HouseSigma actor or another source by implementing the ListingConnector interface.
RealDeal/
├── config.yaml # Config (cities, underwriting, thresholds)
├── requirements.txt
├── src/real_deal/
│ ├── cli.py # Typer CLI
│ ├── config.py # Config loader
│ ├── models.py # Listing, UnderwritingResult, etc.
│ ├── filters.py # Keyword + price filters
│ ├── connectors/
│ │ ├── base.py # ListingConnector interface
│ │ ├── rapidapi_realtor.py
│ │ └── rapidapi_redfin.py
│ ├── underwriting/
│ │ ├── engine.py # UnderwritingEngine
│ │ └── rent.py # Rent estimation + parse from description
│ └── storage/
│ ├── db.py # DuckDB (listings_raw, deals_underwritten)
│ └── export.py # CSV, JSON export
├── tests/
└── output/ # CSV, JSON, DuckDB, raw payloads
- cashflow_monthly: Base-case monthly cash flow
- stress_cashflow_monthly: Stress-case (rent haircut, higher vacancy, higher rate)
- cap_rate: NOI / purchase price
- cash_on_cash: Annual cash flow / total cash in (down + closing)
- DSCR: Debt service coverage ratio
- margin_of_safety_score: 0–100 (higher = more cushion)
- reason_flags: Pass/fail reasons for each threshold
pytest tests/ -v -p no:anchorpyAll data comes from the API. Example report from python -m real_deal.cli run:
Best Deals (Run YYYYMMDD_HHMMSS)
┏━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━┳━━━━━━┓
┃ Rank ┃ Address┃ City ┃ Price ┃ CF/mo ┃ Stress ┃ CoC ┃ DSCR ┃ MoS ┃ Pass ┃
┡━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━╇━━━━━━┩
│ 1 │ ... │ Windsor│ $399,000│ $xxx │ $xxx │ x.x% │ x.xx │ xx │ ✓ │
│ 2 │ ... │ London │ $475,000│ $xxx │ $xxx │ x.x% │ x.xx │ xx │ ✗ │
└──────┴────────┴────────┴────────┴───────┴────────┴───────┴──────┴─────┴──────┘
Output files:
output/deals_<run_id>.csv– Ranked dealsoutput/deals_<run_id>.json– Full underwriting detailsoutput/real_deal.duckdb– DuckDB databaseoutput/raw/listings_<run_id>.json– Raw API payloads (for debugging)
MIT