fix(ingestor): align WebSocket subscribe + routing with live API (#105)
* fix(ingestor): align WebSocket subscribe + routing with live API
The Polymarket ws-live-data WebSocket requires `action: "subscribe"` in
the subscribe envelope. Without it the server accepts the connection but
never delivers trade events, causing the tracker to silently produce
zero alerts.
Additionally, incoming frames are shaped `{connection_id, payload:{...}}`
-- they do NOT echo the `topic`/`type` keys we sent. The previous routing
check matched nothing and every real trade was silently dropped.
Changes:
- Add `action: "subscribe"` to subscription message
- Route incoming messages by payload shape (transactionHash + proxyWallet)
- Add ratchet tests for payload routing edge cases
- Rewrite README as agent-first with <2min quickstart
- Add skill draft (docs/skill-tracking-prediction-market-flow.md)
Closes #89
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(lint): remove unused imports in test_pipeline_persistence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: apply ruff formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ff678468e3
commit
b962bdaee2
@@ -18,7 +18,9 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
class DatabaseSettings(BaseSettings):
|
||||
"""Database connection settings."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="", env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="", env_file=".env", env_file_encoding="utf-8", extra="ignore"
|
||||
)
|
||||
|
||||
url: str = Field(
|
||||
alias="DATABASE_URL",
|
||||
@@ -37,7 +39,9 @@ class DatabaseSettings(BaseSettings):
|
||||
class RedisSettings(BaseSettings):
|
||||
"""Redis connection settings."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="", env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="", env_file=".env", env_file_encoding="utf-8", extra="ignore"
|
||||
)
|
||||
|
||||
url: str = Field(
|
||||
default="redis://localhost:6379",
|
||||
@@ -57,7 +61,9 @@ class RedisSettings(BaseSettings):
|
||||
class PolygonSettings(BaseSettings):
|
||||
"""Polygon blockchain RPC settings."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="POLYGON_", env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="POLYGON_", env_file=".env", env_file_encoding="utf-8", extra="ignore"
|
||||
)
|
||||
|
||||
rpc_url: str = Field(
|
||||
default="https://polygon-rpc.com",
|
||||
@@ -84,7 +90,9 @@ class PolygonSettings(BaseSettings):
|
||||
class PolymarketSettings(BaseSettings):
|
||||
"""Polymarket API settings."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="POLYMARKET_", env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="POLYMARKET_", env_file=".env", env_file_encoding="utf-8", extra="ignore"
|
||||
)
|
||||
|
||||
ws_url: str = Field(
|
||||
default="wss://ws-subscriptions-clob.polymarket.com/ws/market",
|
||||
@@ -109,7 +117,9 @@ class PolymarketSettings(BaseSettings):
|
||||
class DiscordSettings(BaseSettings):
|
||||
"""Discord notification settings."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="DISCORD_", env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="DISCORD_", env_file=".env", env_file_encoding="utf-8", extra="ignore"
|
||||
)
|
||||
|
||||
webhook_url: SecretStr | None = Field(
|
||||
default=None,
|
||||
@@ -126,7 +136,9 @@ class DiscordSettings(BaseSettings):
|
||||
class TelegramSettings(BaseSettings):
|
||||
"""Telegram notification settings."""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="TELEGRAM_", env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="TELEGRAM_", env_file=".env", env_file_encoding="utf-8", extra="ignore"
|
||||
)
|
||||
|
||||
bot_token: SecretStr | None = Field(
|
||||
default=None,
|
||||
@@ -142,7 +154,12 @@ class TelegramSettings(BaseSettings):
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
"""Check if Telegram notifications are enabled."""
|
||||
return self.bot_token is not None and bool(self.bot_token.get_secret_value().strip()) and self.chat_id is not None and bool(self.chat_id.strip())
|
||||
return (
|
||||
self.bot_token is not None
|
||||
and bool(self.bot_token.get_secret_value().strip())
|
||||
and self.chat_id is not None
|
||||
and bool(self.chat_id.strip())
|
||||
)
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
||||
@@ -150,7 +150,7 @@ class TradeStreamHandler:
|
||||
elif self._market_filter:
|
||||
subscription["filters"] = json.dumps({"market_slug": self._market_filter})
|
||||
|
||||
return {"subscriptions": [subscription]}
|
||||
return {"action": "subscribe", "subscriptions": [subscription]}
|
||||
|
||||
async def _connect(self) -> ClientConnection:
|
||||
"""Establish WebSocket connection."""
|
||||
@@ -183,12 +183,13 @@ class TradeStreamHandler:
|
||||
try:
|
||||
data = json.loads(message)
|
||||
|
||||
# Check if this is a trade message
|
||||
topic = data.get("topic")
|
||||
msg_type = data.get("type")
|
||||
|
||||
if topic == "activity" and msg_type == "trades":
|
||||
payload = data.get("payload", {})
|
||||
# ws-live-data pushes {connection_id, payload:{...trade fields}}
|
||||
payload = data.get("payload")
|
||||
if (
|
||||
isinstance(payload, dict)
|
||||
and "transactionHash" in payload
|
||||
and "proxyWallet" in payload
|
||||
):
|
||||
trade = TradeEvent.from_websocket_message(payload)
|
||||
|
||||
self._stats.trades_received += 1
|
||||
@@ -208,8 +209,7 @@ class TradeStreamHandler:
|
||||
logger.error("Error in trade callback: %s", e)
|
||||
|
||||
else:
|
||||
# Log other message types for debugging
|
||||
logger.debug("Received message: topic=%s type=%s", topic, msg_type)
|
||||
logger.debug("Received non-trade message: %s", str(data)[:120])
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning("Invalid JSON message: %s", e)
|
||||
|
||||
Reference in New Issue
Block a user