Competitive set analysis
Intermediate
Build a competitive set around your listing from all collected listings and see where your rate sits.
Setup
This tutorial uses the jokerprice.py helper from the quickstart. Save it next to your script and set JOKERPRICE_API_KEY.
Define your listing
mine = {"latitude": 24.8094, "longitude": 46.6112, "bedrooms": 2, "typeId": "6", "nightly": 450}
CITY, RADIUS_KM = "riyadh", 2.0Fetch the city's listings
all_listings follows nextCursor at 500 listings per request. Save the result so you can rerun the analysis without spending requests.
import json, os
from jokerprice import all_listings
cache = f"{CITY}-listings.json"
if os.path.exists(cache):
listings = json.load(open(cache))
else:
listings = all_listings(CITY)
json.dump(listings, open(cache, "w"))
print(len(listings), "listings")Filter to true competitors
from math import radians, sin, cos, asin, sqrt
def km(a_lat, a_lng, b_lat, b_lng):
d_lat, d_lng = radians(b_lat - a_lat), radians(b_lng - a_lng)
h = sin(d_lat / 2) ** 2 + cos(radians(a_lat)) * cos(radians(b_lat)) * sin(d_lng / 2) ** 2
return 6371 * 2 * asin(sqrt(h))
comps = []
for l in listings:
if l.get("latitude") is None or not l.get("nightly"):
continue
if l.get("bedrooms") != mine["bedrooms"] or str(l.get("typeId")) != mine["typeId"]:
continue
d = km(mine["latitude"], mine["longitude"], l["latitude"], l["longitude"])
if d <= RADIUS_KM:
comps.append({**l, "km": d})
print(len(comps), "comparables")Find your position
from statistics import quantiles, median
rates = sorted(c["nightly"] for c in comps)
p25, p50, p75 = quantiles(rates, n=4)
below = sum(r < mine["nightly"] for r in rates)
print(f"Comp set: 25th SAR {p25:.0f} | median SAR {p50:.0f} | 75th SAR {p75:.0f}")
print(f"Your SAR {mine['nightly']} is above {below / len(rates):.0%} of comparables")
rated = [c for c in comps if c.get("rating")]
top = [c["nightly"] for c in rated if c["rating"] >= 9.5]
if top:
print(f"Median rate of comps rated 9.5+: SAR {median(top):.0f} ({len(top)} listings)")quantiles needs at least two comparables; widen the radius if you have fewer.
Review the nearest competitors
for c in sorted(comps, key=lambda c: c["km"])[:15]:
print(f"{c['km']:.2f} km SAR {c['nightly']:>5} rating {c.get('rating') or '-':>4} reviews {c.get('reviews') or 0:>4} {c['source']:<7} {c.get('district') or ''}")Add booking performance
Asking rates do not tell you what books. /api/v1/metrics accepts the same filters as a map rectangle, so you can read occupancy and ADR for the area around your listing.
from jokerprice import api
d = 0.02 # about 2 km
m = api("GET", "/api/v1/metrics", params={"north": mine["latitude"] + d, "south": mine["latitude"] - d,
"east": mine["longitude"] + d, "west": mine["longitude"] - d, "past_days": 60})["metrics"]
print(m["maturity"], "occupancy", m["past"]["occupancy"], "ADR", m["past"]["adr"], "tracked", m["listings"]["tracked"])