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>
This commit is contained in:
pselamy
2026-06-14 18:18:29 +00:00
parent ff678468e3
commit e6bf1dafaf
4 changed files with 277 additions and 267 deletions
+41 -12
View File
@@ -84,7 +84,10 @@ class TestTradeStreamHandler:
"""Test building subscription message without filters."""
msg = handler._build_subscription_message()
assert msg == {"subscriptions": [{"topic": "activity", "type": "trades"}]}
assert msg == {
"action": "subscribe",
"subscriptions": [{"topic": "activity", "type": "trades"}],
}
def test_build_subscription_message_with_event_filter(self, on_trade_mock: AsyncMock) -> None:
"""Test building subscription message with event filter."""
@@ -110,11 +113,10 @@ class TestTradeStreamHandler:
async def test_handle_message_trade(
self, handler: TradeStreamHandler, on_trade_mock: AsyncMock
) -> None:
"""Test handling a valid trade message."""
"""Test handling a valid trade message (payload-based routing)."""
message = json.dumps(
{
"topic": "activity",
"type": "trades",
"connection_id": "abc123",
"payload": {
"conditionId": "0xmarket",
"transactionHash": "0xtx",
@@ -142,11 +144,10 @@ class TestTradeStreamHandler:
async def test_handle_message_non_trade(
self, handler: TradeStreamHandler, on_trade_mock: AsyncMock
) -> None:
"""Test handling a non-trade message."""
"""Test handling a non-trade message (no transactionHash/proxyWallet)."""
message = json.dumps(
{
"topic": "comments",
"type": "comment_created",
"connection_id": "abc123",
"payload": {"body": "Hello"},
}
)
@@ -156,6 +157,35 @@ class TestTradeStreamHandler:
on_trade_mock.assert_not_called()
assert handler.stats.trades_received == 0
@pytest.mark.asyncio
async def test_handle_message_payload_missing_proxy_wallet(
self, handler: TradeStreamHandler, on_trade_mock: AsyncMock
) -> None:
"""Ratchet: payload with transactionHash but no proxyWallet is not a trade."""
message = json.dumps(
{
"connection_id": "abc",
"payload": {"transactionHash": "0xtx", "other": "field"},
}
)
await handler._handle_message(message)
on_trade_mock.assert_not_called()
assert handler.stats.trades_received == 0
@pytest.mark.asyncio
async def test_handle_message_no_payload_key(
self, handler: TradeStreamHandler, on_trade_mock: AsyncMock
) -> None:
"""Ratchet: message without payload key is ignored."""
message = json.dumps({"connection_id": "abc", "status": "ok"})
await handler._handle_message(message)
on_trade_mock.assert_not_called()
assert handler.stats.trades_received == 0
@pytest.mark.asyncio
async def test_handle_message_invalid_json(
self, handler: TradeStreamHandler, on_trade_mock: AsyncMock
@@ -175,8 +205,7 @@ class TestTradeStreamHandler:
message = json.dumps(
{
"topic": "activity",
"type": "trades",
"connection_id": "abc",
"payload": {
"conditionId": "0x",
"transactionHash": "0x",
@@ -240,8 +269,9 @@ class TestTradeStreamHandler:
assert ws is mock_ws
mock_ws.send.assert_called_once()
# Verify subscription message
# Verify subscription message includes action: subscribe
sent_msg = json.loads(mock_ws.send.call_args[0][0])
assert sent_msg["action"] == "subscribe"
assert "subscriptions" in sent_msg
assert sent_msg["subscriptions"][0]["topic"] == "activity"
assert sent_msg["subscriptions"][0]["type"] == "trades"
@@ -292,8 +322,7 @@ class TestTradeStreamHandlerIntegration:
trade_message = json.dumps(
{
"topic": "activity",
"type": "trades",
"connection_id": "test-conn",
"payload": {
"conditionId": "0xtest",
"transactionHash": "0xtx",