Compare commits

..

1 Commits

Author SHA1 Message Date
pselamy e5d6194947 fix: resolve .env.example variable interpolation and alembic env var mismatch (#62)
.env files don't support shell variable expansion, so DATABASE_URL and
REDIS_URL contained literal ${...} strings. Also alembic/env.py read
SQLALCHEMY_DATABASE_URL instead of DATABASE_URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:04:42 +00:00
8 changed files with 42 additions and 39 deletions
+25 -17
View File
@@ -126,13 +126,13 @@ docker compose up -d
docker compose ps
# Install Python dependencies
uv sync --all-extras
pip install -e .
# Run database migrations
uv run alembic upgrade head
alembic upgrade head
# Run the tracker
uv run python -m polymarket_insider_tracker
python -m src.main
```
### Docker Services
@@ -188,16 +188,24 @@ LIQUIDITY_IMPACT_THRESHOLD=0.02
```
polymarket-insider-tracker/
├── src/
── polymarket_insider_tracker/
├── __main__.py # CLI entry point
── pipeline.py # Core detection pipeline
├── ingestor/ # Real-time market data ingestion
│ ├── clob_client.py # Polymarket CLOB API wrapper
── websocket.py # WebSocket event handler
── profiler/ # Wallet analysis
├── detector/ # Anomaly detection engines
├── alerter/ # Notification dispatch
── storage/ # Persistence layer
── ingestor/ # Real-time market data ingestion
├── clob_client.py # Polymarket CLOB API wrapper
── websocket.py # WebSocket event handler
├── profiler/ # Wallet analysis
│ │ ├── analyzer.py # Core wallet profiling logic
│ │ ── chain.py # Polygon blockchain client
── funding.py # Funding chain tracer
│ ├── detector/ # Anomaly detection engines
├── fresh_wallet.py
── size_anomaly.py
│ │ ├── sniper.py # DBSCAN clustering
│ │ └── scorer.py # Composite risk scoring
│ ├── alerter/ # Notification dispatch
│ │ ├── formatter.py # Alert message formatting
│ │ └── dispatcher.py # Multi-channel delivery
│ └── storage/ # Persistence layer
│ ├── models.py # SQLAlchemy models
│ └── repos.py # Repository pattern
├── tests/ # Test suite
├── scripts/
│ └── backtest.py # Historical analysis
@@ -281,16 +289,16 @@ Contributions are welcome! Please read our Contributing Guide before submitting
```bash
# Install dev dependencies
uv sync --all-extras
pip install -e ".[dev]"
# Run tests
uv run pytest
pytest
# Run linting
uv run ruff check src/
ruff check src/
# Run type checking
uv run mypy src/
mypy src/
```
---
@@ -5,16 +5,16 @@ Revises:
Create Date: 2026-01-04 00:00:00.000000+00:00
"""
from collections.abc import Sequence
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "001_initial"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
+2
View File
@@ -1,3 +1,5 @@
version: "3.8"
services:
postgres:
image: postgres:15
@@ -11,7 +11,7 @@ import logging
from collections.abc import Callable
from dataclasses import dataclass
from datetime import UTC, datetime
from enum import StrEnum
from enum import Enum
from redis.asyncio import Redis
@@ -27,7 +27,7 @@ DEFAULT_CACHE_TTL_SECONDS = 600 # 10 minutes
DEFAULT_REDIS_KEY_PREFIX = "polymarket:market:"
class SyncState(StrEnum):
class SyncState(str, Enum):
"""State of the metadata synchronizer."""
STOPPED = "stopped"
@@ -86,7 +86,7 @@ class Orderbook:
bids: tuple[OrderbookLevel, ...]
asks: tuple[OrderbookLevel, ...]
tick_size: Decimal
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
timestamp: datetime = field(default_factory=datetime.utcnow)
@classmethod
def from_clob_orderbook(cls, orderbook: Any) -> "Orderbook":
@@ -9,9 +9,8 @@ from dataclasses import dataclass
from enum import Enum
from typing import Any
import websockets
from websockets.asyncio.client import ClientConnection
from websockets.asyncio.client import connect as ws_connect
from websockets.exceptions import ConnectionClosed
from polymarket_insider_tracker.ingestor.models import TradeEvent
@@ -157,7 +156,7 @@ class TradeStreamHandler:
await self._set_state(ConnectionState.CONNECTING)
try:
ws = await ws_connect(
ws = await websockets.connect(
self._host,
ping_interval=self._ping_interval,
ping_timeout=self._ping_interval * 2,
@@ -228,7 +227,7 @@ class TradeStreamHandler:
else:
logger.debug("Received binary message (%d bytes)", len(message))
except ConnectionClosed as e:
except websockets.ConnectionClosed as e:
logger.warning("Connection closed: %s", e)
raise
except Exception as e:
@@ -285,7 +284,7 @@ class TradeStreamHandler:
while self._running:
try:
await self._listen(self._ws)
except (ConnectionClosed, Exception) as e:
except (websockets.ConnectionClosed, Exception) as e:
if not self._running:
break
+2 -2
View File
@@ -11,7 +11,7 @@ import contextlib
import logging
from dataclasses import dataclass
from datetime import UTC, datetime
from enum import StrEnum
from enum import Enum
from typing import TYPE_CHECKING
from redis.asyncio import Redis
@@ -43,7 +43,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
class PipelineState(StrEnum):
class PipelineState(str, Enum):
"""Pipeline lifecycle states."""
STOPPED = "stopped"
+2 -8
View File
@@ -231,10 +231,7 @@ class TestTradeStreamHandler:
mock_ws = AsyncMock()
mock_ws.send = AsyncMock()
with patch(
"polymarket_insider_tracker.ingestor.websocket.ws_connect",
AsyncMock(return_value=mock_ws),
):
with patch("websockets.connect", AsyncMock(return_value=mock_ws)):
ws = await handler._connect()
assert ws is mock_ws
@@ -336,10 +333,7 @@ class TestTradeStreamHandlerIntegration:
mock_ws = MockWebSocket(handler, trade_message)
with patch(
"polymarket_insider_tracker.ingestor.websocket.ws_connect",
AsyncMock(return_value=mock_ws),
):
with patch("websockets.connect", AsyncMock(return_value=mock_ws)):
# Run with timeout to prevent hanging
try:
await asyncio.wait_for(handler.start(), timeout=1.0)