feat: Implement Telegram push notifications for weather and market conditions and a Polymarket read-only data interface.
This commit is contained in:
@@ -813,11 +813,10 @@ class PolymarketReadOnlyLayer:
|
||||
if not city_hit:
|
||||
return 0.0
|
||||
|
||||
score = 40.0
|
||||
|
||||
weather_hit = any(_contains_token(haystack, keyword) for keyword in WEATHER_KEYWORDS)
|
||||
if not weather_hit:
|
||||
if not self._is_temperature_market(market):
|
||||
return 0.0
|
||||
|
||||
score = 40.0
|
||||
score += 18.0
|
||||
|
||||
d_target = _parse_target_date(target_date)
|
||||
@@ -874,6 +873,42 @@ class PolymarketReadOnlyLayer:
|
||||
score += min(volume / 50000.0, 8.0)
|
||||
return score
|
||||
|
||||
def _is_temperature_market(self, market: Dict[str, Any]) -> bool:
|
||||
text_parts = [
|
||||
market.get("question"),
|
||||
market.get("title"),
|
||||
market.get("slug"),
|
||||
market.get("eventSlug"),
|
||||
market.get("description"),
|
||||
]
|
||||
raw_text = " ".join(str(part or "") for part in text_parts)
|
||||
if not raw_text:
|
||||
return False
|
||||
|
||||
# Hard signal: contains explicit Celsius bucket text like "10C" / "10°C"
|
||||
if re.search(r"(-?\d+(?:\.\d+)?)\s*[°º]?\s*c\b", raw_text, re.IGNORECASE):
|
||||
return True
|
||||
|
||||
text = _normalize_text(raw_text)
|
||||
if not text:
|
||||
return False
|
||||
|
||||
# Weather temperature event patterns.
|
||||
if "highest temperature" in text:
|
||||
return True
|
||||
if "temperature in" in text:
|
||||
return True
|
||||
if "high temperature" in text:
|
||||
return True
|
||||
|
||||
# Conservative fallback: must explicitly mention temperature and boundary wording.
|
||||
if "temperature" in text and any(
|
||||
key in text for key in ("or higher", "or above", "or lower", "or below", "and above", "and below")
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _extract_market_date(self, market: Dict[str, Any]) -> Optional[str]:
|
||||
for key in (
|
||||
"endDate",
|
||||
@@ -1221,6 +1256,10 @@ class PolymarketReadOnlyLayer:
|
||||
]
|
||||
] = []
|
||||
for market in candidate_markets:
|
||||
bucket_temp = self._extract_market_bucket_temp(market)
|
||||
if bucket_temp is None:
|
||||
continue
|
||||
|
||||
tokens = self._extract_market_tokens(market)
|
||||
yes_token, no_token = self._resolve_yes_no_tokens(tokens)
|
||||
if not yes_token or not no_token:
|
||||
@@ -1289,7 +1328,6 @@ class PolymarketReadOnlyLayer:
|
||||
if no_sell is None and yes_sell is not None:
|
||||
no_sell = max(0.0, min(1.0, 1.0 - yes_sell))
|
||||
|
||||
bucket_temp = self._extract_market_bucket_temp(market)
|
||||
market_slug = str(market.get("slug") or "").strip()
|
||||
|
||||
top_rows.append(
|
||||
|
||||
@@ -141,7 +141,7 @@ def _market_price_cap_ok(alert_payload: Dict[str, Any], max_yes_buy: float) -> b
|
||||
if not isinstance(market, dict) or not market.get("available"):
|
||||
return True
|
||||
|
||||
# Prefer the market bucket that maps to Open-Meteo forecast settlement.
|
||||
# Strict rule: use the bucket mapped from Open-Meteo settlement.
|
||||
forecast_bucket = market.get("forecast_bucket") or {}
|
||||
yes_buy = None
|
||||
bucket_label = None
|
||||
@@ -149,27 +149,14 @@ def _market_price_cap_ok(alert_payload: Dict[str, Any], max_yes_buy: float) -> b
|
||||
yes_buy = _norm_prob(forecast_bucket.get("yes_buy"))
|
||||
bucket_label = str(forecast_bucket.get("label") or "").strip() or None
|
||||
|
||||
# Backward-compatible fallback.
|
||||
if yes_buy is None:
|
||||
yes_buy = _norm_prob(market.get("yes_buy"))
|
||||
if not bucket_label:
|
||||
bucket_label = str(market.get("selected_bucket") or "").strip() or None
|
||||
|
||||
if yes_buy is None:
|
||||
# Fallback to first bucket with valid yes_buy if aggregate field is missing.
|
||||
top_rows = market.get("top_bucket_rows") or []
|
||||
if isinstance(top_rows, list):
|
||||
for row in top_rows:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
yes_buy = _norm_prob(row.get("yes_buy"))
|
||||
if yes_buy is not None:
|
||||
if not bucket_label:
|
||||
bucket_label = str(row.get("label") or "").strip() or None
|
||||
break
|
||||
|
||||
if yes_buy is None:
|
||||
return True
|
||||
logger.info(
|
||||
"trade alert skipped: no mapped forecast bucket city={} om_settle={}".format(
|
||||
alert_payload.get("city"),
|
||||
market.get("open_meteo_settlement"),
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
if yes_buy >= max_yes_buy:
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user