feat: Introduce Polymarket wallet activity watcher with configurable user aliases and link previews.

This commit is contained in:
2569718930@qq.com
2026-03-12 07:59:47 +08:00
parent 47ff575588
commit 0c976ac846
2 changed files with 83 additions and 8 deletions
+5
View File
@@ -62,6 +62,10 @@ POLYGON_WALLET_WATCH_POLYMARKET_CONTRACTS=
# Polymarket Wallet Activity Watcher (all markets, not weather-only)
POLYMARKET_WALLET_ACTIVITY_ENABLED=false
POLYMARKET_WALLET_ACTIVITY_USERS=0x0000000000000000000000000000000000000000
# Optional wallet nicknames:
# - CSV: 0xabc...=Whale_A,0xdef...=Main_Account
# - JSON: {"0xabc...":"Whale A","0xdef...":"Main Account"}
POLYMARKET_WALLET_ACTIVITY_USER_ALIASES=
POLYMARKET_WALLET_ACTIVITY_DATA_API_URL=https://data-api.polymarket.com
POLYMARKET_WALLET_ACTIVITY_INTERVAL_SEC=20
POLYMARKET_WALLET_ACTIVITY_TIMEOUT_SEC=10
@@ -74,6 +78,7 @@ POLYMARKET_WALLET_ACTIVITY_IMMEDIATE_COOLDOWN_SEC=20
POLYMARKET_WALLET_ACTIVITY_MAX_CHANGES_PER_MSG=5
POLYMARKET_WALLET_ACTIVITY_NOTIFY_CLOSED=false
POLYMARKET_WALLET_ACTIVITY_BOOTSTRAP_ALERT=false
POLYMARKET_WALLET_ACTIVITY_LINK_PREVIEW=true
POLYMARKET_WALLET_ACTIVITY_UPDATE_DEBOUNCE_SEC=30
POLYMARKET_WALLET_ACTIVITY_UPDATE_MAX_HOLD_SEC=120
POLYMARKET_WALLET_ACTIVITY_AVG_PRICE_SHOW_MIN=0.01
@@ -76,6 +76,51 @@ def _parse_addresses(raw: Optional[str]) -> List[str]:
return out
def _parse_address_aliases(raw: Optional[str]) -> Dict[str, str]:
"""
Parse wallet aliases from either:
- JSON object: {"0xabc...": "Whale A", "0xdef...": "Whale B"}
- CSV pairs: 0xabc...=Whale A,0xdef...=Whale B
"""
out: Dict[str, str] = {}
if not raw:
return out
text = str(raw).strip()
if not text:
return out
if text.startswith("{"):
try:
parsed = json.loads(text)
if isinstance(parsed, dict):
for k, v in parsed.items():
addr = _normalize_addr(k)
alias = str(v or "").strip()
if addr and alias:
out[addr] = alias
return out
except Exception:
pass
for part in text.split(","):
row = part.strip()
if not row:
continue
if "=" in row:
left, right = row.split("=", 1)
elif ":" in row:
left, right = row.split(":", 1)
else:
continue
addr = _normalize_addr(left)
alias = str(right or "").strip()
if addr and alias:
out[addr] = alias
return out
def _state_file() -> str:
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
return os.path.join(root, "data", "polymarket_wallet_activity_state.json")
@@ -374,6 +419,7 @@ def _should_show_avg_price(avg_price: float) -> bool:
def _format_change_block(
change_type: str,
wallet: str,
wallet_alias: Optional[str],
pos: Dict[str, Any],
now_utc: str,
) -> str:
@@ -393,11 +439,14 @@ def _format_change_block(
else:
lines.append("🔄 仓位更新")
lines.append(f"钱包: {_short(wallet)}")
wallet_label = _short(wallet)
if wallet_alias:
wallet_label = f"{wallet_alias} ({wallet_label})"
lines.append(f"钱包: {wallet_label}")
lines.append(f"市场: {title}")
if market_url:
lines.append(f"市场: {title} ({market_url})")
else:
lines.append(f"市场: {title}")
# Keep raw URL on its own line so Telegram can generate link preview.
lines.append(f"链接: {market_url}")
lines.append(f"买入方向: {outcome}")
@@ -434,6 +483,7 @@ def _build_message(
wallet: str,
changes: List[Tuple[str, Dict[str, Any]]],
max_changes: int,
wallet_alias: Optional[str] = None,
) -> str:
now_bj = (
datetime.now(timezone.utc)
@@ -444,7 +494,15 @@ def _build_message(
lines = [f"🚨 钱包异动监控 ({len(changes)} 个异动):", ""]
for idx, (change_type, pos) in enumerate(shown):
lines.append(_format_change_block(change_type, wallet, pos, f"{now_bj} 北京时间"))
lines.append(
_format_change_block(
change_type,
wallet,
wallet_alias,
pos,
f"{now_bj} 北京时间",
)
)
if idx != len(shown) - 1:
lines.append("")
@@ -459,6 +517,9 @@ def start_polymarket_wallet_activity_loop(bot: Any) -> Optional[threading.Thread
enabled = _env_bool("POLYMARKET_WALLET_ACTIVITY_ENABLED", False)
chat_id = os.getenv("TELEGRAM_CHAT_ID")
users = _parse_addresses(os.getenv("POLYMARKET_WALLET_ACTIVITY_USERS"))
user_aliases = _parse_address_aliases(
os.getenv("POLYMARKET_WALLET_ACTIVITY_USER_ALIASES")
)
if not enabled:
logger.info("polymarket wallet activity watcher disabled")
@@ -499,6 +560,7 @@ def start_polymarket_wallet_activity_loop(bot: Any) -> Optional[threading.Thread
max_changes = max(1, _env_int("POLYMARKET_WALLET_ACTIVITY_MAX_CHANGES_PER_MSG", 5))
notify_closed = _env_bool("POLYMARKET_WALLET_ACTIVITY_NOTIFY_CLOSED", False)
bootstrap_alert = _env_bool("POLYMARKET_WALLET_ACTIVITY_BOOTSTRAP_ALERT", False)
link_preview = _env_bool("POLYMARKET_WALLET_ACTIVITY_LINK_PREVIEW", True)
default_debounce_sec = max(poll_sec, 30)
update_debounce_sec = max(
poll_sec,
@@ -653,8 +715,17 @@ def start_polymarket_wallet_activity_loop(bot: Any) -> Optional[threading.Thread
)
if outgoing:
msg = _build_message(user, outgoing, max_changes=max_changes)
bot.send_message(chat_id, msg, disable_web_page_preview=True)
msg = _build_message(
user,
outgoing,
max_changes=max_changes,
wallet_alias=user_aliases.get(user),
)
bot.send_message(
chat_id,
msg,
disable_web_page_preview=not link_preview,
)
logger.info(
f"wallet activity pushed user={user} changes={len(outgoing)}"
)
@@ -687,4 +758,3 @@ def start_polymarket_wallet_activity_loop(bot: Any) -> Optional[threading.Thread
)
thread.start()
return thread