fix: resolve linting and formatting issues for CI
- Use contextlib.suppress instead of try/except/pass (SIM105) - Prefix unused fixture arguments with underscore (ARG002) - Replace asyncio.TimeoutError with TimeoutError (UP041) - Apply ruff formatting to all files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
a15086688a
commit
1f4f1fa557
@@ -100,9 +100,7 @@ class DiscordChannel:
|
||||
await asyncio.sleep(retry_after)
|
||||
continue
|
||||
|
||||
logger.error(
|
||||
f"Discord webhook failed: {response.status_code} {response.text}"
|
||||
)
|
||||
logger.error(f"Discord webhook failed: {response.status_code} {response.text}")
|
||||
|
||||
except httpx.TimeoutException:
|
||||
logger.warning(f"Discord webhook timeout (attempt {attempt + 1})")
|
||||
|
||||
@@ -101,8 +101,7 @@ class AlertDispatcher:
|
||||
):
|
||||
# Allow half-open attempt
|
||||
logger.info(
|
||||
f"Circuit half-open for {channel_name}, "
|
||||
f"attempt {state.half_open_attempts + 1}"
|
||||
f"Circuit half-open for {channel_name}, attempt {state.half_open_attempts + 1}"
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -130,8 +129,7 @@ class AlertDispatcher:
|
||||
# Open the circuit
|
||||
state.is_open = True
|
||||
logger.warning(
|
||||
f"Circuit opened for {channel_name} after "
|
||||
f"{state.failure_count} failures"
|
||||
f"Circuit opened for {channel_name} after {state.failure_count} failures"
|
||||
)
|
||||
|
||||
async def _send_to_channel(
|
||||
@@ -184,15 +182,11 @@ class AlertDispatcher:
|
||||
channel_results=channel_results,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Dispatch complete: {success_count}/{len(channel_results)} succeeded"
|
||||
)
|
||||
logger.info(f"Dispatch complete: {success_count}/{len(channel_results)} succeeded")
|
||||
|
||||
return result
|
||||
|
||||
async def dispatch_batch(
|
||||
self, alerts: list[FormattedAlert]
|
||||
) -> list[DispatchResult]:
|
||||
async def dispatch_batch(self, alerts: list[FormattedAlert]) -> list[DispatchResult]:
|
||||
"""Dispatch multiple alerts sequentially.
|
||||
|
||||
Args:
|
||||
@@ -215,9 +209,7 @@ class AlertDispatcher:
|
||||
"failure_count": state.failure_count,
|
||||
"half_open_attempts": state.half_open_attempts,
|
||||
"last_failure": (
|
||||
state.last_failure_time.isoformat()
|
||||
if state.last_failure_time
|
||||
else None
|
||||
state.last_failure_time.isoformat() if state.last_failure_time else None
|
||||
),
|
||||
}
|
||||
for name, state in self._circuit_state.items()
|
||||
|
||||
@@ -30,7 +30,7 @@ def truncate_address(address: str, chars: int = 4) -> str:
|
||||
"""Truncate an Ethereum address to 0x1234...5678 format."""
|
||||
if len(address) < chars * 2 + 4:
|
||||
return address
|
||||
return f"{address[:chars+2]}...{address[-chars:]}"
|
||||
return f"{address[: chars + 2]}...{address[-chars:]}"
|
||||
|
||||
|
||||
def format_usdc(amount: Decimal) -> str:
|
||||
@@ -117,9 +117,7 @@ class AlertFormatter:
|
||||
telegram_md = self._build_telegram_markdown(
|
||||
assessment, wallet_short, risk_level, signals, links
|
||||
)
|
||||
plain_text = self._build_plain_text(
|
||||
assessment, wallet_short, risk_level, signals, links
|
||||
)
|
||||
plain_text = self._build_plain_text(assessment, wallet_short, risk_level, signals, links)
|
||||
|
||||
return FormattedAlert(
|
||||
title=title,
|
||||
@@ -226,11 +224,13 @@ class AlertFormatter:
|
||||
|
||||
# Signals (if any)
|
||||
if signals:
|
||||
fields.append({
|
||||
"name": "Signals",
|
||||
"value": ", ".join(signals),
|
||||
"inline": False,
|
||||
})
|
||||
fields.append(
|
||||
{
|
||||
"name": "Signals",
|
||||
"value": ", ".join(signals),
|
||||
"inline": False,
|
||||
}
|
||||
)
|
||||
|
||||
# Add detailed info for detailed verbosity
|
||||
if self.verbosity == "detailed":
|
||||
@@ -244,11 +244,13 @@ class AlertFormatter:
|
||||
confidences.append(f"Size Anomaly: {conf:.0%}")
|
||||
|
||||
if confidences:
|
||||
fields.append({
|
||||
"name": "Confidence",
|
||||
"value": " | ".join(confidences),
|
||||
"inline": False,
|
||||
})
|
||||
fields.append(
|
||||
{
|
||||
"name": "Confidence",
|
||||
"value": " | ".join(confidences),
|
||||
"inline": False,
|
||||
}
|
||||
)
|
||||
|
||||
embed: dict[str, object] = {
|
||||
"title": "🚨 Suspicious Activity Detected",
|
||||
@@ -319,7 +321,26 @@ class AlertFormatter:
|
||||
|
||||
def _escape_telegram_markdown(self, text: str) -> str:
|
||||
"""Escape special Telegram MarkdownV2 characters."""
|
||||
special_chars = ["_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"]
|
||||
special_chars = [
|
||||
"_",
|
||||
"*",
|
||||
"[",
|
||||
"]",
|
||||
"(",
|
||||
")",
|
||||
"~",
|
||||
"`",
|
||||
">",
|
||||
"#",
|
||||
"+",
|
||||
"-",
|
||||
"=",
|
||||
"|",
|
||||
"{",
|
||||
"}",
|
||||
".",
|
||||
"!",
|
||||
]
|
||||
for char in special_chars:
|
||||
text = text.replace(char, f"\\{char}")
|
||||
return text
|
||||
|
||||
@@ -355,9 +355,7 @@ class AlertHistory:
|
||||
end = datetime.now(UTC)
|
||||
start = end - timedelta(hours=hours)
|
||||
|
||||
index_key = (
|
||||
f"{self.KEY_INDEX_WALLET}{wallet}" if wallet else self.KEY_INDEX_TIME
|
||||
)
|
||||
index_key = f"{self.KEY_INDEX_WALLET}{wallet}" if wallet else self.KEY_INDEX_TIME
|
||||
|
||||
count = await self.redis.zcount(
|
||||
index_key,
|
||||
|
||||
@@ -265,14 +265,10 @@ class RiskAssessment:
|
||||
"has_fresh_wallet_signal": self.fresh_wallet_signal is not None,
|
||||
"has_size_anomaly_signal": self.size_anomaly_signal is not None,
|
||||
"fresh_wallet_confidence": (
|
||||
self.fresh_wallet_signal.confidence
|
||||
if self.fresh_wallet_signal
|
||||
else None
|
||||
self.fresh_wallet_signal.confidence if self.fresh_wallet_signal else None
|
||||
),
|
||||
"size_anomaly_confidence": (
|
||||
self.size_anomaly_signal.confidence
|
||||
if self.size_anomaly_signal
|
||||
else None
|
||||
self.size_anomaly_signal.confidence if self.size_anomaly_signal else None
|
||||
),
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
}
|
||||
|
||||
@@ -155,8 +155,7 @@ class RiskScorer:
|
||||
# Log assessment
|
||||
if should_alert:
|
||||
logger.info(
|
||||
"Risk assessment triggered alert: wallet=%s, market=%s, "
|
||||
"score=%.2f, signals=%d",
|
||||
"Risk assessment triggered alert: wallet=%s, market=%s, score=%.2f, signals=%d",
|
||||
bundle.wallet_address[:10] + "...",
|
||||
bundle.market_id[:10] + "...",
|
||||
weighted_score,
|
||||
@@ -180,9 +179,7 @@ class RiskScorer:
|
||||
should_alert=should_alert,
|
||||
)
|
||||
|
||||
def calculate_weighted_score(
|
||||
self, bundle: SignalBundle
|
||||
) -> tuple[float, int]:
|
||||
def calculate_weighted_score(self, bundle: SignalBundle) -> tuple[float, int]:
|
||||
"""Calculate weighted score from all signals.
|
||||
|
||||
Applies per-signal weights and multi-signal bonuses.
|
||||
@@ -273,9 +270,7 @@ class RiskScorer:
|
||||
deleted = await self._redis.delete(key)
|
||||
return deleted > 0
|
||||
|
||||
async def assess_batch(
|
||||
self, bundles: list[SignalBundle]
|
||||
) -> list[RiskAssessment]:
|
||||
async def assess_batch(self, bundles: list[SignalBundle]) -> list[RiskAssessment]:
|
||||
"""Assess multiple trade bundles.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -228,10 +228,17 @@ class SniperDetector:
|
||||
for entry in entries:
|
||||
# Normalize market ID to 0-1 range
|
||||
market_hash = (
|
||||
int(hashlib.md5( # noqa: S324
|
||||
entry.market_id.encode()
|
||||
).hexdigest()[:8], 16) % 1000
|
||||
) / 1000.0
|
||||
(
|
||||
int(
|
||||
hashlib.md5( # noqa: S324
|
||||
entry.market_id.encode()
|
||||
).hexdigest()[:8],
|
||||
16,
|
||||
)
|
||||
% 1000
|
||||
)
|
||||
/ 1000.0
|
||||
)
|
||||
|
||||
# Normalize entry delta to hours (0-5 mins = 0-0.083 hours)
|
||||
delta_hours = entry.entry_delta_seconds / 3600.0
|
||||
@@ -415,11 +422,7 @@ class SniperDetector:
|
||||
overlap_factor = min(1.0, markets_common / 5.0)
|
||||
|
||||
# Weighted combination
|
||||
confidence = (
|
||||
0.3 * size_factor +
|
||||
0.4 * speed_factor +
|
||||
0.3 * overlap_factor
|
||||
)
|
||||
confidence = 0.3 * size_factor + 0.4 * speed_factor + 0.3 * overlap_factor
|
||||
|
||||
return round(min(1.0, confidence), 3)
|
||||
|
||||
|
||||
@@ -35,10 +35,12 @@ from polymarket_insider_tracker.ingestor.publisher import (
|
||||
)
|
||||
from polymarket_insider_tracker.ingestor.websocket import (
|
||||
ConnectionState,
|
||||
StreamStats as WebSocketStreamStats,
|
||||
TradeStreamError,
|
||||
TradeStreamHandler,
|
||||
)
|
||||
from polymarket_insider_tracker.ingestor.websocket import (
|
||||
StreamStats as WebSocketStreamStats,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# CLOB Client
|
||||
|
||||
@@ -6,7 +6,7 @@ import os
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from functools import wraps
|
||||
from typing import Any, ParamSpec, TypeVar
|
||||
from typing import ParamSpec, TypeVar
|
||||
|
||||
from py_clob_client.client import ClobClient as BaseClobClient
|
||||
from py_clob_client.clob_types import BookParams
|
||||
|
||||
@@ -335,8 +335,10 @@ class HealthMonitor:
|
||||
|
||||
overall_status = self._determine_overall_status()
|
||||
HEALTH_STATUS.set(
|
||||
1.0 if overall_status == HealthStatus.HEALTHY
|
||||
else 0.5 if overall_status == HealthStatus.DEGRADED
|
||||
1.0
|
||||
if overall_status == HealthStatus.HEALTHY
|
||||
else 0.5
|
||||
if overall_status == HealthStatus.DEGRADED
|
||||
else 0.0
|
||||
)
|
||||
|
||||
@@ -362,10 +364,7 @@ class HealthMonitor:
|
||||
report = self.get_health_report()
|
||||
|
||||
# Notify on status change
|
||||
if (
|
||||
self._on_health_change
|
||||
and report.status != self._last_health_status
|
||||
):
|
||||
if self._on_health_change and report.status != self._last_health_status:
|
||||
self._last_health_status = report.status
|
||||
try:
|
||||
await self._on_health_change(report)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Data models for the ingestor module."""
|
||||
|
||||
import contextlib
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import UTC, datetime
|
||||
from decimal import Decimal
|
||||
@@ -46,10 +47,8 @@ class Market:
|
||||
end_date = None
|
||||
end_date_iso = data.get("end_date_iso")
|
||||
if end_date_iso:
|
||||
try:
|
||||
with contextlib.suppress(ValueError, AttributeError):
|
||||
end_date = datetime.fromisoformat(end_date_iso.replace("Z", "+00:00"))
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
return cls(
|
||||
condition_id=str(data["condition_id"]),
|
||||
@@ -466,10 +465,8 @@ class MarketMetadata:
|
||||
end_date = None
|
||||
end_date_str = data.get("end_date")
|
||||
if end_date_str:
|
||||
try:
|
||||
with contextlib.suppress(ValueError, AttributeError):
|
||||
end_date = datetime.fromisoformat(end_date_str)
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
last_updated_str = data.get("last_updated")
|
||||
if last_updated_str:
|
||||
|
||||
@@ -91,7 +91,9 @@ class WalletAnalyzer:
|
||||
return WalletProfile(
|
||||
address=data["address"],
|
||||
nonce=data["nonce"],
|
||||
first_seen=datetime.fromisoformat(data["first_seen"]) if data["first_seen"] else None,
|
||||
first_seen=datetime.fromisoformat(data["first_seen"])
|
||||
if data["first_seen"]
|
||||
else None,
|
||||
age_hours=data["age_hours"],
|
||||
is_fresh=data["is_fresh"],
|
||||
total_tx_count=data["total_tx_count"],
|
||||
|
||||
@@ -503,9 +503,7 @@ class PolygonClient:
|
||||
balance_task = self.get_balance(address)
|
||||
first_tx_task = self.get_first_transaction(address)
|
||||
|
||||
nonce, balance, first_tx = await asyncio.gather(
|
||||
nonce_task, balance_task, first_tx_task
|
||||
)
|
||||
nonce, balance, first_tx = await asyncio.gather(nonce_task, balance_task, first_tx_task)
|
||||
|
||||
return WalletInfo(
|
||||
address=address.lower(),
|
||||
|
||||
@@ -195,19 +195,16 @@ class EntityRegistry:
|
||||
True if the address is a known smart contract.
|
||||
"""
|
||||
entity_type = self.classify(address)
|
||||
contract_types = (
|
||||
self.DEX_ENTITY_TYPES
|
||||
| {
|
||||
EntityType.TOKEN_USDC,
|
||||
EntityType.TOKEN_USDT,
|
||||
EntityType.TOKEN_WETH,
|
||||
EntityType.TOKEN_WMATIC,
|
||||
EntityType.DEFI_AAVE,
|
||||
EntityType.DEFI_COMPOUND,
|
||||
EntityType.DEFI_OTHER,
|
||||
EntityType.CONTRACT,
|
||||
}
|
||||
)
|
||||
contract_types = self.DEX_ENTITY_TYPES | {
|
||||
EntityType.TOKEN_USDC,
|
||||
EntityType.TOKEN_USDT,
|
||||
EntityType.TOKEN_WETH,
|
||||
EntityType.TOKEN_WMATIC,
|
||||
EntityType.DEFI_AAVE,
|
||||
EntityType.DEFI_COMPOUND,
|
||||
EntityType.DEFI_OTHER,
|
||||
EntityType.CONTRACT,
|
||||
}
|
||||
return entity_type in contract_types
|
||||
|
||||
def get_entity_category(self, address: str) -> str:
|
||||
|
||||
@@ -58,7 +58,10 @@ class WalletInfo:
|
||||
"""Return wallet age in days based on first transaction."""
|
||||
if self.first_transaction is None:
|
||||
return None
|
||||
delta = datetime.now(tz=self.first_transaction.timestamp.tzinfo) - self.first_transaction.timestamp
|
||||
delta = (
|
||||
datetime.now(tz=self.first_transaction.timestamp.tzinfo)
|
||||
- self.first_transaction.timestamp
|
||||
)
|
||||
return delta.total_seconds() / 86400
|
||||
|
||||
|
||||
|
||||
@@ -109,7 +109,9 @@ class WalletRelationshipModel(Base):
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("wallet_a", "wallet_b", "relationship_type", name="uq_wallet_relationship"),
|
||||
UniqueConstraint(
|
||||
"wallet_a", "wallet_b", "relationship_type", name="uq_wallet_relationship"
|
||||
),
|
||||
Index("idx_wallet_relationships_a", "wallet_a"),
|
||||
Index("idx_wallet_relationships_b", "wallet_b"),
|
||||
)
|
||||
|
||||
@@ -274,9 +274,7 @@ class FundingRepository:
|
||||
"""
|
||||
self.session = session
|
||||
|
||||
async def get_transfers_to(
|
||||
self, address: str, limit: int = 100
|
||||
) -> list[FundingTransferDTO]:
|
||||
async def get_transfers_to(self, address: str, limit: int = 100) -> list[FundingTransferDTO]:
|
||||
"""Get transfers to a wallet address.
|
||||
|
||||
Args:
|
||||
@@ -294,9 +292,7 @@ class FundingRepository:
|
||||
)
|
||||
return [FundingTransferDTO.from_model(m) for m in result.scalars().all()]
|
||||
|
||||
async def get_transfers_from(
|
||||
self, address: str, limit: int = 100
|
||||
) -> list[FundingTransferDTO]:
|
||||
async def get_transfers_from(self, address: str, limit: int = 100) -> list[FundingTransferDTO]:
|
||||
"""Get transfers from a wallet address.
|
||||
|
||||
Args:
|
||||
@@ -492,9 +488,7 @@ class RelationshipRepository:
|
||||
await self.session.flush()
|
||||
return dto
|
||||
|
||||
async def delete(
|
||||
self, wallet_a: str, wallet_b: str, relationship_type: str
|
||||
) -> bool:
|
||||
async def delete(self, wallet_a: str, wallet_b: str, relationship_type: str) -> bool:
|
||||
"""Delete a specific relationship.
|
||||
|
||||
Args:
|
||||
|
||||
Reference in New Issue
Block a user