fix: Data freshness - only show active auctions

CRITICAL FIXES:
- API: Added end_time > now() filter to all auction queries
- Scheduler: Cleanup expired auctions every 15 minutes
- Scheduler: Scrape auctions every 2 hours (was 1 hour)
- Scheduler: Sniper alert matching every 30 minutes

Affected endpoints:
- GET /auctions (search)
- GET /auctions/feed (unified)
- GET /auctions/hot
- GET /auctions/ending-soon (already had filter)

Updated MARKET_CONCEPT.md with:
- 3 pillars: Pounce Direct, Live Auctions, Drops Tomorrow
- Data freshness architecture
- Unicorn roadmap
This commit is contained in:
yves.gugger
2025-12-11 09:55:27 +01:00
parent e9f06d1cbf
commit 22cb9561ad
3 changed files with 355 additions and 427 deletions

View File

@ -6,504 +6,342 @@
## 📋 Executive Summary
Die **Market Page** ist das Herzstück von Pounce. Hier fließen alle Datenquellen zusammen und werden dem User als **"Clean Feed"** präsentiert.
Die **Market Page** ist das Herzstück von Pounce. Hier fließen alle Datenquellen zusammen:
### Vision (aus pounce_terminal.md)
> *"Die Market Page zeigt alle Domains die entweder:*
> 1. *Zu Verkauf stehen (Auktionen)*
> 2. *Bald frei werden (Drops)*
> 3. *Über Pounce direkt angeboten werden (Pounce Direct)"*
1. **Pounce Direct** — User-Listings (unser USP, 0% Provision)
2. **Live Auktionen** — Externe Plattformen (GoDaddy, Sedo, etc.)
3. **Drops Tomorrow** — Domains bevor sie in Auktionen landen (Phase 3)
### Aktueller Stand: Phase 1 — Intelligence
### Der Weg zum Unicorn (aus pounce_strategy.md)
> *"Der Weg zum Unicorn führt nicht über besseres Scraping, sondern über einzigartigen Content."*
**Aggregation kann jeder. Pounce Direct ist unser USP.**
---
## 🔧 KRITISCHE FIXES (Implementiert am 11.12.2025)
### Problem: Veraltete Daten wurden angezeigt
```
VORHER: Abgelaufene Auktionen wurden im Feed angezeigt
→ Schlechte User Experience
→ Vertrauensverlust
```
### Lösung: Multi-Layer Data Freshness
```python
# 1. API-Filter: Nur laufende Auktionen
query = select(DomainAuction).where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > datetime.utcnow() # ← NEU!
)
)
# 2. Scheduler: Cleanup alle 15 Minuten
scheduler.add_job(
cleanup_expired_auctions,
CronTrigger(minute='*/15'), # Alle 15 Minuten
id="auction_cleanup"
)
# 3. Scraper: Alle 2 Stunden frische Daten
scheduler.add_job(
scrape_auctions,
CronTrigger(hour='*/2', minute=30), # Alle 2 Stunden
id="auction_scrape"
)
```
---
## 📊 Die 3 Säulen des Market
### Säule 1: POUNCE DIRECT (Unser USP!)
> *"Das sind die Domains, die es NUR bei Pounce gibt."*
```
┌─────────────────────────────────────────────────────────────────┐
│ POUNCE MARKET — Aktueller Datenfluss
💎 POUNCE DIRECT
├─────────────────────────────────────────────────────────────────┤
│ │
DATENQUELLEN:
Warum es genial ist:
│ ───────────────────────────────────────────────────────────── │
│ ✓ Unique Content (nur bei uns!) │
│ ✓ 0% Provision (vs. 15-20% bei Sedo) │
│ ✓ DNS-Verifizierung = Trust │
│ ✓ Instant Buy (kein Bieten) │
│ ✓ SEO: Jedes Listing = eigene Landing Page │
│ │
📦 WEB SCRAPING (Hauptquelle)
│ └─→ ExpiredDomains.net (325 Auktionen) ✅ │
│ └─→ GoDaddy RSS Feed (10 Auktionen) ✅ │
│ └─→ Sedo Public (7 Auktionen) ✅ │
│ └─→ NameJet Public (6 Auktionen) ✅ │
│ └─→ DropCatch Public (7 Auktionen) ✅ │
│ │
│ 🔌 OFFIZIELLE APIs (Konfiguriert) │
│ └─→ DropCatch Partner API ⚠️ (Nur eigene Aktivitäten) │
│ └─→ Sedo Partner API ⏳ (Credentials fehlen) │
│ │
│ 💎 POUNCE DIRECT (User-Listings) │
│ └─→ DNS-verifizierte Verkaufsangebote ❌ (0 Listings) │
│ │
│ 🔮 ZONE FILES (Phase 3 — Zukunft) │
│ └─→ Verisign .com/.net 🔜 │
│ └─→ PIR .org 🔜 │
│ │
Der Flow:
│ ───────────────────────────────────────────────────────────── │
TOTAL: 355 Domains im Feed | 0 Pounce Direct
1. User listet Domain (Trader/Tycoon Abo)
│ 2. DNS-Verifizierung (TXT Record) │
│ 3. Listing erscheint im Market Feed │
│ 4. Käufer kontaktiert Verkäufer (nach Login) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 📊 TEIL 1: Bestandsaufnahme — Was haben wir?
### A. Backend-Komponenten ✅
| Komponente | Status | Beschreibung |
|------------|--------|--------------|
| **Unified Feed API** `/auctions/feed` | ✅ Live | Kombiniert Pounce Direct + External |
| **Pounce Score v2.0** | ✅ Live | Length, TLD, Bids, Time Pressure |
| **Vanity Filter** | ✅ Live | Premium-Domains für Public Users |
| **Auction Scraper** | ✅ Läuft | 5 Plattformen, Scheduler aktiv |
| **Listings API** | ✅ Fertig | DNS-Verifizierung, Inquiry-System |
| **Sniper Alerts** | ✅ Fertig | Keyword-Matching, Notifications |
### B. Frontend-Komponenten ✅
| Seite | Status | Beschreibung |
|-------|--------|--------------|
| `/terminal/market` | ✅ Live | Vollständiger Market Feed für Auth Users |
| `/auctions` | ✅ Live | Public Market mit Vanity Filter |
| `/buy` | ✅ Live | Pounce Direct Marketplace Browse |
| `/buy/[slug]` | ✅ Live | Listing-Detailseite |
| `/terminal/listing` | ✅ Live | Seller Dashboard |
### C. Datenquellen — Realitätscheck
#### Offizielle APIs — Die Ernüchterung
**DropCatch API:**
```
Status: ✅ Authentifiziert
Problem: Zeigt nur EIGENE Aktivitäten (Bids, Backorders)
NICHT das öffentliche Auktionsinventar
Nutzen: User-Integration (verbinde dein DropCatch-Konto)
```
**Sedo API:**
```
Status: ⏳ Client bereit, Credentials fehlen
Wo finden: Sedo.com → Mein Sedo → API-Zugang
Benötigt: Partner ID + SignKey
```
#### Web Scraping — Unsere Hauptquelle
```python
# Aktuelle Scraper-Architektur
TIER_1_APIS = [
("DropCatch", _fetch_dropcatch_api), # Für eigene Aktivitäten
("Sedo", _fetch_sedo_api), # Wenn konfiguriert
]
TIER_2_SCRAPING = [
("ExpiredDomains", _scrape_expireddomains), # 325 Domains
("GoDaddy", _scrape_godaddy_rss), # 10 Domains
("Sedo", _scrape_sedo_public), # 7 Domains (Fallback)
("NameJet", _scrape_namejet_public), # 6 Domains
("DropCatch", _scrape_dropcatch_public), # 7 Domains (Fallback)
]
```
**Status:** ⏳ 0 Listings — Muss aktiviert werden!
---
## 🎯 TEIL 2: Das Konzept — Die 3 Säulen des Market
### Säule 1: AUKTIONEN (Externe Plattformen)
### Säule 2: LIVE AUKTIONEN (Content Filler)
> *"Zeige alle relevanten Auktionen von GoDaddy, Sedo, NameJet, etc."*
**Datenquellen:**
- Web Scraping (primär)
- Partner APIs (wenn verfügbar)
**Filter-Strategie:**
```python
# Vanity Filter für Public Users (aus pounce_features.md)
def is_premium_domain(domain: str) -> bool:
name, tld = domain.rsplit('.', 1)
# Premium TLDs only
if tld not in ['com', 'io', 'ai', 'co', 'ch', 'de', 'net', 'org', 'app', 'dev']:
return False
# Keine Spam-Muster
if len(name) > 12: return False
if '-' in name: return False
if sum(c.isdigit() for c in name) > 1: return False
return True
```
**UI-Darstellung:**
| Domain | Source | Price | Status | Action |
|--------|--------|-------|--------|--------|
| **crypto-bank.io** | 🏢 GoDaddy | $2,500 | ⏱️ 2h left | [Bid ↗] |
| **meta-shop.com** | 🏢 Sedo | $5,000 | 🤝 Offer | [View ↗] |
---
### Säule 2: POUNCE DIRECT (User-Listings)
> *"Das sind die Domains, die es NUR bei Pounce gibt. Unser USP."*
**Das Konzept (aus pounce_terminal.md):**
```
┌─────────────────────────────────────────────────────────────────┐
POUNCE DIRECT — Der Listing-Wizard
🏢 LIVE AUKTIONEN
├─────────────────────────────────────────────────────────────────┤
│ │
STEP 1: DOMAIN EINGEBEN
│ ───────────────────────────────────────────────────────────
Domain: [zurich-immo.ch___________]
Preis: [$950_______] ○ Fixpreis ● Verhandlungsbasis
Datenquellen:
│ ─────────────────────────────────────────────────────────────
📦 Web Scraping (Hauptquelle)
└─→ ExpiredDomains.net (~350 Domains)
│ └─→ GoDaddy RSS │
│ └─→ Sedo Public │
│ └─→ NameJet Public │
│ └─→ DropCatch Public │
│ │
STEP 2: DNS VERIFICATION (Trust-Check)
│ ───────────────────────────────────────────────────────────
Füge diesen TXT-Record bei deinem Registrar hinzu:
Data Freshness:
│ ─────────────────────────────────────────────────────────────
⏱️ Scraping: Alle 2 Stunden
│ 🧹 Cleanup: Alle 15 Minuten │
│ ✅ Filter: Nur end_time > now() │
│ │
Name: _pounce-verify
Value: pounce-verify-8a3f7b9c2e1d
[🔄 VERIFY DNS]
│ STEP 3: LIVE! │
│ ─────────────────────────────────────────────────────────── │
│ ✅ Domain verifiziert! │
│ Dein Listing erscheint jetzt im Market Feed. │
Qualitätsfilter:
─────────────────────────────────────────────────────────────
• Vanity Filter für Public (nur Premium-Domains)
• Pounce Score (0-100)
• TLD Filter (com, io, ai, etc.)
│ │
└─────────────────────────────────────────────────────────────────┘
```
**Warum das genial ist:**
| Vorteil | Erklärung |
|---------|-----------|
| **Unique Content** | Domains, die es NUR bei Pounce gibt |
| **Höhere Conversion** | "Instant Buy" statt "Bid on external site" |
| **Vendor Lock-in** | Verkäufer listen bei uns (0% Provision!) |
| **SEO Power** | Jede Listing = eigene Landing Page |
| **Trust Signal** | DNS-Verifizierung = Qualitätsgarantie |
**UI-Darstellung:**
| Domain | Source | Price | Status | Action |
|--------|--------|-------|--------|--------|
| **zurich-immo.ch** | 💎 **Pounce** | **$950** | ⚡ **Instant** | **[Buy Now]** |
**Status:** ✅ ~361 aktive Auktionen
---
### Säule 3: DROPS (Domains die bald frei werden)
### Säule 3: DROPS TOMORROW (Tycoon Exclusive)
> *"Zeige Domains BEVOR sie in Auktionen landen."*
**Phase 1 (Jetzt): Deleted Domains via Scraping**
```
ExpiredDomains.net → Deleted Domains Liste → Pounce Filter → Feed
```
**Phase 3 (Zukunft): Zone File Analysis**
```
┌─────────────────────────────────────────────────────────────────┐
ZONE FILE PIPELINE — Die Unicorn-Strategie
🔮 DROPS TOMORROW — Phase 3
├─────────────────────────────────────────────────────────────────┤
│ │
1. DAILY DOWNLOAD (4:00 UTC)
└─→ Zone Files von Verisign, PIR, etc.
Das Konzept:
─────────────────────────────────────────────────────────────
│ 1. Zone Files von Verisign (.com/.net) beziehen │
│ 2. Tägliche Diff-Analyse (was war gestern da, ist heute weg) │
│ 3. Diese Domains droppen in 1-5 Tagen! │
│ 4. Pounce Algorithm filtert nur Premium-Domains │
│ │
2. DIFF ANALYSIS
└─→ Was war gestern da, ist heute weg?
└─→ Diese Domains DROPPEN in 1-5 Tagen!
3. POUNCE ALGORITHM
│ └─→ Nur Premium-Domains durchlassen (Score > 70) │
│ │
│ 4. OUTPUT: "Drops Tomorrow" (Tycoon Exclusive) │
│ └─→ Domains BEVOR sie in Auktionen erscheinen │
Warum das ein MONOPOL schafft:
─────────────────────────────────────────────────────────────
• ExpiredDomains zeigt ALLES (Spam-Hölle)
• Pounce zeigt nur die TOP 100 (kuratiert)
• = Zeitersparnis = Premium Feature = $29/Monat
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 🔧 TEIL 3: Technische Architektur
### Der Unified Feed API Endpoint
```python
# backend/app/api/auctions.py
@router.get("/feed")
async def get_market_feed(
source: str = Query("all", enum=["all", "pounce", "external"]),
keyword: Optional[str] = None,
tld: Optional[str] = None,
min_price: Optional[float] = None,
max_price: Optional[float] = None,
min_score: int = Query(0, ge=0, le=100),
ending_within: Optional[int] = None, # Stunden
verified_only: bool = False,
sort_by: str = Query("score", enum=["score", "price_asc", "price_desc", "time", "newest"]),
limit: int = Query(50, le=200),
offset: int = Query(0),
current_user: Optional[User] = Depends(get_current_user_optional),
):
"""
🚀 UNIFIED MARKET FEED — Das Herz von Pounce
Kombiniert:
- 💎 Pounce Direct: DNS-verifizierte User-Listings (Instant Buy)
- 🏢 External Auctions: Scraped von GoDaddy, Sedo, etc.
- 🔮 Drops: Domains die bald frei werden (Phase 3)
Für nicht-authentifizierte User:
- Vanity Filter aktiv (nur Premium-Domains)
- Pounce Score sichtbar, aber limited Details
Für authentifizierte User (Trader/Tycoon):
- Vollzugriff auf alle Domains
- Advanced Filtering
- Valuation Data
"""
```
### Pounce Score v2.0
```python
def calculate_pounce_score_v2(domain: str, auction_data: dict) -> int:
"""
Der Pounce Score — Qualitäts- und Opportunity-Bewertung
A) INTRINSIC VALUE (Domain selbst)
- Länge (kurz = wertvoll)
- TLD Premium (com > io > xyz)
- Dictionary Word Bonus
B) MARKET SIGNALS (Aktivität)
- Bid Activity (mehr Bids = mehr Interesse)
- Time Pressure (endet bald = Opportunity)
- Price-to-Value Ratio (unterbewertet = 🔥)
C) PENALTIES
- Bindestriche (-30)
- Zahlen wenn >3 Zeichen (-20)
- Zu lang >15 Zeichen (-25)
"""
score = 50 # Baseline
name = domain.rsplit('.', 1)[0]
tld = domain.rsplit('.', 1)[1]
# Länge
if len(name) <= 3: score += 30
elif len(name) == 4: score += 25
elif len(name) == 5: score += 20
elif len(name) <= 7: score += 10
# TLD
tld_scores = {'com': 20, 'ai': 25, 'io': 18, 'co': 12, 'ch': 15}
score += tld_scores.get(tld, 0)
# Market Signals
bids = auction_data.get('num_bids', 0)
if bids >= 20: score += 15
elif bids >= 10: score += 10
elif bids >= 5: score += 5
# Penalties
if '-' in name: score -= 30
if any(c.isdigit() for c in name) and len(name) > 3: score -= 20
return max(0, min(100, score))
```
**Status:** 🔜 Geplant (6-12 Monate)
---
## 📈 TEIL 4: Roadmap
### ✅ ERLEDIGT (Stand: 11. Dezember 2025)
- [x] Unified Feed API `/auctions/feed`
- [x] Pounce Score v2.0 mit Market Signals
- [x] Vanity Filter für Public Users
- [x] Pounce Direct Listing-System (DNS-Verifizierung)
- [x] Sniper Alerts mit Keyword-Matching
- [x] Web Scraping für 5 Plattformen
- [x] DropCatch API Client (für User-Integration)
- [x] Sedo API Client (bereit für Credentials)
### 🎯 NÄCHSTE SCHRITTE (Diese Woche)
1. **Sedo API Credentials eingeben**
- Sedo.com → Mein Sedo → API-Zugang
- Partner ID + SignKey in `.env`
2. **Erste Pounce Direct Listings erstellen**
- Test-Domains zum Verifizieren des Flows
- Zeigt "Unique Content" im Feed
3. **Scraper-Stabilität verbessern**
- Fallback-Logik testen
- Error-Handling optimieren
### 🔮 PHASE 3 (6-12 Monate)
1. **Zone File Access beantragen**
- Verisign (.com/.net)
- PIR (.org)
- Kosten: $0-$10,000/Jahr
2. **"Drops Tomorrow" Feature**
- Zone File Diff-Analyse
- Tycoon Exclusive ($29/mo)
3. **Pounce Instant Exchange**
- Integrierter Escrow-Service
- 5% Gebühr (statt 15-20% bei Konkurrenz)
---
## 🎨 TEIL 5: UI/UX Design
### Die Master-Tabelle (aus pounce_terminal.md)
| Spalte | Inhalt | Visualisierung |
|--------|--------|----------------|
| **Domain** | Name der Domain | Fettgedruckt. Bei "Pounce Direct" → 💎 Icon |
| **Pounce Score** | Qualitäts-Algorithmus | 0-100 (Grün > 80, Gelb 50-80, Rot < 50) |
| **Price / Bid** | Preis oder aktuelles Gebot | `$500` oder `$50 (Bid)` |
| **Status / Time** | Countdown oder Verfügbarkeit | `4h left` oder `Instant` |
| **Source** | Herkunft | 🏢 GoDaddy, 💎 Pounce |
| **Action** | Der Button | `[Bid ↗]` oder `[Buy Now]` |
## 🎨 UI/UX: Die Market Page
### Filter Bar
```
[Toggle] Hide Spam (Standard: AN)
[Toggle] Pounce Direct Only
[Dropdown] TLD: .com, .ai, .io, .ch
[Dropdown] Price: < $100, < $1k, High Roller
[Dropdown] Ending: 1h, 4h, 24h, 7d
[] Hide Spam [○] Pounce Only [TLD ▾] [Price ▾] [Ending ▾]
```
### Die Master-Tabelle
| Spalte | Inhalt | Visualisierung |
|--------|--------|----------------|
| **Domain** | Name | Fettgedruckt. 💎 Icon für Pounce Direct |
| **Score** | Pounce Score | 0-100 (Grün > 80, Gelb 50-80, Rot < 50) |
| **Price** | Preis/Gebot | `$500` oder `$50 (Bid)` |
| **Status** | Zeit/Verfügbarkeit | `4h left` oder `Instant` |
| **Source** | Herkunft | 🏢 GoDaddy, 💎 Pounce |
| **Action** | Button | `[Bid ↗]` oder `[Buy Now]` |
### Visuelle Hierarchie
```tsx
// Pounce Direct Items werden prominent angezeigt
{pounceDirectItems.length > 0 && (
<section className="mb-6">
<div className="flex items-center gap-2 text-emerald-400 font-bold mb-3">
<Diamond className="w-4 h-4" />
Pounce Exclusive Verified Instant Buy
</div>
{pounceDirectItems.map(item => <PounceDirectCard />)}
</section>
)}
// External Auctions darunter
<section>
<h2>Active Auctions</h2>
{externalItems.map(item => <AuctionCard />)}
</section>
```
---
## 💰 TEIL 6: Monetarisierung
### Tier-basierte Features (aus pounce_pricing.md)
| Feature | Scout ($0) | Trader ($9) | Tycoon ($29) |
|---------|------------|-------------|--------------|
| **Market Feed** | 🌪 Raw (Vanity Filter) | Curated (Clean) | Curated + Priority |
| **Alert Speed** | 🐢 Daily | 🐇 Hourly | Real-Time (10m) |
| **Watchlist** | 5 Domains | 50 Domains | 500 Domains |
| **Sell Domains** | | 5 Listings | 50 Listings + Featured |
| **Pounce Score** | Locked | Basic | + SEO Data |
| **Drops Tomorrow** | | | Exclusive |
### Die "Conversion-Falle" (aus pounce_features.md)
Wenn ein nicht-eingeloggter User auf "Buy Now" bei einem Pounce Direct Listing klickt:
```
┌─────────────────────────────────────────────────────────────────┐
🔒 Secure Transaction
MARKET FEED
├─────────────────────────────────────────────────────────────────┤
│ │
Du bist dabei, ein verifiziertes Direct-Listing anzusehen.
💎 POUNCE EXCLUSIVE — Verified Instant Buy
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ zurich-immo.ch $950 ⚡ Instant ✅ Verified [Buy] │ │
│ │ crypto-hub.io $2.5k ⚡ Instant ✅ Verified [Buy] │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
Um den Verkäufer zu kontaktieren und Käuferschutz zu
genießen, logge dich bitte ein.
🏢 LIVE AUCTIONS
┌───────────────────────────────────────────────────────────┐
│ │ techflow.io $250 ⏱️ 4h left GoDaddy [Bid ↗] │ │
│ │ datalab.com $1.2k ⏱️ 23h left Sedo [Bid ↗] │ │
│ │ nexus.ai $5k ⏱️ 2d left NameJet [Bid ↗] │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
[Login] [Create Free Scout Account]
🔮 DROPS TOMORROW (Tycoon Only)
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 🔒 Upgrade to Tycoon to see domains dropping tomorrow │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 🔑 TEIL 7: Kritische Erkenntnisse
## 💰 Monetarisierung (aus pounce_pricing.md)
### API-Realität vs. Erwartung
| API | Erwartung | Realität |
|-----|-----------|----------|
| **DropCatch** | Alle öffentlichen Auktionen | Nur eigene Bids/Backorders |
| **Sedo** | TBD | Credentials fehlen noch |
**Konsequenz:**
- Web Scraping bleibt unsere **Hauptquelle** für öffentliche Daten
- APIs sind nützlich für **User-Integration** (verbinde dein DropCatch-Konto)
- **Zone Files** sind der langfristige Weg zur Datenhoheit
### Der echte USP: Pounce Direct
> *"Domains die es NUR bei Pounce gibt."*
Das ist der Schlüssel. Nicht die Aggregation (das kann jeder), sondern der **Unique Content** durch User-Listings.
**Priorität:** Erste Pounce Direct Listings aktivieren!
| Feature | Scout ($0) | Trader ($9) | Tycoon ($29) |
|---------|------------|-------------|--------------|
| **Market Feed** | 🌪 Vanity Filter | Clean | Clean + Priority |
| **Alert Speed** | 🐢 Daily | 🐇 Hourly | Real-Time (10m) |
| **Watchlist** | 5 Domains | 50 Domains | 500 Domains |
| **Sell Domains** | | 5 Listings | 50 + Featured |
| **Pounce Score** | Locked | Basic | + SEO Data |
| **Drops Tomorrow** | | | Exclusive |
---
## 📋 Checkliste für den Launch
## ⚙️ Technische Architektur
### Backend
- [x] Unified Feed API
### Scheduler Jobs
```python
# Aktive Jobs (Scheduler)
# ─────────────────────────────────────────────────────────────────
# 1. Auction Scrape — Alle 2 Stunden
scheduler.add_job(scrape_auctions, CronTrigger(hour='*/2', minute=30))
# 2. Expired Cleanup — Alle 15 Minuten (KRITISCH!)
scheduler.add_job(cleanup_expired_auctions, CronTrigger(minute='*/15'))
# 3. Sniper Matching — Alle 30 Minuten
scheduler.add_job(match_sniper_alerts, CronTrigger(minute='*/30'))
# 4. TLD Prices — Täglich 03:00 UTC
scheduler.add_job(scrape_tld_prices, CronTrigger(hour=3))
```
### API Endpoints
```python
# Market Feed Endpoints
# ─────────────────────────────────────────────────────────────────
GET /api/v1/auctions/feed # Unified Feed (Pounce + External)
GET /api/v1/auctions # External Auctions only
GET /api/v1/auctions/ending-soon
GET /api/v1/auctions/hot
GET /api/v1/listings # Pounce Direct Listings
```
### Data Freshness Garantie
```python
# Jede Query filtert automatisch auf aktive Auktionen:
query = select(DomainAuction).where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > datetime.utcnow() # ← IMMER!
)
)
```
---
## 📈 Roadmap
### ✅ ERLEDIGT (11. Dezember 2025)
- [x] Unified Feed API `/auctions/feed`
- [x] Pounce Score v2.0
- [x] Vanity Filter
- [x] Scraper aktiv
- [ ] Sedo API Credentials eingeben
- [ ] Scheduler-Intervall optimieren
- [x] Web Scraping (5 Plattformen)
- [x] **FIX: end_time Filter** (nur laufende Auktionen)
- [x] **FIX: Cleanup alle 15 Minuten**
- [x] **FIX: Scraper alle 2 Stunden**
- [x] Sniper Alerts
### Frontend
- [x] Terminal Market Page
- [x] Public Auctions Page
- [x] Pounce Direct Highlighting
- [x] Filter (Source, TLD, Price)
- [ ] "Hot Right Now" Section
- [ ] Better Empty States
### 🎯 NÄCHSTE SCHRITTE (Diese Woche)
### Content
- [ ] Erste 5 Test-Listings erstellen
- [ ] DNS-Verifizierung testen
- [ ] Listing-to-Feed Flow validieren
1. **Erste Pounce Direct Listings erstellen**
- Test-Domains zum Verifizieren des Flows
- USP aktivieren!
2. **Sedo API Credentials eingeben**
- Sedo.com Mein Sedo API-Zugang
- Partner ID + SignKey in `.env`
3. **Frontend: "Live" Indikator**
- Zeige wann Daten zuletzt aktualisiert wurden
### 🔮 PHASE 2-3 (6-12 Monate)
1. **Zone File Access beantragen**
- Verisign (.com/.net)
- "Drops Tomorrow" Feature
2. **Pounce Instant Exchange**
- Integrierter Escrow-Service
- 5% Gebühr
---
## 💎 Fazit
## 🚀 Der Unicorn-Pfad
Die Market Page ist **funktional**, aber der wahre USP (Pounce Direct) ist noch nicht aktiviert.
```
Phase 1: INTELLIGENCE (Jetzt)
├── Pounce Direct aktivieren (Unique Content)
├── Clean Feed (aktuelle Daten, Spam-frei)
├── Trust aufbauen
└── 10.000 User, $1M ARR
**Die Reihenfolge:**
1. Aggregation funktioniert (Scraping)
2. Pounce Direct aktivieren (User-Listings)
3. 🔮 Zone Files für Datenhoheit (Phase 3)
Phase 2: LIQUIDITÄT (18-36 Monate)
├── Pounce Instant Exchange
├── Buy Now im Dashboard
├── 5% Gebühr
└── $10M ARR
> *"Der Weg zum Unicorn führt nicht über besseres Scraping, sondern über einzigartigen Content."*
>
> — pounce_strategy.md
Phase 3: FINANZIALISIERUNG (3-5 Jahre)
├── Fractional Ownership
├── Domain-Backed Lending
└── = FINTECH ($50-100M ARR)
Phase 4: IMPERIUM (5+ Jahre)
├── Enterprise Sentinel (B2B)
├── Fortune 500 Kunden
└── = $1 Mrd. Bewertung
```
---
## 💎 Das Fazit
**Aggregation ist Commodity. Pounce Direct ist der USP.**
Der Weg zum Unicorn:
1. Datenqualität (aktuelle, saubere Daten)
2. Unique Content (Pounce Direct aktivieren!)
3. 🔮 Datenhoheit (Zone Files)
> *"Don't guess. Know."*
>
> — Phase 1: Intelligence

View File

@ -277,8 +277,14 @@ async def search_auctions(
- Look for value_ratio > 1.0 (estimated value exceeds current bid)
- Focus on auctions ending soon with low bid counts
"""
# Build query
query = select(DomainAuction).where(DomainAuction.is_active == True)
# Build query - ONLY show active auctions that haven't ended yet
now = datetime.utcnow()
query = select(DomainAuction).where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > now # ← KRITISCH: Nur Auktionen die noch laufen!
)
)
# VANITY FILTER: For public (non-logged-in) users, only show premium-looking domains
# This ensures the first impression is high-quality, not spam domains
@ -457,9 +463,15 @@ async def get_hot_auctions(
Data is scraped from public auction sites - no mock data.
"""
now = datetime.utcnow()
query = (
select(DomainAuction)
.where(DomainAuction.is_active == True)
.where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > now # Only show active auctions
)
)
.order_by(DomainAuction.num_bids.desc())
.limit(limit)
)
@ -996,7 +1008,13 @@ async def get_market_feed(
# 2. EXTERNAL AUCTIONS (Scraped from platforms)
# ═══════════════════════════════════════════════════════════════
if source in ["all", "external"]:
auction_query = select(DomainAuction).where(DomainAuction.is_active == True)
now = datetime.utcnow()
auction_query = select(DomainAuction).where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > now # ← KRITISCH: Nur laufende Auktionen!
)
)
if keyword:
auction_query = auction_query.where(

View File

@ -204,12 +204,30 @@ def setup_scheduler():
replace_existing=True,
)
# Auction scrape every hour (at :30 to avoid conflict with other jobs)
# Auction scrape every 2 hours (at :30 to avoid conflict with other jobs)
scheduler.add_job(
scrape_auctions,
CronTrigger(minute=30), # Every hour at :30
id="hourly_auction_scrape",
name="Hourly Auction Scrape",
CronTrigger(hour='*/2', minute=30), # Every 2 hours at :30
id="auction_scrape",
name="Auction Scrape (2h)",
replace_existing=True,
)
# Cleanup expired auctions every 15 minutes (CRITICAL for data freshness!)
scheduler.add_job(
cleanup_expired_auctions,
CronTrigger(minute='*/15'), # Every 15 minutes
id="auction_cleanup",
name="Expired Auction Cleanup (15m)",
replace_existing=True,
)
# Sniper alert matching every 30 minutes
scheduler.add_job(
match_sniper_alerts,
CronTrigger(minute='*/30'), # Every 30 minutes
id="sniper_matching",
name="Sniper Alert Matching (30m)",
replace_existing=True,
)
@ -220,7 +238,9 @@ def setup_scheduler():
f"\n - Tycoon domain check every 10 minutes"
f"\n - TLD price scrape at 03:00 UTC"
f"\n - Price change alerts at 04:00 UTC"
f"\n - Auction scrape every hour at :30"
f"\n - Auction scrape every 2 hours at :30"
f"\n - Expired auction cleanup every 15 minutes"
f"\n - Sniper alert matching every 30 minutes"
)
@ -302,6 +322,58 @@ async def check_price_changes():
logger.exception(f"Price change check failed: {e}")
async def cleanup_expired_auctions():
"""
Mark expired auctions as inactive and delete very old ones.
This is CRITICAL for data freshness! Without this, the Market page
would show auctions that ended days ago.
Runs every 15 minutes to ensure users always see live data.
"""
from app.models.auction import DomainAuction
from sqlalchemy import update, delete
logger.info("Starting expired auction cleanup...")
try:
async with AsyncSessionLocal() as db:
now = datetime.utcnow()
# 1. Mark ended auctions as inactive
stmt = (
update(DomainAuction)
.where(
and_(
DomainAuction.end_time < now,
DomainAuction.is_active == True
)
)
.values(is_active=False)
)
result = await db.execute(stmt)
marked_inactive = result.rowcount
# 2. Delete very old inactive auctions (> 7 days)
cutoff = now - timedelta(days=7)
stmt = delete(DomainAuction).where(
and_(
DomainAuction.is_active == False,
DomainAuction.end_time < cutoff
)
)
result = await db.execute(stmt)
deleted = result.rowcount
await db.commit()
if marked_inactive > 0 or deleted > 0:
logger.info(f"Auction cleanup: {marked_inactive} marked inactive, {deleted} deleted")
except Exception as e:
logger.exception(f"Auction cleanup failed: {e}")
async def scrape_auctions():
"""Scheduled task to scrape domain auctions from public sources."""
from app.services.auction_scraper import auction_scraper