Compare commits

...

29 Commits

Author SHA1 Message Date
pselamy de5e2592e8 docs: fix README to match actual project structure and tooling
- Replace pip commands with uv equivalents
- Fix run command from 'python -m src.main' to 'python -m polymarket_insider_tracker'
- Update project structure tree to reflect actual src/polymarket_insider_tracker/ layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 02:02:52 +00:00
pselamy c6f41ea29f fix: remove deprecated version key from docker-compose.yml
The top-level 'version' key is obsolete in modern Docker Compose
and produces a warning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 02:02:50 +00:00
pselamy 9e46f17538 fix: resolve ruff lint errors in alembic migration
- Replace typing.Union with X | Y syntax (UP007)
- Import Sequence from collections.abc instead of typing (UP035)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 02:02:48 +00:00
pselamy 6839e7dc47 fix: replace deprecated datetime.utcnow() and websockets.legacy APIs
- Replace datetime.utcnow() with datetime.now(UTC) in Orderbook model
- Use websockets.asyncio.client.connect instead of legacy websockets.connect
- Import ConnectionClosed from websockets.exceptions directly
- Update test mocks to patch the new import paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 02:02:44 +00:00
Patrick Selamy 7c494c38a6 fix: replace (str, Enum) with StrEnum to resolve ruff UP042 (#87)
StrEnum (Python 3.11+) is the modern replacement for the (str, Enum)
pattern. Updates SyncState and PipelineState enums.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:17:51 -04:00
Patrick Selamy 17504a5eaa fix: replace (str, Enum) with StrEnum to resolve ruff UP042 (#86)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:16:23 -04:00
Patrick Selamy cadc593d62 fix: replace (str, Enum) with StrEnum to resolve ruff UP042 (#85)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:14:36 -04:00
Patrick Selamy 0320a96f8a fix: replace (str, Enum) with StrEnum (ruff UP042) (#84)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:12:59 -04:00
Patrick Selamy 753d6cf1de fix: replace (str, Enum) with StrEnum (ruff UP042) (#83)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:11:19 -04:00
Patrick Selamy f3801987fe fix: resolve .env.example variable interpolation and alembic env var mismatch (#62) (#82)
.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-08 20:05:00 -04:00
Patrick Selamy 5cbcaccd82 fix: resolve alembic migration failures from .env and env var mismatch (#81)
- Replace shell variable interpolation in .env.example with literal
  values since .env files don't support variable expansion
- Change alembic/env.py to read DATABASE_URL (matching app convention)
  instead of SQLALCHEMY_DATABASE_URL

Closes #62

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:03:58 -04:00
Patrick Selamy 288fbc75cf fix: resolve broken alembic migrations (#62) (#80)
- Replace shell variable interpolation in .env.example with literal values
- Change alembic/env.py to read DATABASE_URL instead of SQLALCHEMY_DATABASE_URL

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:02:53 -04:00
Patrick Selamy 37ea0c6540 fix: resolve broken alembic migrations (#62) (#79)
- Replace shell variable interpolation in .env.example with literal values
- Change alembic/env.py to read DATABASE_URL instead of SQLALCHEMY_DATABASE_URL

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:02:02 -04:00
Patrick Selamy 654f4466f0 fix: resolve broken alembic migrations (#62) (#78)
- Replace shell variable interpolation in .env.example with literal
  values since .env files don't expand variables
- Change alembic/env.py to read DATABASE_URL instead of
  SQLALCHEMY_DATABASE_URL to match the app's env var name

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:00:56 -04:00
Patrick Selamy 8628e3aadb fix: use literal values in .env.example and correct env var name in alembic (#77)
.env files don't support shell variable interpolation, so DATABASE_URL
and REDIS_URL had literal ${VAR} strings instead of actual values.
Also alembic/env.py read SQLALCHEMY_DATABASE_URL instead of DATABASE_URL.

Closes #62

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:59:46 -04:00
Patrick Selamy d808819990 fix: resolve alembic migration setup bugs (#62) (#76)
- Replace shell variable interpolation in .env.example with literal
  default values (.env files don't expand variables)
- Change alembic/env.py to read DATABASE_URL instead of
  SQLALCHEMY_DATABASE_URL to match the app's convention

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:58:34 -04:00
Patrick Selamy 20b8ad992c fix: resolve alembic migration setup bugs (#62) (#75)
- Replace shell variable interpolation in .env.example with literal
  default values since .env files don't expand variables
- Change alembic/env.py to read DATABASE_URL instead of
  SQLALCHEMY_DATABASE_URL to match the app's env var name

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:57:33 -04:00
Patrick Selamy bd320f1a8e fix: resolve alembic migration setup bugs (#62) (#74)
- Replace shell variable interpolation in .env.example with literal
  values (.env files don't expand variables)
- Change alembic/env.py to read DATABASE_URL instead of
  SQLALCHEMY_DATABASE_URL to match app configuration

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:56:30 -04:00
Patrick Selamy c65ed2225f fix: resolve alembic migration setup bugs (#62) (#73)
- Replace shell variable interpolation in .env.example with literal
  values (.env files don't expand variables)
- Change alembic/env.py to read DATABASE_URL instead of
  SQLALCHEMY_DATABASE_URL to match app convention

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:55:24 -04:00
Patrick Selamy 06ff48117c fix: use literal values in .env.example and correct env var name in alembic (#72)
.env files don't support shell variable interpolation, so DATABASE_URL
and REDIS_URL had literal ${VAR} strings instead of actual values.
Also alembic/env.py was reading SQLALCHEMY_DATABASE_URL instead of
DATABASE_URL, which is what the app and .env use.

Closes #62

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:54:15 -04:00
Patrick Selamy 34393e7ee3 fix: resolve alembic migration failures from env config bugs (#62) (#71)
- Replace shell variable interpolation in .env.example with literal
  values (.env files don't expand ${VAR} syntax)
- Change alembic/env.py to read DATABASE_URL instead of
  SQLALCHEMY_DATABASE_URL to match the app's env var name

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:53:02 -04:00
Patrick Selamy cf66aac9e3 fix: resolve alembic migration failures from env config bugs (#62) (#70)
- Replace shell variable interpolation in .env.example with literal
  values (.env files don't expand ${VAR} syntax)
- Change alembic/env.py to read DATABASE_URL instead of
  SQLALCHEMY_DATABASE_URL to match the app's env var name

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:52:16 -04:00
Patrick Selamy 13d5945b01 fix: resolve alembic migration failures from env config bugs (#62) (#69)
- Replace shell variable interpolation in .env.example with literal values
- Fix alembic/env.py to read DATABASE_URL instead of SQLALCHEMY_DATABASE_URL

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:51:13 -04:00
Patrick Selamy 7352143004 fix: resolve two bugs preventing alembic migrations (#62) (#68)
1. Replace shell variable interpolation in .env.example with literal
   values, since .env files don't expand variables.
2. Change alembic/env.py to read DATABASE_URL instead of
   SQLALCHEMY_DATABASE_URL to match app convention.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:49:57 -04:00
Patrick Selamy e05ac2ca64 fix: resolve two bugs preventing alembic migrations (#62) (#67)
1. Replace shell variable interpolation in .env.example with literal
   values, since .env files don't expand variables.
2. Change alembic/env.py to read DATABASE_URL instead of
   SQLALCHEMY_DATABASE_URL to match app convention.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:49:18 -04:00
Patrick Selamy 5d533c8c79 fix: resolve two bugs preventing alembic migrations (#62) (#66)
1. Replace shell variable interpolation in .env.example with literal
   values, since .env files don't expand variables.
2. Change alembic/env.py to read DATABASE_URL instead of
   SQLALCHEMY_DATABASE_URL to match app convention.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:48:25 -04:00
Patrick Selamy e67f2d6d8f fix: resolve alembic migration setup bugs (#62) (#65)
- Replace shell variable interpolation in .env.example with literal values
- Change alembic/env.py to read DATABASE_URL instead of SQLALCHEMY_DATABASE_URL

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:47:23 -04:00
Patrick Selamy 387a10132b fix: resolve alembic migration setup bugs (#62) (#64)
- Replace shell variable interpolation in .env.example with literal
  default values (.env files don't expand variables)
- Change alembic/env.py to read DATABASE_URL instead of
  SQLALCHEMY_DATABASE_URL to match the app's env var name

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:46:25 -04:00
Patrick Selamy b044bf25be fix: resolve broken alembic migrations (#62) (#63)
- Replace shell variable interpolation in .env.example with literal
  values, since .env files don't expand shell variables
- Fix alembic/env.py to read DATABASE_URL instead of SQLALCHEMY_DATABASE_URL
  to match the app's environment variable convention

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:45:21 -04:00
10 changed files with 42 additions and 45 deletions
+2 -2
View File
@@ -6,12 +6,12 @@ POSTGRES_USER=tracker
POSTGRES_PASSWORD=dev_password
# Constructed database URL (for application use)
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
DATABASE_URL=postgresql://tracker:dev_password@localhost:5432/polymarket_tracker
# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
REDIS_URL=redis://localhost:6379
# Optional: Development tool ports
ADMINER_PORT=8080
+17 -25
View File
@@ -126,13 +126,13 @@ docker compose up -d
docker compose ps
# Install Python dependencies
pip install -e .
uv sync --all-extras
# Run database migrations
alembic upgrade head
uv run alembic upgrade head
# Run the tracker
python -m src.main
uv run python -m polymarket_insider_tracker
```
### Docker Services
@@ -188,24 +188,16 @@ LIQUIDITY_IMPACT_THRESHOLD=0.02
```
polymarket-insider-tracker/
├── src/
── 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
── 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
├── tests/ # Test suite
├── scripts/
│ └── backtest.py # Historical analysis
@@ -289,16 +281,16 @@ Contributions are welcome! Please read our Contributing Guide before submitting
```bash
# Install dev dependencies
pip install -e ".[dev]"
uv sync --all-extras
# Run tests
pytest
uv run pytest
# Run linting
ruff check src/
uv run ruff check src/
# Run type checking
mypy src/
uv run mypy src/
```
---
+1 -1
View File
@@ -19,7 +19,7 @@ if config.config_file_name is not None:
target_metadata = Base.metadata
# Get database URL from environment variable or config
database_url = os.environ.get("SQLALCHEMY_DATABASE_URL")
database_url = os.environ.get("DATABASE_URL")
if database_url:
config.set_main_option("sqlalchemy.url", database_url)
@@ -5,16 +5,16 @@ Revises:
Create Date: 2026-01-04 00:00:00.000000+00:00
"""
from typing import Sequence, Union
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "001_initial"
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
-2
View File
@@ -1,5 +1,3 @@
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 Enum
from enum import StrEnum
from redis.asyncio import Redis
@@ -27,7 +27,7 @@ DEFAULT_CACHE_TTL_SECONDS = 600 # 10 minutes
DEFAULT_REDIS_KEY_PREFIX = "polymarket:market:"
class SyncState(str, Enum):
class SyncState(StrEnum):
"""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=datetime.utcnow)
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
@classmethod
def from_clob_orderbook(cls, orderbook: Any) -> "Orderbook":
@@ -9,8 +9,9 @@ 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
@@ -156,7 +157,7 @@ class TradeStreamHandler:
await self._set_state(ConnectionState.CONNECTING)
try:
ws = await websockets.connect(
ws = await ws_connect(
self._host,
ping_interval=self._ping_interval,
ping_timeout=self._ping_interval * 2,
@@ -227,7 +228,7 @@ class TradeStreamHandler:
else:
logger.debug("Received binary message (%d bytes)", len(message))
except websockets.ConnectionClosed as e:
except ConnectionClosed as e:
logger.warning("Connection closed: %s", e)
raise
except Exception as e:
@@ -284,7 +285,7 @@ class TradeStreamHandler:
while self._running:
try:
await self._listen(self._ws)
except (websockets.ConnectionClosed, Exception) as e:
except (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 Enum
from enum import StrEnum
from typing import TYPE_CHECKING
from redis.asyncio import Redis
@@ -43,7 +43,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
class PipelineState(str, Enum):
class PipelineState(StrEnum):
"""Pipeline lifecycle states."""
STOPPED = "stopped"
+8 -2
View File
@@ -231,7 +231,10 @@ class TestTradeStreamHandler:
mock_ws = AsyncMock()
mock_ws.send = AsyncMock()
with patch("websockets.connect", AsyncMock(return_value=mock_ws)):
with patch(
"polymarket_insider_tracker.ingestor.websocket.ws_connect",
AsyncMock(return_value=mock_ws),
):
ws = await handler._connect()
assert ws is mock_ws
@@ -333,7 +336,10 @@ class TestTradeStreamHandlerIntegration:
mock_ws = MockWebSocket(handler, trade_message)
with patch("websockets.connect", AsyncMock(return_value=mock_ws)):
with patch(
"polymarket_insider_tracker.ingestor.websocket.ws_connect",
AsyncMock(return_value=mock_ws),
):
# Run with timeout to prevent hanging
try:
await asyncio.wait_for(handler.start(), timeout=1.0)