[{"content":"Nếu bạn từng thử học các cuộc tấn công WiFi theo cách truyền thống, quy trình thường như sau: đặt một USB WiFi adapter hỗ trợ chế độ monitor và packet injection, vật lộn với driver Linux cả buổi tối, dựng một access point thử nghiệm mà bạn thực sự sở hữu, và sau đó mới bắt đầu luyện kỹ thuật bạn muốn học. WiFi-Forge bỏ qua toàn bộ quá trình chuẩn bị này.\nWiFi-Forge là một dự án mã nguồn mở của Black Hills InfoSec, cung cấp môi trường an toàn và hợp pháp để luyện tấn công không dây. Không cần phần cứng, không có nguy cơ vô tình tác động vào mạng người khác, và không lo brick driver. Khởi động một lab ảo và bắt đầu hack ngay.\nVì sao tồn tại —— ba cái bẫy khi học WiFi Học WiFi truyền thống có ba điểm khiến nhiều người bỏ cuộc:\nHên xui phần cứng. Không phải USB adapter nào cũng hỗ trợ monitor + injection sạch sẽ. Những cái đáng tin (Alfa AWUS036, Panda PAU09, v.v.) tốn 30–60 USD, và một lần chỉ dùng được một cái. Vùng xám pháp lý. Ở hầu hết các nước, chạm vào bất kỳ mạng nào bạn không sở hữu —— kể cả nghe lén bị động —— đều là phạm pháp. \u0026ldquo;Tôi chỉ sniff thôi\u0026rdquo; không phải lý do biện hộ được. Chi phí reset. Phần cứng thật không reset bằng một lệnh. Bạn không thể git checkout để hoàn tác cấu hình hỏng. WiFi-Forge gói gọn cả ba vấn đề này vào một sandbox duy nhất trên laptop của bạn.\nCấu trúc bên trong WiFi-Forge được xây dựng trên mininet-wifi —— một bộ mô phỏng mạng 802.11 tạo các access point ảo, station và \u0026ldquo;sóng vô tuyến\u0026rdquo; trong các Linux network namespace. Mỗi AP và client là một process Linux thực —— bạn có thể dùng iwconfig, airodump-ng, tcpdump, thậm chí cả Reaver và Hashcat trên traffic mô phỏng, và tất cả các công cụ tiêu chuẩn đều hành xử như đang chạm vào sóng radio thật.\nWiFi-Forge thêm vào trên đó: các topology dựng sẵn, các kịch bản tấn công sẵn sàng chạy, và cấu trúc hướng dẫn để bạn không phải thiết kế mạng từ đầu mỗi lần muốn luyện tập.\nCó thể luyện gì Các lab có sẵn bao phủ những loại tấn công WiFi phổ biến:\nBắt handshake WPA/WPA2 —— deauth một client, bắt 4-way handshake, crack offline bằng hashcat hoặc aircrack-ng Tấn công WPS —— brute-force PIN bằng Reaver, tấn công Pixie-Dust Evil-twin / Karma —— dựng AP giả mạo SSID mục tiêu, xem các client tự động kết nối Deauth flood —— đá client khỏi các AP hợp pháp Beacon flooding —— phát hàng nghìn AP giả để làm rối các scanner Phân tích MAC randomization —— xem cách các thiết bị hiện đại che giấu danh tính (và rò rỉ ở đâu) Tấn công PMKID —— bắt handshake mà không cần client đang kết nối Mỗi lab khởi động một topology cụ thể, đưa bạn vào shell, và đặt ra mục tiêu kiểu CTF nhỏ.\nBắt đầu git clone https://github.com/blackhillsinfosec/WifiForge cd WifiForge sudo ./install.sh sudo python3 wififorge.py Cần Linux (Ubuntu hoặc Debian là tốt nhất), Python 3, và quyền root (mininet-wifi dùng các tính năng kernel). Script cài đặt sẽ xử lý dependencies —— mininet-wifi, aircrack-ng, hashcat, reaver, v.v.\nDành cho ai Người ôn OSCP / OSWP —— luyện các kịch bản giống lab thi mà không cần mua phần cứng Người tổ chức CTF —— dựng nhanh các challenge wireless mà không cần radio thật Giảng viên bảo mật —— cấp cho mỗi học viên một lab độc lập, reset trong vài giây Lập trình viên tò mò —— cuối cùng cũng hiểu được 4-way handshake thực sự trông như thế nào, với debugger gắn vào ⚠️ WiFi-Forge không dạy được gì: lớp vật lý —— RF, chọn ăng-ten, suy hao tín hiệu thực. Phần đó cuối cùng vẫn cần một card thật. Nhưng ở lớp giao thức, nơi 90% các cuộc tấn công thực tế diễn ra, mô phỏng và traffic thật về cơ bản không phân biệt được.\nMột lưu ý về hợp pháp và đạo đức Loại dự án này bắt buộc phải nói thẳng: chỉ sử dụng các kỹ thuật này trên các mạng bạn sở hữu hoặc có giấy phép kiểm thử bằng văn bản. WiFi-Forge tồn tại chính vì lab mô phỏng loại bỏ mọi cám dỗ \u0026ldquo;thử một chút\u0026rdquo; với WiFi của quán cà phê bên cạnh. Học một cách an toàn —— đó là toàn bộ ý nghĩa.\nRepo: github.com/blackhillsinfosec/WifiForge Dựa trên: mininet-wifi Bảo trì bởi: Black Hills InfoSec ","permalink":"https://dibi8.com/vi/posts/wifi-forge-sandbox-an-to%C3%A0n-v%C3%A0-h%E1%BB%A3p-ph%C3%A1p-%C4%91%E1%BB%83-h%E1%BB%8Dc-hack-wifi/","summary":"\u003cp\u003eNếu bạn từng thử học các cuộc tấn công WiFi theo cách truyền thống, quy trình thường như sau: đặt một USB WiFi adapter hỗ trợ chế độ monitor và packet injection, vật lộn với driver Linux cả buổi tối, dựng một access point thử nghiệm mà bạn thực sự sở hữu, và \u003cem\u003esau đó\u003c/em\u003e mới bắt đầu luyện kỹ thuật bạn muốn học. \u003cstrong\u003eWiFi-Forge\u003c/strong\u003e bỏ qua toàn bộ quá trình chuẩn bị này.\u003c/p\u003e","title":"WiFi-Forge — Sandbox An Toàn và Hợp Pháp Để Học Hack WiFi"},{"content":"Bài viết này tổng hợp đầy đủ 7 đoạn code hiện được công bố trên Code Vault — một kho code cá nhân về radar giao dịch crypto, hệ thống giao dịch tự động và công cụ bảo mật, tất cả viết bằng Python thuần với chi phí API bằng 0 hoặc gần bằng 0. Mỗi đoạn code bên dưới đều kèm mã nguồn đầy đủ để bạn đọc, fork và chạy cục bộ. Sử dụng mục lục bên phải để nhảy đến công cụ cụ thể.\n⚠️ Cảnh báo rủi ro — Các công cụ này tiếp xúc trực tiếp với thị trường và dữ liệu on-chain. Một số công cụ gửi cảnh báo thời gian thực qua Telegram, và một công cụ (\u0026ldquo;AI giao dịch tự động\u0026rdquo;) mở vị thế ảo trên Binance Futures. Hãy đọc kỹ ghi chú dưới mỗi công cụ. Sử dụng với rủi ro tự chịu; tác giả không bảo đảm bất kỳ kết quả giao dịch nào.\nRadar Giao Dịch Vitalik Sell Radar (Radar bán của Vitalik) Ngày: 2026.05.02　Thẻ: Python · WebSocket · Ethereum · Telegram · Event-Driven\nGitHub: vitalik-sell-radar\nSự kiện WebSocket · Phát hiện ví Vitalik bán · Đẩy Telegram dưới giây\nTheo dõi ví Vitalik Buterin (vitalik.eth) phát hiện các giao dịch bán token ERC-20 qua subscribe sự kiện WebSocket — không polling, độ trễ dưới 1 giây. Tự động phân loại người nhận: DEX Router (Uniswap, 1inch, SushiSwap), ví nóng CEX (Binance, Coinbase, Kraken), hoặc LP Pool. Lấy giá token thời gian thực từ DexScreener. Failover đa RPC với tự động reconnect. Python thuần, chi phí 0 — dùng RPC công khai miễn phí.\nMã nguồn đầy đủ #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; Vitalik Sell Radar — Event-Driven Edition WebSocket subscription to ERC-20 Transfer events, real-time detection of Vitalik\u0026#39;s sell activity, instant push to Telegram. Architecture: 1. WebSocket subscribes to Transfer(from=vitalik) events → sub-second detection 2. Classifies sell behavior (transfers to DEX Router / CEX / LP Pool) 3. Queries token info + price via DexScreener 4. Pushes alert to Telegram 5. Auto-reconnect + multi-RPC failover \u0026#34;\u0026#34;\u0026#34; import asyncio import json import logging import os import signal import sys import time from datetime import datetime, timezone from pathlib import Path import aiohttp import websockets # Load .env file def load_env(): env_path = Path(__file__).parent / \u0026#34;.env\u0026#34; if env_path.exists(): for line in env_path.read_text().splitlines(): line = line.strip() if line and not line.startswith(\u0026#34;#\u0026#34;) and \u0026#34;=\u0026#34; in line: k, v = line.split(\u0026#34;=\u0026#34;, 1) os.environ.setdefault(k.strip(), v.strip()) load_env() # ============================================================ # Configuration # ============================================================ # Vitalik\u0026#39;s main wallet VITALIK_ADDRESS = \u0026#34;0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\u0026#34; VITALIK_PADDED = \u0026#34;0x\u0026#34; + VITALIK_ADDRESS[2:].lower().zfill(64) # ERC-20 Transfer event topic TRANSFER_TOPIC = \u0026#34;0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\u0026#34; # WebSocket RPC endpoints (free, support eth_subscribe) WS_ENDPOINTS = [ \u0026#34;wss://ethereum-rpc.publicnode.com\u0026#34;, \u0026#34;wss://eth.drpc.org\u0026#34;, \u0026#34;wss://ethereum.publicnode.com\u0026#34;, ] # HTTP RPC for querying token info HTTP_RPC = os.environ.get(\u0026#34;HTTP_RPC\u0026#34;, \u0026#34;https://eth.drpc.org\u0026#34;) # Telegram TG_BOT_TOKEN = os.environ.get(\u0026#34;TG_BOT_TOKEN\u0026#34;, \u0026#34;\u0026#34;) TG_CHAT_ID = os.environ.get(\u0026#34;TG_CHAT_ID\u0026#34;, \u0026#34;\u0026#34;) # Known DEX Router addresses (sell destinations) KNOWN_DEX_ROUTERS = { # Uniswap \u0026#34;0x7a250d5630b4cf539739df2c5dacb4c659f2488d\u0026#34;: \u0026#34;Uniswap V2 Router\u0026#34;, \u0026#34;0xe592427a0aece92de3edee1f18e0157c05861564\u0026#34;: \u0026#34;Uniswap V3 Router\u0026#34;, \u0026#34;0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45\u0026#34;: \u0026#34;Uniswap V3 Router2\u0026#34;, \u0026#34;0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad\u0026#34;: \u0026#34;Uniswap Universal Router\u0026#34;, \u0026#34;0xef1c6e67703c7bd7107eed8303fbe6ec2554bf6b\u0026#34;: \u0026#34;Uniswap Universal Router (old)\u0026#34;, # 1inch \u0026#34;0x1111111254eeb25477b68fb85ed929f73a960582\u0026#34;: \u0026#34;1inch V5\u0026#34;, \u0026#34;0x111111125421ca6dc452d289314280a0f8842a65\u0026#34;: \u0026#34;1inch V6\u0026#34;, # SushiSwap \u0026#34;0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f\u0026#34;: \u0026#34;SushiSwap Router\u0026#34;, # CoW Protocol \u0026#34;0x9008d19f58aabd9ed0d60971565aa8510560ab41\u0026#34;: \u0026#34;CoW Settlement\u0026#34;, # 0x \u0026#34;0xdef1c0ded9bec7f1a1670819833240f027b25eff\u0026#34;: \u0026#34;0x Exchange Proxy\u0026#34;, # Curve \u0026#34;0x99a58482bd75cbab83b27ec03ca68ff489b5788f\u0026#34;: \u0026#34;Curve Router\u0026#34;, } # Known CEX hot wallets (partial list) KNOWN_CEX = { \u0026#34;0x28c6c06298d514db089934071355e5743bf21d60\u0026#34;: \u0026#34;Binance Hot Wallet\u0026#34;, \u0026#34;0x21a31ee1afc51d94c2efccaa2092ad1028285549\u0026#34;: \u0026#34;Binance Hot Wallet 2\u0026#34;, \u0026#34;0xdfd5293d8e347dfe59e90efd55b2956a1343963d\u0026#34;: \u0026#34;Binance Hot Wallet 3\u0026#34;, \u0026#34;0x56eddb7aa87536c09ccc2793473599fd21a8b17f\u0026#34;: \u0026#34;Binance Hot Wallet 4\u0026#34;, \u0026#34;0x71660c4005ba85c37ccec55d0c4493e66fe775d3\u0026#34;: \u0026#34;Coinbase\u0026#34;, \u0026#34;0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43\u0026#34;: \u0026#34;Coinbase 10\u0026#34;, \u0026#34;0x503828976d22510aad0201ac7ec88293211d23da\u0026#34;: \u0026#34;Coinbase 2\u0026#34;, \u0026#34;0x2faf487a4414fe77e2327f0bf4ae2a264a776ad2\u0026#34;: \u0026#34;FTX (defunct)\u0026#34;, \u0026#34;0x267be1c1d684f78cb4f6a176c4911b741e4ffdc0\u0026#34;: \u0026#34;Kraken\u0026#34;, \u0026#34;0xae2d4617c862309a3d75a0ffb358c7a5009c673f\u0026#34;: \u0026#34;Kraken 10\u0026#34;, } # Minimum notification amount (USD), 0 = notify all MIN_NOTIFY_USD = int(os.environ.get(\u0026#34;MIN_NOTIFY_USD\u0026#34;, \u0026#34;0\u0026#34;)) # Reconnect settings RECONNECT_DELAY = 5 MAX_RECONNECT_DELAY = 60 # ============================================================ # Logging # ============================================================ logging.basicConfig( level=logging.INFO, format=\u0026#34;%(asctime)s [%(levelname)s] %(message)s\u0026#34;, datefmt=\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;, ) log = logging.getLogger(\u0026#34;vitalik-radar\u0026#34;) # ============================================================ # Global state # ============================================================ # Dedup set for processed tx hashes (last 1000) seen_txs: set = set() seen_txs_list: list = [] # HTTP session http_session: aiohttp.ClientSession | None = None # Token info cache: {address: {symbol, name, decimals}} token_cache: dict = {} # Running flag running = True # ============================================================ # Utility functions # ============================================================ def shorten_addr(addr: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;Shorten address for display\u0026#34;\u0026#34;\u0026#34; return f\u0026#34;{addr[:6]}...{addr[-4:]}\u0026#34; def decode_transfer_value(data_hex: str, decimals: int) -\u0026gt; float: \u0026#34;\u0026#34;\u0026#34;Decode Transfer event value\u0026#34;\u0026#34;\u0026#34; try: raw = int(data_hex, 16) return raw / (10 ** decimals) except: return 0.0 def topic_to_address(topic: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;Extract 20-byte address from 32-byte topic\u0026#34;\u0026#34;\u0026#34; return \u0026#34;0x\u0026#34; + topic[-40:] def classify_recipient(to_addr: str) -\u0026gt; tuple[str, str]: \u0026#34;\u0026#34;\u0026#34; Classify recipient address. Returns (type, name) Type: \u0026#34;dex\u0026#34; / \u0026#34;cex\u0026#34; / \u0026#34;pool\u0026#34; / \u0026#34;unknown\u0026#34; \u0026#34;\u0026#34;\u0026#34; to_lower = to_addr.lower() if to_lower in KNOWN_DEX_ROUTERS: return \u0026#34;dex\u0026#34;, KNOWN_DEX_ROUTERS[to_lower] if to_lower in KNOWN_CEX: return \u0026#34;cex\u0026#34;, KNOWN_CEX[to_lower] return \u0026#34;unknown\u0026#34;, \u0026#34;\u0026#34; async def get_http_session() -\u0026gt; aiohttp.ClientSession: global http_session if http_session is None or http_session.closed: http_session = aiohttp.ClientSession() return http_session async def rpc_call(method: str, params: list) -\u0026gt; dict | None: \u0026#34;\u0026#34;\u0026#34;HTTP JSON-RPC call\u0026#34;\u0026#34;\u0026#34; session = await get_http_session() try: async with session.post( HTTP_RPC, json={\u0026#34;jsonrpc\u0026#34;: \u0026#34;2.0\u0026#34;, \u0026#34;id\u0026#34;: 1, \u0026#34;method\u0026#34;: method, \u0026#34;params\u0026#34;: params}, timeout=aiohttp.ClientTimeout(total=10), ) as resp: data = await resp.json() return data.get(\u0026#34;result\u0026#34;) except Exception as e: log.error(f\u0026#34;RPC call {method} failed: {e}\u0026#34;) return None async def get_token_info(token_addr: str) -\u0026gt; dict: \u0026#34;\u0026#34;\u0026#34;Query ERC-20 token info (symbol, name, decimals)\u0026#34;\u0026#34;\u0026#34; addr_lower = token_addr.lower() if addr_lower in token_cache: return token_cache[addr_lower] info = {\u0026#34;symbol\u0026#34;: \u0026#34;???\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Unknown\u0026#34;, \u0026#34;decimals\u0026#34;: 18, \u0026#34;address\u0026#34;: token_addr} # symbol() result = await rpc_call(\u0026#34;eth_call\u0026#34;, [ {\u0026#34;to\u0026#34;: token_addr, \u0026#34;data\u0026#34;: \u0026#34;0x95d89b41\u0026#34;}, \u0026#34;latest\u0026#34; ]) if result and len(result) \u0026gt; 2: try: hex_str = result[2:] if len(hex_str) \u0026gt;= 128: offset = int(hex_str[:64], 16) * 2 length = int(hex_str[offset:offset+64], 16) symbol_hex = hex_str[offset+64:offset+64+length*2] info[\u0026#34;symbol\u0026#34;] = bytes.fromhex(symbol_hex).decode(\u0026#34;utf-8\u0026#34;, errors=\u0026#34;replace\u0026#34;).strip(\u0026#39;\\x00\u0026#39;) elif len(hex_str) == 64: info[\u0026#34;symbol\u0026#34;] = bytes.fromhex(hex_str).decode(\u0026#34;utf-8\u0026#34;, errors=\u0026#34;replace\u0026#34;).strip(\u0026#39;\\x00\u0026#39;) except: pass # decimals() result = await rpc_call(\u0026#34;eth_call\u0026#34;, [ {\u0026#34;to\u0026#34;: token_addr, \u0026#34;data\u0026#34;: \u0026#34;0x313ce567\u0026#34;}, \u0026#34;latest\u0026#34; ]) if result and len(result) \u0026gt; 2: try: info[\u0026#34;decimals\u0026#34;] = int(result, 16) except: pass # name() result = await rpc_call(\u0026#34;eth_call\u0026#34;, [ {\u0026#34;to\u0026#34;: token_addr, \u0026#34;data\u0026#34;: \u0026#34;0x06fdde03\u0026#34;}, \u0026#34;latest\u0026#34; ]) if result and len(result) \u0026gt; 2: try: hex_str = result[2:] if len(hex_str) \u0026gt;= 128: offset = int(hex_str[:64], 16) * 2 length = int(hex_str[offset:offset+64], 16) name_hex = hex_str[offset+64:offset+64+length*2] info[\u0026#34;name\u0026#34;] = bytes.fromhex(name_hex).decode(\u0026#34;utf-8\u0026#34;, errors=\u0026#34;replace\u0026#34;).strip(\u0026#39;\\x00\u0026#39;) elif len(hex_str) == 64: info[\u0026#34;name\u0026#34;] = bytes.fromhex(hex_str).decode(\u0026#34;utf-8\u0026#34;, errors=\u0026#34;replace\u0026#34;).strip(\u0026#39;\\x00\u0026#39;) except: pass token_cache[addr_lower] = info return info async def check_if_pool(addr: str) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34;Check if address is a Uniswap V2/V3 pool (has token0 method)\u0026#34;\u0026#34;\u0026#34; result = await rpc_call(\u0026#34;eth_call\u0026#34;, [ {\u0026#34;to\u0026#34;: addr, \u0026#34;data\u0026#34;: \u0026#34;0x0dfe1681\u0026#34;}, \u0026#34;latest\u0026#34; # token0() ]) if result and len(result) == 66: # 0x + 64 hex chars return True return False async def get_eth_price() -\u0026gt; float: \u0026#34;\u0026#34;\u0026#34;Get ETH price from CoinGecko\u0026#34;\u0026#34;\u0026#34; session = await get_http_session() try: async with session.get( \u0026#34;https://api.coingecko.com/api/v3/simple/price?ids=ethereum\u0026amp;vs_currencies=usd\u0026#34;, timeout=aiohttp.ClientTimeout(total=5), ) as resp: data = await resp.json() return data.get(\u0026#34;ethereum\u0026#34;, {}).get(\u0026#34;usd\u0026#34;, 0) except: return 2300 # fallback async def get_token_price_usd(token_addr: str) -\u0026gt; float | None: \u0026#34;\u0026#34;\u0026#34;Get token USD price from DexScreener (free, no API key needed)\u0026#34;\u0026#34;\u0026#34; session = await get_http_session() try: async with session.get( f\u0026#34;https://api.dexscreener.com/latest/dex/tokens/{token_addr}\u0026#34;, timeout=aiohttp.ClientTimeout(total=5), ) as resp: data = await resp.json() pairs = data.get(\u0026#34;pairs\u0026#34;, []) if pairs: # Pick pair with highest liquidity pairs.sort(key=lambda p: float(p.get(\u0026#34;liquidity\u0026#34;, {}).get(\u0026#34;usd\u0026#34;, 0) or 0), reverse=True) price = float(pairs[0].get(\u0026#34;priceUsd\u0026#34;, 0) or 0) return price if price \u0026gt; 0 else None except: pass return None # ============================================================ # Telegram notifications # ============================================================ async def send_telegram(text: str): \u0026#34;\u0026#34;\u0026#34;Send Telegram message\u0026#34;\u0026#34;\u0026#34; if not TG_BOT_TOKEN: log.warning(\u0026#34;[TG] No bot token configured, skipping notification\u0026#34;) return session = await get_http_session() url = f\u0026#34;https://api.telegram.org/bot{TG_BOT_TOKEN}/sendMessage\u0026#34; payload = { \u0026#34;chat_id\u0026#34;: TG_CHAT_ID, \u0026#34;text\u0026#34;: text, \u0026#34;parse_mode\u0026#34;: \u0026#34;HTML\u0026#34;, \u0026#34;disable_web_page_preview\u0026#34;: True, } try: async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=10)) as resp: result = await resp.json() if not result.get(\u0026#34;ok\u0026#34;): log.error(f\u0026#34;[TG] Send failed: {result.get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;)}\u0026#34;) # Retry with plain text if HTML parse fails if \u0026#34;parse\u0026#34; in result.get(\u0026#34;description\u0026#34;, \u0026#34;\u0026#34;).lower(): payload[\u0026#34;parse_mode\u0026#34;] = None async with session.post(url, json=payload) as resp2: pass else: log.info(\u0026#34;[TG] Message sent\u0026#34;) except Exception as e: log.error(f\u0026#34;[TG] Error: {e}\u0026#34;) # ============================================================ # Event handling # ============================================================ async def handle_transfer_event(log_entry: dict): \u0026#34;\u0026#34;\u0026#34;Process a single Transfer event\u0026#34;\u0026#34;\u0026#34; tx_hash = log_entry.get(\u0026#34;transactionHash\u0026#34;, \u0026#34;\u0026#34;) token_addr = log_entry.get(\u0026#34;address\u0026#34;, \u0026#34;\u0026#34;) topics = log_entry.get(\u0026#34;topics\u0026#34;, []) data = log_entry.get(\u0026#34;data\u0026#34;, \u0026#34;0x0\u0026#34;) # Dedup dedup_key = f\u0026#34;{tx_hash}:{token_addr}\u0026#34; if dedup_key in seen_txs: return seen_txs.add(dedup_key) seen_txs_list.append(dedup_key) # Cleanup (keep last 1000) while len(seen_txs_list) \u0026gt; 1000: old = seen_txs_list.pop(0) seen_txs.discard(old) # Parse topics: [Transfer, from, to] if len(topics) \u0026lt; 3: return from_addr = topic_to_address(topics[1]) to_addr = topic_to_address(topics[2]) # Confirm it\u0026#39;s from Vitalik if from_addr.lower() != VITALIK_ADDRESS.lower(): return # Get token info token_info = await get_token_info(token_addr) symbol = token_info[\u0026#34;symbol\u0026#34;] decimals = token_info[\u0026#34;decimals\u0026#34;] amount = decode_transfer_value(data, decimals) if amount == 0: return # Classify recipient recipient_type, recipient_name = classify_recipient(to_addr) # If unknown, check if it\u0026#39;s an LP pool is_pool = False if recipient_type == \u0026#34;unknown\u0026#34;: is_pool = await check_if_pool(to_addr) if is_pool: recipient_type = \u0026#34;pool\u0026#34; recipient_name = \u0026#34;LP Pool\u0026#34; # Determine if this is a \u0026#34;sell\u0026#34; action is_sell = recipient_type in (\u0026#34;dex\u0026#34;, \u0026#34;cex\u0026#34;, \u0026#34;pool\u0026#34;) # If not a sell, just a regular transfer — log silently if not is_sell: log.info(f\u0026#34;[Transfer] {symbol} {amount:,.2f} → {shorten_addr(to_addr)} (regular transfer, not notifying)\u0026#34;) return # Get price token_price = await get_token_price_usd(token_addr) usd_value = amount * token_price if token_price else None # Below minimum notification amount — skip if usd_value is not None and usd_value \u0026lt; MIN_NOTIFY_USD: log.info(f\u0026#34;[Sell] {symbol} ${usd_value:.0f} \u0026lt; ${MIN_NOTIFY_USD} minimum, skipping\u0026#34;) return # Build notification message timestamp = datetime.now(timezone.utc).strftime(\u0026#34;%H:%M:%S UTC\u0026#34;) sell_type_label = { \u0026#34;dex\u0026#34;: \u0026#34;🔄 DEX Sell\u0026#34;, \u0026#34;cex\u0026#34;: \u0026#34;🏦 CEX Transfer\u0026#34;, \u0026#34;pool\u0026#34;: \u0026#34;🌊 Pool Sell\u0026#34;, } msg_lines = [ f\u0026#34;\u0026lt;b\u0026gt;🚨 Vitalik Sell Signal\u0026lt;/b\u0026gt;\u0026#34;, f\u0026#34;\u0026#34;, f\u0026#34;Token: \u0026lt;b\u0026gt;{symbol}\u0026lt;/b\u0026gt;\u0026#34;, f\u0026#34;Amount: {amount:,.4f}\u0026#34;, ] if usd_value is not None: msg_lines.append(f\u0026#34;Value: \u0026lt;b\u0026gt;${usd_value:,.0f}\u0026lt;/b\u0026gt;\u0026#34;) if token_price is not None: msg_lines.append(f\u0026#34;Price: ${token_price:,.8f}\u0026#34;) msg_lines.extend([ f\u0026#34;\u0026#34;, f\u0026#34;Type: {sell_type_label.get(recipient_type, \u0026#39;Unknown\u0026#39;)}\u0026#34;, f\u0026#34;Destination: {recipient_name or shorten_addr(to_addr)}\u0026#34;, f\u0026#34;Time: {timestamp}\u0026#34;, f\u0026#34;\u0026#34;, f\u0026#34;TX: https://etherscan.io/tx/{tx_hash}\u0026#34;, f\u0026#34;Token: https://dexscreener.com/ethereum/{token_addr}\u0026#34;, ]) msg = \u0026#34;\\n\u0026#34;.join(msg_lines) log.info(f\u0026#34;[SELL DETECTED] {symbol} {amount:,.4f} → {recipient_name or to_addr} | ${usd_value or \u0026#39;?\u0026#39;}\u0026#34;) await send_telegram(msg) # ============================================================ # WebSocket listener main loop # ============================================================ async def subscribe_and_listen(ws_url: str): \u0026#34;\u0026#34;\u0026#34;Connect to WebSocket and subscribe to Vitalik Transfer events\u0026#34;\u0026#34;\u0026#34; log.info(f\u0026#34;Connecting to {ws_url}...\u0026#34;) async with websockets.connect( ws_url, ping_interval=20, ping_timeout=30, close_timeout=10, max_size=2**20, # 1MB ) as ws: # Subscribe to Transfer FROM Vitalik sub_from = { \u0026#34;jsonrpc\u0026#34;: \u0026#34;2.0\u0026#34;, \u0026#34;id\u0026#34;: 1, \u0026#34;method\u0026#34;: \u0026#34;eth_subscribe\u0026#34;, \u0026#34;params\u0026#34;: [\u0026#34;logs\u0026#34;, { \u0026#34;topics\u0026#34;: [TRANSFER_TOPIC, VITALIK_PADDED] }] } await ws.send(json.dumps(sub_from)) resp = await asyncio.wait_for(ws.recv(), timeout=10) data = json.loads(resp) if \u0026#34;error\u0026#34; in data: raise Exception(f\u0026#34;Subscribe failed: {data[\u0026#39;error\u0026#39;]}\u0026#34;) sub_id_from = data.get(\u0026#34;result\u0026#34;, \u0026#34;\u0026#34;) log.info(f\u0026#34;✅ Subscribed to Transfer FROM Vitalik (id={sub_id_from[:10]}...)\u0026#34;) # Also subscribe to Transfer TO Vitalik (monitor buys/receives) sub_to = { \u0026#34;jsonrpc\u0026#34;: \u0026#34;2.0\u0026#34;, \u0026#34;id\u0026#34;: 2, \u0026#34;method\u0026#34;: \u0026#34;eth_subscribe\u0026#34;, \u0026#34;params\u0026#34;: [\u0026#34;logs\u0026#34;, { \u0026#34;topics\u0026#34;: [TRANSFER_TOPIC, None, VITALIK_PADDED] }] } await ws.send(json.dumps(sub_to)) resp2 = await asyncio.wait_for(ws.recv(), timeout=10) data2 = json.loads(resp2) sub_id_to = data2.get(\u0026#34;result\u0026#34;, \u0026#34;\u0026#34;) if sub_id_to: log.info(f\u0026#34;✅ Subscribed to Transfer TO Vitalik (id={sub_id_to[:10]}...)\u0026#34;) log.info(\u0026#34;🔍 Monitoring Vitalik wallet... waiting for events\u0026#34;) # Listen for events async for message in ws: if not running: break try: evt = json.loads(message) if \u0026#34;params\u0026#34; not in evt: continue sub_id = evt[\u0026#34;params\u0026#34;].get(\u0026#34;subscription\u0026#34;, \u0026#34;\u0026#34;) log_entry = evt[\u0026#34;params\u0026#34;].get(\u0026#34;result\u0026#34;, {}) if sub_id == sub_id_from: # Vitalik sent tokens → check if it\u0026#39;s a sell await handle_transfer_event(log_entry) elif sub_id == sub_id_to: # Vitalik received tokens — log silently token_addr = log_entry.get(\u0026#34;address\u0026#34;, \u0026#34;\u0026#34;) token_info = await get_token_info(token_addr) data_hex = log_entry.get(\u0026#34;data\u0026#34;, \u0026#34;0x0\u0026#34;) amount = decode_transfer_value(data_hex, token_info[\u0026#34;decimals\u0026#34;]) log.debug(f\u0026#34;[Receive] {token_info[\u0026#39;symbol\u0026#39;]} +{amount:,.4f}\u0026#34;) except json.JSONDecodeError: continue except Exception as e: log.error(f\u0026#34;Event handling error: {e}\u0026#34;, exc_info=True) async def main(): \u0026#34;\u0026#34;\u0026#34;Main loop with auto-reconnect\u0026#34;\u0026#34;\u0026#34; global running # Validate required config if not TG_BOT_TOKEN: log.warning(\u0026#34;TG_BOT_TOKEN not set — Telegram notifications disabled\u0026#34;) if not TG_CHAT_ID: log.warning(\u0026#34;TG_CHAT_ID not set — Telegram notifications disabled\u0026#34;) # Signal handling def shutdown(sig, frame): global running log.info(f\u0026#34;Received signal {sig}, shutting down...\u0026#34;) running = False signal.signal(signal.SIGINT, shutdown) signal.signal(signal.SIGTERM, shutdown) # Startup notice log.info(\u0026#34;=\u0026#34; * 50) log.info(\u0026#34;Vitalik Sell Radar — Started\u0026#34;) log.info(f\u0026#34;Monitoring: {VITALIK_ADDRESS}\u0026#34;) log.info(f\u0026#34;Min notify: ${MIN_NOTIFY_USD}\u0026#34;) log.info(f\u0026#34;Telegram: {\u0026#39;✅ Configured\u0026#39; if TG_BOT_TOKEN else \u0026#39;❌ Not configured\u0026#39;}\u0026#34;) log.info(\u0026#34;=\u0026#34; * 50) if TG_BOT_TOKEN and TG_CHAT_ID: await send_telegram( \u0026#34;🟢 Vitalik Sell Radar — Started\\n\\n\u0026#34; f\u0026#34;Monitoring: {shorten_addr(VITALIK_ADDRESS)}\\n\u0026#34; f\u0026#34;Min notify: ${MIN_NOTIFY_USD}\\n\u0026#34; \u0026#34;Mode: WebSocket event-driven (sub-second latency)\u0026#34; ) endpoint_idx = 0 reconnect_delay = RECONNECT_DELAY while running: ws_url = WS_ENDPOINTS[endpoint_idx % len(WS_ENDPOINTS)] try: await subscribe_and_listen(ws_url) reconnect_delay = RECONNECT_DELAY # reset on success except websockets.exceptions.ConnectionClosed as e: log.warning(f\u0026#34;WebSocket closed: {e}\u0026#34;) except asyncio.TimeoutError: log.warning(\u0026#34;WebSocket timeout\u0026#34;) except Exception as e: log.error(f\u0026#34;WebSocket error: {e}\u0026#34;) if not running: break # Switch endpoint and retry endpoint_idx += 1 log.info(f\u0026#34;Reconnecting in {reconnect_delay}s... (next: {WS_ENDPOINTS[endpoint_idx % len(WS_ENDPOINTS)]})\u0026#34;) await asyncio.sleep(reconnect_delay) reconnect_delay = min(reconnect_delay * 1.5, MAX_RECONNECT_DELAY) # Cleanup if http_session and not http_session.closed: await http_session.close() log.info(\u0026#34;Vitalik Sell Radar — Stopped\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: asyncio.run(main()) On-Chain Narrative Radar (Radar tường thuật on-chain) Ngày: 2026.04.28　Thẻ: Python · GMGN · DEXScreener · Telegram\nTheo động lượng · Phát hiện token on-chain mới · ETH/SOL/BSC/Base\nĐộng lượng là động cơ đẩy duy nhất — tường thuật chỉ là nhãn phân loại. Quét 30 giây mỗi lần trên 4 chain. Token phải tăng vốn hóa 3 lần liên tiếp với tổng tăng ≥5% mới kích hoạt cảnh báo. Tường thuật (Musk/Trump, Binance/CZ, người nổi tiếng) được phân loại ★★★/★★/★ nhưng không tự kích hoạt push. Kiểm tra an toàn: SOL dùng RugCheck, EVM dùng GoPlus. Python thuần, chi phí AI = 0.\nMã nguồn đầy đủ #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; 叙事雷达 → 链上雷达 v1 纯Python，零AI成本（关键词匹配 + 叙事去重） 三条推送通道： 1. 全新叙事 — 从未见过的概念/故事，全链推 2. 马斯克/川普相关 — 重点ETH+SOL，BSC也推 3. 币安/CZ相关 — 只推BSC 数据源：GMGN新币 + DEXScreener搜索 叙事历史：SQLite去重 \u0026#34;\u0026#34;\u0026#34; import requests import json import time import os import re import sqlite3 import hashlib from datetime import datetime, timedelta from pathlib import Path from difflib import SequenceMatcher # === 配置 === DATA_DIR = os.path.expanduser(\u0026#34;~/crypto-trading\u0026#34;) DB_FILE = os.path.join(DATA_DIR, \u0026#34;narrative_history.db\u0026#34;) LOG_FILE = os.path.join(DATA_DIR, \u0026#34;narrative_radar.log\u0026#34;) SEEN_FILE = os.path.join(DATA_DIR, \u0026#34;narrative_seen.json\u0026#34;) FLAP_SEEN_FILE = os.path.join(DATA_DIR, \u0026#34;flap_seen.json\u0026#34;) # 扫描间隔 SCAN_INTERVAL = 30 # 30秒（GMGN数据约1-5分钟刷新一次，10秒太频繁且数据不变） # 动量追踪器 — 内存中记录每个币的价格/市值快照 # {address: [{\u0026#39;ts\u0026#39;: timestamp, \u0026#39;mc\u0026#39;: market_cap, \u0026#39;vol\u0026#39;: volume, \u0026#39;price\u0026#39;: price}, ...]} MOMENTUM_TRACKER = {} MOMENTUM_PUSHED = {} # {address: {\u0026#39;count\u0026#39;: N, \u0026#39;last_ts\u0026#39;: ts, \u0026#39;last_mc\u0026#39;: mc}} 推送计数 MOMENTUM_CONSECUTIVE_UP = 3 # 连续涨3轮（数据实际变化时才算一轮） # 从.env读取TG配置 def load_env(): env = {} env_file = os.path.expanduser(\u0026#34;~/.env\u0026#34;) if os.path.exists(env_file): with open(env_file) as f: for line in f: line = line.strip() if \u0026#39;=\u0026#39; in line and not line.startswith(\u0026#39;#\u0026#39;): k, v = line.split(\u0026#39;=\u0026#39;, 1) env[k] = v return env ENV = load_env() TG_TOKEN = ENV.get(\u0026#39;TELEGRAM_BOT_TOKEN\u0026#39;, \u0026#39;\u0026#39;) TG_CHAT_ID = int(os.environ.get(\u0026#39;TG_CHAT_ID\u0026#39;, \u0026#39;0\u0026#39;)) GMGN_HEADERS = { \u0026#39;User-Agent\u0026#39;: \u0026#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36\u0026#39;, \u0026#39;Accept\u0026#39;: \u0026#39;application/json\u0026#39;, \u0026#39;Referer\u0026#39;: \u0026#39;https://gmgn.ai/\u0026#39;, } # ============================================================ # 马斯克/川普关键词库（大小写不敏感） # ============================================================ MUSK_TRUMP_KEYWORDS = { # 马斯克核心 \u0026#39;musk\u0026#39;, \u0026#39;elon\u0026#39;, \u0026#39;elonmusk\u0026#39;, # SpaceX/Tesla/X \u0026#39;spacex\u0026#39;, \u0026#39;starship\u0026#39;, \u0026#39;tesla\u0026#39;, \u0026#39;cybertruck\u0026#39;, \u0026#39;roadster\u0026#39;, \u0026#39;neuralink\u0026#39;, \u0026#39;boring\u0026#39;, \u0026#39;hyperloop\u0026#39;, \u0026#39;xai\u0026#39;, \u0026#39;grok\u0026#39;, # 马斯克相关人物/宠物/梗 \u0026#39;floki\u0026#39;, \u0026#39;shiba\u0026#39;, # 只在新币上下文中用 \u0026#39;doge father\u0026#39;, \u0026#39;dogefather\u0026#39;, \u0026#39;technoking\u0026#39;, \u0026#39;mars colony\u0026#39;, \u0026#39;mars\u0026#39;, # 川普核心 \u0026#39;trump\u0026#39;, \u0026#39;donald\u0026#39;, \u0026#39;maga\u0026#39;, \u0026#39;potus\u0026#39;, \u0026#39;trump47\u0026#39;, \u0026#39;melania\u0026#39;, \u0026#39;barron\u0026#39;, \u0026#39;ivanka\u0026#39;, # 川普相关 \u0026#39;dark maga\u0026#39;, \u0026#39;darkmaga\u0026#39;, \u0026#39;ultra maga\u0026#39;, \u0026#39;save america\u0026#39;, \u0026#39;truth social\u0026#39;, \u0026#39;covfefe\u0026#39;, # 马斯克+川普联动 \u0026#39;doge department\u0026#39;, \u0026#39;d.o.g.e\u0026#39;, \u0026#39;government efficiency\u0026#39;, } # 马斯克/川普正则（捕捉变体） MUSK_TRUMP_PATTERNS = [ r\u0026#39;\\belon\\b\u0026#39;, r\u0026#39;\\bmusk\\b\u0026#39;, r\u0026#39;\\btrump\\b\u0026#39;, r\u0026#39;\\bmaga\\b\u0026#39;, r\u0026#39;\\bspacex\\b\u0026#39;, r\u0026#39;\\bstarship\\b\u0026#39;, r\u0026#39;\\btesla\\b\u0026#39;, r\u0026#39;\\bgrok\\b\u0026#39;, r\u0026#39;\\bmelania\\b\u0026#39;, r\u0026#39;\\bbarron\\b\u0026#39;, r\u0026#39;\\bdoge\\s*department\\b\u0026#39;, r\u0026#39;\\bd\\.?o\\.?g\\.?e\\b\u0026#39;, # D.O.G.E变体 r\u0026#39;\\bx\\s*ai\\b\u0026#39;, r\u0026#39;\\bneuralink\\b\u0026#39;, ] # ============================================================ # 币安/CZ关键词库 # ============================================================ BINANCE_CZ_KEYWORDS = { # CZ核心 \u0026#39;cz\u0026#39;, \u0026#39;changpeng\u0026#39;, \u0026#39;zhao\u0026#39;, \u0026#39;czb\u0026#39;, \u0026#39;czbinance\u0026#39;, # 何一（BSC现在的核心推手！） \u0026#39;heyi\u0026#39;, \u0026#39;yi he\u0026#39;, \u0026#39;he yi\u0026#39;, \u0026#39;何一\u0026#39;, \u0026#39;yihe\u0026#39;, \u0026#39;sister yi\u0026#39;, \u0026#39;yi jie\u0026#39;, \u0026#39;一姐\u0026#39;, \u0026#39;何一姐\u0026#39;, # 币安品牌 \u0026#39;binance\u0026#39;, \u0026#39;bnb\u0026#39;, \u0026#39;pancake\u0026#39;, \u0026#39;pancakeswap\u0026#39;, # CZ相关动态词（书、活动、推特高频词） \u0026#39;giggle academy\u0026#39;, \u0026#39;binance life\u0026#39;, \u0026#39;bnb chain\u0026#39;, \u0026#39;principles\u0026#39;, \u0026#39;cz book\u0026#39;, # YZi Labs (原Binance Labs) \u0026#39;yzi\u0026#39;, \u0026#39;yzi labs\u0026#39;, # 中文关键词（BSC上常见） \u0026#39;赵长鹏\u0026#39;, \u0026#39;币安\u0026#39;, \u0026#39;长鹏\u0026#39;, \u0026#39;cz的\u0026#39;, \u0026#39;何一的\u0026#39;, # Four.meme平台相关 \u0026#39;fourmeme\u0026#39;, \u0026#39;four meme\u0026#39;, \u0026#39;4meme\u0026#39;, # CZ/何一推特互动高频词 \u0026#39;czs dog\u0026#39;, \u0026#39;cz dog\u0026#39;, \u0026#39;bnb dog\u0026#39;, \u0026#39;build on bnb\u0026#39;, \u0026#39;bnb ecosystem\u0026#39;, } BINANCE_CZ_PATTERNS = [ r\u0026#39;\\bcz\\b\u0026#39;, r\u0026#39;\\bbinance\\b\u0026#39;, r\u0026#39;\\bbnb\\b\u0026#39;, r\u0026#39;\\bheyi\\b\u0026#39;, r\u0026#39;\\byi\\s*he\\b\u0026#39;, r\u0026#39;\\bhe\\s*yi\\b\u0026#39;, r\u0026#39;\\b何一\\b\u0026#39;, r\u0026#39;\\b一姐\\b\u0026#39;, r\u0026#39;\\bpancake\\b\u0026#39;, r\u0026#39;\\bgiggle\\b\u0026#39;, r\u0026#39;\\byzi\\b\u0026#39;, r\u0026#39;\\bfourmeme\\b\u0026#39;, r\u0026#39;\\b4meme\\b\u0026#39;, ] # ============================================================ # 推特热点/名人关键词库（★★级别） # ============================================================ CELEBRITY_VIRAL_KEYWORDS = { # 科技名人 \u0026#39;vitalik\u0026#39;, \u0026#39;buterin\u0026#39;, \u0026#39;sam altman\u0026#39;, \u0026#39;satoshi\u0026#39;, \u0026#39;michael saylor\u0026#39;, \u0026#39;saylor\u0026#39;, \u0026#39;cathie wood\u0026#39;, \u0026#39;jack dorsey\u0026#39;, \u0026#39;zuckerberg\u0026#39;, \u0026#39;bezos\u0026#39;, \u0026#39;jensen huang\u0026#39;, \u0026#39;nvidia\u0026#39;, \u0026#39;tim cook\u0026#39;, # 币圈名人 \u0026#39;justin sun\u0026#39;, \u0026#39;sun yuchen\u0026#39;, \u0026#39;孙宇晨\u0026#39;, \u0026#39;tron\u0026#39;, \u0026#39;arthur hayes\u0026#39;, \u0026#39;su zhu\u0026#39;, \u0026#39;3ac\u0026#39;, \u0026#39;brian armstrong\u0026#39;, \u0026#39;coinbase\u0026#39;, \u0026#39;larry fink\u0026#39;, \u0026#39;blackrock\u0026#39;, \u0026#39;gary gensler\u0026#39;, \u0026#39;sec\u0026#39;, \u0026#39;michael novogratz\u0026#39;, \u0026#39;galaxy\u0026#39;, # 政治/社会名人 \u0026#39;biden\u0026#39;, \u0026#39;obama\u0026#39;, \u0026#39;putin\u0026#39;, \u0026#39;xi jinping\u0026#39;, \u0026#39;kanye\u0026#39;, \u0026#39;drake\u0026#39;, \u0026#39;snoop dogg\u0026#39;, \u0026#39;paris hilton\u0026#39;, \u0026#39;mark cuban\u0026#39;, \u0026#39;mr beast\u0026#39;, \u0026#39;mrbeast\u0026#39;, # 病毒式传播热词（龙虾级别的梗） \u0026#39;lobster\u0026#39;, \u0026#39;龙虾\u0026#39;, \u0026#39;lobsta\u0026#39;, \u0026#39;hawk tuah\u0026#39;, \u0026#39;griddy\u0026#39;, \u0026#39;skibidi\u0026#39;, \u0026#39;rizz\u0026#39;, \u0026#39;sigma\u0026#39;, \u0026#39;gyatt\u0026#39;, # 重大事件关键词 \u0026#39;etf\u0026#39;, \u0026#39;halving\u0026#39;, \u0026#39;减半\u0026#39;, \u0026#39;world war\u0026#39;, \u0026#39;wwiii\u0026#39;, \u0026#39;fed\u0026#39;, \u0026#39;rate cut\u0026#39;, \u0026#39;降息\u0026#39;, \u0026#39;tiktok ban\u0026#39;, \u0026#39;tiktok\u0026#39;, } CELEBRITY_VIRAL_PATTERNS = [ r\u0026#39;\\bvitalik\\b\u0026#39;, r\u0026#39;\\bsaylor\\b\u0026#39;, r\u0026#39;\\bblackrock\\b\u0026#39;, r\u0026#39;\\bcoinbase\\b\u0026#39;, r\u0026#39;\\bjustin\\s*sun\\b\u0026#39;, r\u0026#39;\\blobster\\b\u0026#39;, r\u0026#39;\\betf\\b\u0026#39;, r\u0026#39;\\bhalving\\b\u0026#39;, r\u0026#39;\\bmrbeast\\b\u0026#39;, r\u0026#39;\\bsnoop\\b\u0026#39;, r\u0026#39;\\bkanye\\b\u0026#39;, r\u0026#39;\\bdrake\\b\u0026#39;, ] # ============================================================ # 通用垃圾词（过滤明显的骗局/低质量币） # ============================================================ SPAM_PATTERNS = [ r\u0026#39;airdrop\u0026#39;, r\u0026#39;presale\u0026#39;, r\u0026#39;pre\\s*sale\u0026#39;, r\u0026#39;1000x\u0026#39;, r\u0026#39;100x guaranteed\u0026#39;, r\u0026#39;safe\\s*moon\u0026#39;, r\u0026#39;baby\\s*\\w+\u0026#39;, # babydoge等仿盘 r\u0026#39;pornhub\u0026#39;, r\u0026#39;porn\u0026#39;, r\u0026#39;xxx\u0026#39;, r\u0026#39;nsfw\u0026#39;, r\u0026#39;nigga\u0026#39;, r\u0026#39;nigger\u0026#39;, r\u0026#39;faggot\u0026#39;, r\u0026#39;scam\u0026#39;, r\u0026#39;rugpull\u0026#39;, r\u0026#39;rug\\s*pull\u0026#39;, r\u0026#39;official\\s*token\u0026#39;, r\u0026#39;official\\s*coin\u0026#39;, ] # 常见无叙事意义的单词（过滤单词名币） COMMON_NOISE_WORDS = { \u0026#39;nice\u0026#39;, \u0026#39;good\u0026#39;, \u0026#39;bad\u0026#39;, \u0026#39;cool\u0026#39;, \u0026#39;hot\u0026#39;, \u0026#39;big\u0026#39;, \u0026#39;small\u0026#39;, \u0026#39;life\u0026#39;, \u0026#39;love\u0026#39;, \u0026#39;hate\u0026#39;, \u0026#39;happy\u0026#39;, \u0026#39;sad\u0026#39;, \u0026#39;fun\u0026#39;, \u0026#39;lol\u0026#39;, \u0026#39;cat\u0026#39;, \u0026#39;dog\u0026#39;, \u0026#39;moon\u0026#39;, \u0026#39;sun\u0026#39;, \u0026#39;star\u0026#39;, \u0026#39;king\u0026#39;, \u0026#39;queen\u0026#39;, \u0026#39;gold\u0026#39;, \u0026#39;rich\u0026#39;, \u0026#39;cash\u0026#39;, \u0026#39;money\u0026#39;, \u0026#39;pay\u0026#39;, \u0026#39;buy\u0026#39;, \u0026#39;sell\u0026#39;, \u0026#39;pump\u0026#39;, \u0026#39;dump\u0026#39;, \u0026#39;bull\u0026#39;, \u0026#39;bear\u0026#39;, \u0026#39;green\u0026#39;, \u0026#39;red\u0026#39;, \u0026#39;hello\u0026#39;, \u0026#39;world\u0026#39;, \u0026#39;yes\u0026#39;, \u0026#39;no\u0026#39;, \u0026#39;wow\u0026#39;, \u0026#39;omg\u0026#39;, \u0026#39;lmao\u0026#39;, \u0026#39;simp\u0026#39;, \u0026#39;chad\u0026#39;, \u0026#39;based\u0026#39;, \u0026#39;cope\u0026#39;, \u0026#39;seethe\u0026#39;, \u0026#39;test\u0026#39;, \u0026#39;new\u0026#39;, \u0026#39;old\u0026#39;, \u0026#39;real\u0026#39;, \u0026#39;fake\u0026#39;, # 垃圾币名常见词 \u0026#39;shit\u0026#39;, \u0026#39;shitcoin\u0026#39;, \u0026#39;fuck\u0026#39;, \u0026#39;fart\u0026#39;, \u0026#39;poop\u0026#39;, \u0026#39;pee\u0026#39;, \u0026#39;cum\u0026#39;, \u0026#39;dick\u0026#39;, \u0026#39;ass\u0026#39;, \u0026#39;boob\u0026#39;, \u0026#39;tit\u0026#39;, \u0026#39;nigga\u0026#39;, \u0026#39;retard\u0026#39;, \u0026#39;slop\u0026#39;, # 超通用币名 \u0026#39;the\u0026#39;, \u0026#39;and\u0026#39;, \u0026#39;for\u0026#39;, \u0026#39;from\u0026#39;, \u0026#39;with\u0026#39;, \u0026#39;this\u0026#39;, \u0026#39;that\u0026#39;, \u0026#39;coin\u0026#39;, \u0026#39;token\u0026#39;, \u0026#39;meme\u0026#39;, \u0026#39;pepe\u0026#39;, \u0026#39;wojak\u0026#39;, \u0026#39;peg\u0026#39;, \u0026#39;usd\u0026#39;, \u0026#39;usdt\u0026#39;, \u0026#39;usdc\u0026#39;, \u0026#39;dai\u0026#39;, } # ============================================================ # 工具函数 # ============================================================ def log(msg): ts = datetime.now().strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;) line = f\u0026#34;[{ts}] {msg}\u0026#34; print(line) os.makedirs(DATA_DIR, exist_ok=True) with open(LOG_FILE, \u0026#39;a\u0026#39;) as f: f.write(line + \u0026#39;\\n\u0026#39;) def load_flap_seen(): if os.path.exists(FLAP_SEEN_FILE): try: with open(FLAP_SEEN_FILE) as f: return json.load(f) except: pass return {} def save_flap_seen(data): # 只保留7天内的 cutoff = int(time.time()) - 86400 * 7 data = {k: v for k, v in data.items() if v \u0026gt; cutoff} with open(FLAP_SEEN_FILE, \u0026#39;w\u0026#39;) as f: json.dump(data, f) def tg_send(text, parse_mode=\u0026#39;Markdown\u0026#39;): if not TG_TOKEN: log(f\u0026#34;[TG] No token, skip: {text[:80]}\u0026#34;) return False try: resp = requests.post( f\u0026#39;https://api.telegram.org/bot{TG_TOKEN}/sendMessage\u0026#39;, json={\u0026#39;chat_id\u0026#39;: TG_CHAT_ID, \u0026#39;text\u0026#39;: text, \u0026#39;parse_mode\u0026#39;: parse_mode}, timeout=10 ) result = resp.json() if not result.get(\u0026#39;ok\u0026#39;): # Markdown失败时降级到纯文本 if \u0026#39;can\\\u0026#39;t parse\u0026#39; in str(result.get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;)).lower(): resp = requests.post( f\u0026#39;https://api.telegram.org/bot{TG_TOKEN}/sendMessage\u0026#39;, json={\u0026#39;chat_id\u0026#39;: TG_CHAT_ID, \u0026#39;text\u0026#39;: text}, timeout=10 ) else: log(f\u0026#34;[TG] Error: {result.get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;)}\u0026#34;) return False return True except Exception as e: log(f\u0026#34;[TG] Send error: {e}\u0026#34;) return False # ============================================================ # 叙事历史数据库 # ============================================================ def init_db(): \u0026#34;\u0026#34;\u0026#34;初始化SQLite叙事历史库\u0026#34;\u0026#34;\u0026#34; conn = sqlite3.connect(DB_FILE) c = conn.cursor() # 所有见过的叙事主题 c.execute(\u0026#39;\u0026#39;\u0026#39;CREATE TABLE IF NOT EXISTS narratives ( id INTEGER PRIMARY KEY AUTOINCREMENT, theme TEXT NOT NULL, -- 归一化的叙事主题（小写） first_token_name TEXT, -- 第一次出现时的代币名 first_token_address TEXT, -- 第一次出现时的地址 first_chain TEXT, -- 第一次出现的链 first_seen_at INTEGER, -- 第一次看到的时间戳 token_count INTEGER DEFAULT 1, -- 出现过多少次 last_seen_at INTEGER -- 最近一次看到 )\u0026#39;\u0026#39;\u0026#39;) # 所有扫描过的代币 c.execute(\u0026#39;\u0026#39;\u0026#39;CREATE TABLE IF NOT EXISTS tokens_seen ( address TEXT PRIMARY KEY, chain TEXT, name TEXT, symbol TEXT, narrative_theme TEXT, category TEXT, -- \u0026#39;musk_trump\u0026#39; / \u0026#39;binance_cz\u0026#39; / \u0026#39;novel\u0026#39; / \u0026#39;common\u0026#39; first_seen_at INTEGER, market_cap REAL, pushed INTEGER DEFAULT 0, -- 是否已推送 seen_count INTEGER DEFAULT 1 -- 出现次数 )\u0026#39;\u0026#39;\u0026#39;) # 索引 c.execute(\u0026#39;CREATE INDEX IF NOT EXISTS idx_theme ON narratives(theme)\u0026#39;) c.execute(\u0026#39;CREATE INDEX IF NOT EXISTS idx_addr ON tokens_seen(address)\u0026#39;) conn.commit() return conn def normalize_theme(name, symbol): \u0026#34;\u0026#34;\u0026#34; 从代币名称+符号提取归一化的叙事主题 例如：\u0026#39;Elon Mars Colony\u0026#39; → \u0026#39;elon mars colony\u0026#39; \u0026#39;TRUMP2028\u0026#39; → \u0026#39;trump\u0026#39; \u0026#39;PancakeBunny\u0026#39; → \u0026#39;pancake bunny\u0026#39; \u0026#34;\u0026#34;\u0026#34; # 合并name和symbol text = f\u0026#34;{name} {symbol}\u0026#34;.lower().strip() # 去除常见后缀/前缀 noise = [\u0026#39;token\u0026#39;, \u0026#39;coin\u0026#39;, \u0026#39;inu\u0026#39;, \u0026#39;swap\u0026#39;, \u0026#39;finance\u0026#39;, \u0026#39;protocol\u0026#39;, \u0026#39;dao\u0026#39;, \u0026#39;defi\u0026#39;, \u0026#39;nft\u0026#39;, \u0026#39;meta\u0026#39;, \u0026#39;verse\u0026#39;, \u0026#39;fi\u0026#39;, \u0026#39;ai\u0026#39;, \u0026#39;pepe\u0026#39;, \u0026#39;wojak\u0026#39;, \u0026#39;chad\u0026#39;, \u0026#39;based\u0026#39;] # 分割camelCase text = re.sub(r\u0026#39;([a-z])([A-Z])\u0026#39;, r\u0026#39;\\1 \\2\u0026#39;, text) # 去除数字（如2028、1000x） text = re.sub(r\u0026#39;\\d+x?\u0026#39;, \u0026#39;\u0026#39;, text) # 只保留字母和空格 text = re.sub(r\u0026#39;[^a-z\\s]\u0026#39;, \u0026#39; \u0026#39;, text) # 去噪 words = [w for w in text.split() if w and len(w) \u0026gt; 1 and w not in noise] if not words: return name.lower().strip() return \u0026#39; \u0026#39;.join(sorted(set(words))) def is_similar_theme(theme1, theme2, threshold=0.7): \u0026#34;\u0026#34;\u0026#34;模糊匹配两个叙事主题\u0026#34;\u0026#34;\u0026#34; if theme1 == theme2: return True # 子串匹配 if theme1 in theme2 or theme2 in theme1: return True # 词重叠 words1 = set(theme1.split()) words2 = set(theme2.split()) if words1 and words2: overlap = len(words1 \u0026amp; words2) / min(len(words1), len(words2)) if overlap \u0026gt;= 0.6: return True # 序列匹配 return SequenceMatcher(None, theme1, theme2).ratio() \u0026gt;= threshold def check_narrative_novelty(conn, theme, name, symbol, address, chain): \u0026#34;\u0026#34;\u0026#34; 检查叙事状态 返回： (\u0026#39;novel\u0026#39;, None) — 第一次见到 (\u0026#39;heating\u0026#39;, narrative_row) — 短时间内持续出现新币！热点信号！ (\u0026#39;existing\u0026#39;, existing_theme_row) — 已有叙事，不热 核心逻辑：同一主题在30分钟内出现2+个不同的币 = 热点 \u0026#34;\u0026#34;\u0026#34; c = conn.cursor() now = int(time.time()) HEAT_WINDOW = 1800 # 30分钟窗口 HEAT_THRESHOLD = 2 # 窗口内出现2个以上同主题币就是热点 # 精确匹配 c.execute(\u0026#39;SELECT id, theme, first_token_name, first_token_address, first_chain, first_seen_at, token_count, last_seen_at FROM narratives WHERE theme = ?\u0026#39;, (theme,)) exact = c.fetchone() if exact: row_id, _, _, _, _, first_seen, count, last_seen = exact # 更新计数 new_count = count + 1 c.execute(\u0026#39;UPDATE narratives SET token_count = ?, last_seen_at = ? WHERE theme = ?\u0026#39;, (new_count, now, theme)) conn.commit() # 热点判断：在HEAT_WINDOW内出现了多个币 if now - first_seen \u0026lt; HEAT_WINDOW and new_count \u0026gt;= HEAT_THRESHOLD: return (\u0026#39;heating\u0026#39;, exact) # 或者：最近一次和这次间隔很短（说明持续在冒） if now - last_seen \u0026lt; HEAT_WINDOW and new_count \u0026gt;= HEAT_THRESHOLD: return (\u0026#39;heating\u0026#39;, exact) return (\u0026#39;existing\u0026#39;, exact) # 模糊匹配 — 取最近1000个主题比对 c.execute(\u0026#39;SELECT id, theme, first_token_name, first_token_address, first_chain, first_seen_at, token_count, last_seen_at FROM narratives ORDER BY last_seen_at DESC LIMIT 1000\u0026#39;) for row in c.fetchall(): if is_similar_theme(theme, row[1]): row_id, _, _, _, _, first_seen, count, last_seen = row new_count = count + 1 c.execute(\u0026#39;UPDATE narratives SET token_count = ?, last_seen_at = ? WHERE id = ?\u0026#39;, (new_count, now, row[0])) conn.commit() # 热点判断 if now - last_seen \u0026lt; HEAT_WINDOW and new_count \u0026gt;= HEAT_THRESHOLD: return (\u0026#39;heating\u0026#39;, row) return (\u0026#39;existing\u0026#39;, row) # 第一次见到 — 记录 c.execute(\u0026#39;\u0026#39;\u0026#39;INSERT INTO narratives (theme, first_token_name, first_token_address, first_chain, first_seen_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?)\u0026#39;\u0026#39;\u0026#39;, (theme, name, address, chain, now, now)) conn.commit() return (\u0026#39;novel\u0026#39;, None) def get_token_seen_count(conn, address): \u0026#34;\u0026#34;\u0026#34;获取代币出现次数\u0026#34;\u0026#34;\u0026#34; c = conn.cursor() c.execute(\u0026#39;SELECT seen_count FROM tokens_seen WHERE address = ?\u0026#39;, (address,)) row = c.fetchone() return row[0] if row else 0 def is_token_seen(conn, address): \u0026#34;\u0026#34;\u0026#34;检查代币是否已经扫描过\u0026#34;\u0026#34;\u0026#34; c = conn.cursor() c.execute(\u0026#39;SELECT address FROM tokens_seen WHERE address = ?\u0026#39;, (address,)) return c.fetchone() is not None def record_token(conn, address, chain, name, symbol, theme, category, mc, pushed=False): \u0026#34;\u0026#34;\u0026#34;记录已扫描的代币 — 重复出现时计数+1\u0026#34;\u0026#34;\u0026#34; c = conn.cursor() # 检查是否已存在 c.execute(\u0026#39;SELECT seen_count FROM tokens_seen WHERE address = ?\u0026#39;, (address,)) existing = c.fetchone() if existing: # 已存在：计数+1，更新市值 new_count = existing[0] + 1 c.execute(\u0026#39;\u0026#39;\u0026#39;UPDATE tokens_seen SET seen_count = ?, market_cap = ?, category = ? WHERE address = ?\u0026#39;\u0026#39;\u0026#39;, (new_count, mc, category, address)) else: # 新记录 c.execute(\u0026#39;\u0026#39;\u0026#39;INSERT INTO tokens_seen (address, chain, name, symbol, narrative_theme, category, first_seen_at, market_cap, pushed, seen_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1)\u0026#39;\u0026#39;\u0026#39;, (address, chain, name, symbol, theme, category, int(time.time()), mc, 1 if pushed else 0)) conn.commit() # ============================================================ # 叙事分类引擎 # ============================================================ def classify_narrative(name, symbol, chain): \u0026#34;\u0026#34;\u0026#34; 分类代币叙事 返回：(\u0026#39;musk_trump\u0026#39;, matched_keywords) / (\u0026#39;binance_cz\u0026#39;, matched_keywords) / (\u0026#39;novel\u0026#39;, None) / (\u0026#39;common\u0026#39;, None) \u0026#34;\u0026#34;\u0026#34; text = f\u0026#34;{name} {symbol}\u0026#34;.lower() # 1. 检查是否是垃圾币 for pat in SPAM_PATTERNS: if re.search(pat, text, re.IGNORECASE): return (\u0026#39;spam\u0026#39;, None) # 2. 马斯克/川普检测 matched_mt = [] for kw in MUSK_TRUMP_KEYWORDS: if kw.lower() in text: matched_mt.append(kw) if not matched_mt: for pat in MUSK_TRUMP_PATTERNS: m = re.search(pat, text, re.IGNORECASE) if m: matched_mt.append(m.group()) if matched_mt: # 马斯克/川普：重点ETH+SOL，BSC也可以 chain_lower = chain.lower() if chain_lower in (\u0026#39;eth\u0026#39;, \u0026#39;ethereum\u0026#39;, \u0026#39;sol\u0026#39;, \u0026#39;solana\u0026#39;, \u0026#39;bsc\u0026#39;, \u0026#39;base\u0026#39;): return (\u0026#39;musk_trump\u0026#39;, matched_mt) # 3. 币安/CZ检测 — 只在BSC上推 matched_bc = [] for kw in BINANCE_CZ_KEYWORDS: if kw.lower() in text: matched_bc.append(kw) if not matched_bc: for pat in BINANCE_CZ_PATTERNS: m = re.search(pat, text, re.IGNORECASE) if m: matched_bc.append(m.group()) if matched_bc: chain_lower = chain.lower() if chain_lower in (\u0026#39;bsc\u0026#39;,): return (\u0026#39;binance_cz\u0026#39;, matched_bc) else: return (\u0026#39;binance_cz_wrong_chain\u0026#39;, matched_bc) # 4. 名人/推特热点检测（★★级别） matched_cv = [] for kw in CELEBRITY_VIRAL_KEYWORDS: if kw.lower() in text: matched_cv.append(kw) if not matched_cv: for pat in CELEBRITY_VIRAL_PATTERNS: m = re.search(pat, text, re.IGNORECASE) if m: matched_cv.append(m.group()) if matched_cv: return (\u0026#39;celebrity_viral\u0026#39;, matched_cv) # 5. 都不匹配 → 需要进一步检查是否全新叙事 return (\u0026#39;check_novelty\u0026#39;, None) # ============================================================ # 安全检查（复用现有逻辑） # ============================================================ def check_token_safety(chain, address): \u0026#34;\u0026#34;\u0026#34;快速安全检查 — 只拦硬伤（蜜罐/可增发），卖税不作为否决条件\u0026#34;\u0026#34;\u0026#34; if chain in (\u0026#39;sol\u0026#39;, \u0026#39;solana\u0026#39;): try: r = requests.get(f\u0026#39;https://api.rugcheck.xyz/v1/tokens/{address}/report\u0026#39;, timeout=10) if r.status_code == 200: data = r.json() score = data.get(\u0026#39;score\u0026#39;, 999) mint = data.get(\u0026#39;mintAuthority\u0026#39;) freeze = data.get(\u0026#39;freezeAuthority\u0026#39;) return { \u0026#39;safe\u0026#39;: not mint and not freeze, \u0026#39;score\u0026#39;: score, \u0026#39;mint\u0026#39;: mint is not None, \u0026#39;freeze\u0026#39;: freeze is not None } except: pass else: chain_map = {\u0026#39;ethereum\u0026#39;: \u0026#39;1\u0026#39;, \u0026#39;eth\u0026#39;: \u0026#39;1\u0026#39;, \u0026#39;bsc\u0026#39;: \u0026#39;56\u0026#39;, \u0026#39;base\u0026#39;: \u0026#39;8453\u0026#39;} cid = chain_map.get(chain, \u0026#39;1\u0026#39;) try: r = requests.get(f\u0026#39;https://api.gopluslabs.io/api/v1/token_security/{cid}?contract_addresses={address}\u0026#39;, timeout=10) if r.status_code == 200: result = r.json().get(\u0026#39;result\u0026#39;, {}) data = result.get(address.lower(), {}) if data: honeypot = data.get(\u0026#39;is_honeypot\u0026#39;, \u0026#39;0\u0026#39;) == \u0026#39;1\u0026#39; mintable = data.get(\u0026#39;is_mintable\u0026#39;, \u0026#39;0\u0026#39;) == \u0026#39;1\u0026#39; sell_tax = float(data.get(\u0026#39;sell_tax\u0026#39;, \u0026#39;0\u0026#39;) or \u0026#39;0\u0026#39;) buy_tax = float(data.get(\u0026#39;buy_tax\u0026#39;, \u0026#39;0\u0026#39;) or \u0026#39;0\u0026#39;) return { \u0026#39;safe\u0026#39;: not honeypot and not mintable, # 卖税不作为否决 \u0026#39;honeypot\u0026#39;: honeypot, \u0026#39;mintable\u0026#39;: mintable, \u0026#39;sell_tax\u0026#39;: sell_tax, \u0026#39;buy_tax\u0026#39;: buy_tax } except: pass return {\u0026#39;safe\u0026#39;: False, \u0026#39;reason\u0026#39;: \u0026#39;无法检查\u0026#39;} # 无法检查时不推，宁可错过不踩坑 # ============================================================ # GMGN数据获取 # ============================================================ def gmgn_get(url): try: resp = requests.get(url, headers=GMGN_HEADERS, timeout=15) if resp.status_code == 200: return resp.json().get(\u0026#39;data\u0026#39;, {}) except: pass return {} def fetch_token_description(chain, address): \u0026#34;\u0026#34;\u0026#34;获取代币描述/故事 — 叙事雷达核心信息\u0026#34;\u0026#34;\u0026#34; desc = \u0026#39;\u0026#39; # SOL链：Pump.fun有最完整的description if chain in (\u0026#39;sol\u0026#39;, \u0026#39;solana\u0026#39;): try: r = requests.get(f\u0026#39;https://frontend-api-v3.pump.fun/coins/{address}\u0026#39;, timeout=8) if r.status_code == 200: data = r.json() desc = data.get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;) or \u0026#39;\u0026#39; twitter = data.get(\u0026#39;twitter\u0026#39;, \u0026#39;\u0026#39;) or \u0026#39;\u0026#39; telegram = data.get(\u0026#39;telegram\u0026#39;, \u0026#39;\u0026#39;) or \u0026#39;\u0026#39; website = data.get(\u0026#39;website\u0026#39;, \u0026#39;\u0026#39;) or \u0026#39;\u0026#39; return { \u0026#39;description\u0026#39;: desc.strip(), \u0026#39;twitter\u0026#39;: twitter, \u0026#39;telegram\u0026#39;: telegram, \u0026#39;website\u0026#39;: website, } except: pass # 所有链：DEXScreener info字段（网站+社交链接） try: chain_dex = {\u0026#39;sol\u0026#39;: \u0026#39;solana\u0026#39;, \u0026#39;eth\u0026#39;: \u0026#39;ethereum\u0026#39;, \u0026#39;bsc\u0026#39;: \u0026#39;bsc\u0026#39;, \u0026#39;base\u0026#39;: \u0026#39;base\u0026#39;, \u0026#39;solana\u0026#39;: \u0026#39;solana\u0026#39;, \u0026#39;ethereum\u0026#39;: \u0026#39;ethereum\u0026#39;}.get(chain, chain) r = requests.get(f\u0026#39;https://api.dexscreener.com/latest/dex/tokens/{address}\u0026#39;, timeout=8) if r.status_code == 200: pairs = r.json().get(\u0026#39;pairs\u0026#39;, []) if pairs: info = pairs[0].get(\u0026#39;info\u0026#39;, {}) websites = info.get(\u0026#39;websites\u0026#39;, []) socials = info.get(\u0026#39;socials\u0026#39;, []) twitter = \u0026#39;\u0026#39; telegram = \u0026#39;\u0026#39; website = \u0026#39;\u0026#39; for s in socials: if s.get(\u0026#39;type\u0026#39;) == \u0026#39;twitter\u0026#39;: twitter = s.get(\u0026#39;url\u0026#39;, \u0026#39;\u0026#39;) elif s.get(\u0026#39;type\u0026#39;) == \u0026#39;telegram\u0026#39;: telegram = s.get(\u0026#39;url\u0026#39;, \u0026#39;\u0026#39;) for w in websites: if w.get(\u0026#39;label\u0026#39;, \u0026#39;\u0026#39;).lower() == \u0026#39;website\u0026#39;: website = w.get(\u0026#39;url\u0026#39;, \u0026#39;\u0026#39;) if not desc: # DEXScreener没有description但有社交信息 return { \u0026#39;description\u0026#39;: desc, \u0026#39;twitter\u0026#39;: twitter, \u0026#39;telegram\u0026#39;: telegram, \u0026#39;website\u0026#39;: website, } except: pass return {\u0026#39;description\u0026#39;: desc, \u0026#39;twitter\u0026#39;: \u0026#39;\u0026#39;, \u0026#39;telegram\u0026#39;: \u0026#39;\u0026#39;, \u0026#39;website\u0026#39;: \u0026#39;\u0026#39;} def fetch_new_tokens(): \u0026#34;\u0026#34;\u0026#34;从GMGN获取各链新币 + 多维度覆盖\u0026#34;\u0026#34;\u0026#34; all_tokens = [] seen_addrs = set() for chain in [\u0026#39;eth\u0026#39;, \u0026#39;bsc\u0026#39;, \u0026#39;base\u0026#39;]: # 多维度拉数据，避免漏掉 urls = [ # 按创建时间 — 最新的币 f\u0026#39;https://gmgn.ai/defi/quotation/v1/rank/{chain}/swaps/1h?orderby=open_timestamp\u0026amp;direction=desc\u0026amp;limit=100\u0026#39;, # 按交易量 — 最活跃的币 f\u0026#39;https://gmgn.ai/defi/quotation/v1/rank/{chain}/swaps/1h?orderby=swaps\u0026amp;direction=desc\u0026amp;limit=50\u0026#39;, ] for url in urls: data = gmgn_get(url) tokens = data.get(\u0026#39;rank\u0026#39;, []) for t in tokens: addr = t.get(\u0026#39;address\u0026#39;, \u0026#39;\u0026#39;) if not addr or addr in seen_addrs: continue mc = t.get(\u0026#39;market_cap\u0026#39;, 0) or t.get(\u0026#39;fdv\u0026#39;, 0) or 0 liq = t.get(\u0026#39;liquidity\u0026#39;, 0) or 0 # 基本过滤：太小的不看 if mc \u0026lt; 1000 or liq \u0026lt; 500 or mc \u0026gt; 10000000: continue age_ts = t.get(\u0026#39;open_timestamp\u0026#39;, 0) age_h = (time.time() - age_ts) / 3600 if age_ts \u0026gt; 0 else 999 # 不限年龄 — 动量追踪核心逻辑：涨就推，不管新旧 seen_addrs.add(addr) all_tokens.append({ \u0026#39;address\u0026#39;: addr, \u0026#39;chain\u0026#39;: chain, \u0026#39;name\u0026#39;: t.get(\u0026#39;name\u0026#39;, \u0026#39;?\u0026#39;), \u0026#39;symbol\u0026#39;: t.get(\u0026#39;symbol\u0026#39;, \u0026#39;?\u0026#39;), \u0026#39;mc\u0026#39;: mc, \u0026#39;liq\u0026#39;: liq, \u0026#39;volume\u0026#39;: t.get(\u0026#39;volume\u0026#39;, 0) or 0, \u0026#39;holders\u0026#39;: t.get(\u0026#39;holder_count\u0026#39;, 0) or 0, \u0026#39;sm\u0026#39;: t.get(\u0026#39;smart_degen_count\u0026#39;, 0) or 0, \u0026#39;chg_1h\u0026#39;: t.get(\u0026#39;price_change_percent1h\u0026#39;, 0) or 0, \u0026#39;chg_24h\u0026#39;: t.get(\u0026#39;price_change_percent\u0026#39;, 0) or 0, \u0026#39;age_h\u0026#39;: age_h, \u0026#39;price\u0026#39;: t.get(\u0026#39;price\u0026#39;, 0), \u0026#39;buys_1h\u0026#39;: t.get(\u0026#39;buys\u0026#39;, 0) or 0, \u0026#39;sells_1h\u0026#39;: t.get(\u0026#39;sells\u0026#39;, 0) or 0, }) time.sleep(0.3) return all_tokens def fetch_flap_tokens(): \u0026#34;\u0026#34;\u0026#34; FLAP平台扫描 — BSC社区驱动型发射台 找形态：跌下来但有底部支撑（有庄在低位推） 特征：24h跌了，但1h企稳/反弹，买入\u0026gt;卖出，holders在涨 \u0026#34;\u0026#34;\u0026#34; data = gmgn_get( \u0026#39;https://gmgn.ai/defi/quotation/v1/rank/bsc/swaps/24h?launchpad=flap\u0026amp;orderby=volume\u0026amp;direction=desc\u0026amp;limit=30\u0026#39; ) tokens = data.get(\u0026#39;rank\u0026#39;, []) candidates = [] for t in tokens: addr = t.get(\u0026#39;address\u0026#39;, \u0026#39;\u0026#39;) if not addr: continue mc = t.get(\u0026#39;market_cap\u0026#39;, 0) or 0 liq = t.get(\u0026#39;liquidity\u0026#39;, 0) or 0 vol = t.get(\u0026#39;volume\u0026#39;, 0) or 0 holders = t.get(\u0026#39;holder_count\u0026#39;, 0) or 0 buys = t.get(\u0026#39;buys\u0026#39;, 0) or 0 sells = t.get(\u0026#39;sells\u0026#39;, 0) or 0 chg_1h = t.get(\u0026#39;price_change_percent1h\u0026#39;, 0) or 0 chg_24h = t.get(\u0026#39;price_change_percent\u0026#39;, 0) or 0 age_ts = t.get(\u0026#39;open_timestamp\u0026#39;, 0) age_h = (time.time() - age_ts) / 3600 if age_ts \u0026gt; 0 else 0 # 基本门槛 if mc \u0026lt; 1000 or liq \u0026lt; 500: continue if holders \u0026lt; 5: continue # 底部支撑形态判断： # 条件1: 24h跌了（或者涨幅有限），说明不是刚拉的 # 条件2: 1h跌幅小于24h跌幅，说明在企稳 # 条件3: 买入 \u0026gt; 卖出，有人在接 buy_ratio = buys / max(sells, 1) is_support = False reason = \u0026#39;\u0026#39; # 形态A: 24h跌了，1h在企稳/反弹 if chg_24h \u0026lt; -10 and chg_1h \u0026gt; chg_24h * 0.3: is_support = True reason = f\u0026#39;24h跌{chg_24h:.0f}%但1h企稳{chg_1h:+.0f}%\u0026#39; # 形态B: 24h微跌或横盘，1h微涨，买卖比健康 if -10 \u0026lt;= chg_24h \u0026lt;= 30 and chg_1h \u0026gt; -5 and buy_ratio \u0026gt; 1.1: is_support = True reason = f\u0026#39;底部横盘 买卖比{buy_ratio:.2f}\u0026#39; # 形态C: 大跌后强反弹 if chg_24h \u0026lt; -30 and chg_1h \u0026gt; 10: is_support = True reason = f\u0026#39;大跌{chg_24h:.0f}%后反弹{chg_1h:+.0f}%\u0026#39; if is_support and buy_ratio \u0026gt;= 1.0: candidates.append({ \u0026#39;address\u0026#39;: addr, \u0026#39;chain\u0026#39;: \u0026#39;bsc\u0026#39;, \u0026#39;name\u0026#39;: t.get(\u0026#39;name\u0026#39;, \u0026#39;?\u0026#39;), \u0026#39;symbol\u0026#39;: t.get(\u0026#39;symbol\u0026#39;, \u0026#39;?\u0026#39;), \u0026#39;mc\u0026#39;: mc, \u0026#39;liq\u0026#39;: liq, \u0026#39;volume\u0026#39;: vol, \u0026#39;holders\u0026#39;: holders, \u0026#39;sm\u0026#39;: 0, \u0026#39;chg_1h\u0026#39;: chg_1h, \u0026#39;chg_24h\u0026#39;: chg_24h, \u0026#39;age_h\u0026#39;: age_h, \u0026#39;price\u0026#39;: t.get(\u0026#39;price\u0026#39;, 0), \u0026#39;buys\u0026#39;: buys, \u0026#39;sells\u0026#39;: sells, \u0026#39;buy_ratio\u0026#39;: buy_ratio, \u0026#39;support_reason\u0026#39;: reason, \u0026#39;launchpad\u0026#39;: \u0026#39;flap\u0026#39;, }) # 按市值排序 candidates.sort(key=lambda x: x[\u0026#39;mc\u0026#39;], reverse=True) return candidates def format_flap_alert(token, desc_info=None): \u0026#34;\u0026#34;\u0026#34;FLAP低吸信号推送\u0026#34;\u0026#34;\u0026#34; msg = f\u0026#34;链上雷达 — FLAP低吸信号\\n\u0026#34; msg += f\u0026#34;链: BSC | 平台: FLAP\\n\\n\u0026#34; msg += f\u0026#34;{token[\u0026#39;name\u0026#39;]} ({token[\u0026#39;symbol\u0026#39;]})\\n\u0026#34; msg += f\u0026#34;`{token[\u0026#39;address\u0026#39;]}`\\n\\n\u0026#34; # 故事描述 desc = (desc_info or {}).get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;) if desc: if len(desc) \u0026gt; 200: desc = desc[:200] + \u0026#39;...\u0026#39; msg += f\u0026#34;故事: {desc}\\n\\n\u0026#34; msg += f\u0026#34;形态: {token[\u0026#39;support_reason\u0026#39;]}\\n\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; msg += f\u0026#34;市值 ${token[\u0026#39;mc\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;流动性 ${token[\u0026#39;liq\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;24h量 ${token[\u0026#39;volume\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;持有人 {token[\u0026#39;holders\u0026#39;]:\u0026gt;12,d}\\n\u0026#34; msg += f\u0026#34;买/卖 {token[\u0026#39;buys\u0026#39;]:\u0026gt;6,d}/{token[\u0026#39;sells\u0026#39;]:\u0026gt;6,d}\\n\u0026#34; msg += f\u0026#34;买卖比 {token[\u0026#39;buy_ratio\u0026#39;]:\u0026gt;12.2f}\\n\u0026#34; msg += f\u0026#34;1h涨幅 {token[\u0026#39;chg_1h\u0026#39;]:\u0026gt;+11.1f}%\\n\u0026#34; msg += f\u0026#34;24h涨幅 {token[\u0026#39;chg_24h\u0026#39;]:\u0026gt;+11.1f}%\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; msg += \u0026#34;\\nFLAP社区币 — 低吸进场信号\u0026#34; # 社交链接 links = [] if (desc_info or {}).get(\u0026#39;twitter\u0026#39;): links.append(f\u0026#34;\\nTwitter: {desc_info[\u0026#39;twitter\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;telegram\u0026#39;): links.append(f\u0026#34;TG: {desc_info[\u0026#39;telegram\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;website\u0026#39;): links.append(f\u0026#34;Web: {desc_info[\u0026#39;website\u0026#39;]}\u0026#34;) if links: msg += \u0026#39;\\n\u0026#39;.join(links) return msg # ============================================================ # 推送格式 # ============================================================ def format_musk_trump_alert(token, matched_kw, desc_info=None): \u0026#34;\u0026#34;\u0026#34;马斯克/川普叙事推送\u0026#34;\u0026#34;\u0026#34; chain_map = {\u0026#39;sol\u0026#39;: \u0026#39;SOL\u0026#39;, \u0026#39;eth\u0026#39;: \u0026#39;ETH\u0026#39;, \u0026#39;bsc\u0026#39;: \u0026#39;BSC\u0026#39;, \u0026#39;base\u0026#39;: \u0026#39;BASE\u0026#39;} ch = chain_map.get(token[\u0026#39;chain\u0026#39;], token[\u0026#39;chain\u0026#39;].upper()) msg = f\u0026#34;链上雷达 — 马斯克/川普概念\\n\u0026#34; msg += f\u0026#34;链: {ch}\\n\\n\u0026#34; msg += f\u0026#34;{token[\u0026#39;name\u0026#39;]} ({token[\u0026#39;symbol\u0026#39;]})\\n\u0026#34; msg += f\u0026#34;`{token[\u0026#39;address\u0026#39;]}`\\n\\n\u0026#34; # 叙事故事（核心！） desc = (desc_info or {}).get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;) if desc: # 截取前200字符，避免太长 if len(desc) \u0026gt; 200: desc = desc[:200] + \u0026#39;...\u0026#39; msg += f\u0026#34;故事: {desc}\\n\\n\u0026#34; msg += f\u0026#34;命中关键词: {\u0026#39;, \u0026#39;.join(matched_kw[:5])}\\n\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; msg += f\u0026#34;市值 ${token[\u0026#39;mc\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;流动性 ${token[\u0026#39;liq\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;1h涨幅 {token[\u0026#39;chg_1h\u0026#39;]:\u0026gt;+11.1f}%\\n\u0026#34; if token.get(\u0026#39;sm\u0026#39;, 0) \u0026gt; 0: msg += f\u0026#34;聪明钱 {token[\u0026#39;sm\u0026#39;]:\u0026gt;12d}\\n\u0026#34; msg += f\u0026#34;币龄 {token[\u0026#39;age_h\u0026#39;]:\u0026gt;10.1f}h\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; # 社交链接 links = [] if (desc_info or {}).get(\u0026#39;twitter\u0026#39;): links.append(f\u0026#34;Twitter: {desc_info[\u0026#39;twitter\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;telegram\u0026#39;): links.append(f\u0026#34;TG: {desc_info[\u0026#39;telegram\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;website\u0026#39;): links.append(f\u0026#34;Web: {desc_info[\u0026#39;website\u0026#39;]}\u0026#34;) if links: msg += \u0026#39;\\n\u0026#39; + \u0026#39;\\n\u0026#39;.join(links) return msg def format_binance_cz_alert(token, matched_kw, desc_info=None): \u0026#34;\u0026#34;\u0026#34;币安/CZ叙事推送\u0026#34;\u0026#34;\u0026#34; msg = f\u0026#34;链上雷达 — 币安/CZ概念\\n\u0026#34; msg += f\u0026#34;链: BSC\\n\\n\u0026#34; msg += f\u0026#34;{token[\u0026#39;name\u0026#39;]} ({token[\u0026#39;symbol\u0026#39;]})\\n\u0026#34; msg += f\u0026#34;`{token[\u0026#39;address\u0026#39;]}`\\n\\n\u0026#34; # 叙事故事 desc = (desc_info or {}).get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;) if desc: if len(desc) \u0026gt; 200: desc = desc[:200] + \u0026#39;...\u0026#39; msg += f\u0026#34;故事: {desc}\\n\\n\u0026#34; msg += f\u0026#34;命中关键词: {\u0026#39;, \u0026#39;.join(matched_kw[:5])}\\n\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; msg += f\u0026#34;市值 ${token[\u0026#39;mc\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;流动性 ${token[\u0026#39;liq\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;1h涨幅 {token[\u0026#39;chg_1h\u0026#39;]:\u0026gt;+11.1f}%\\n\u0026#34; msg += f\u0026#34;币龄 {token[\u0026#39;age_h\u0026#39;]:\u0026gt;10.1f}h\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; # 社交链接 links = [] if (desc_info or {}).get(\u0026#39;twitter\u0026#39;): links.append(f\u0026#34;Twitter: {desc_info[\u0026#39;twitter\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;telegram\u0026#39;): links.append(f\u0026#34;TG: {desc_info[\u0026#39;telegram\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;website\u0026#39;): links.append(f\u0026#34;Web: {desc_info[\u0026#39;website\u0026#39;]}\u0026#34;) if links: msg += \u0026#39;\\n\u0026#39; + \u0026#39;\\n\u0026#39;.join(links) return msg def format_novel_narrative_alert(token, theme, desc_info=None): \u0026#34;\u0026#34;\u0026#34;全新叙事推送 — 保留备用\u0026#34;\u0026#34;\u0026#34; return format_heating_narrative_alert(token, theme, 1, desc_info) def format_heating_narrative_alert(token, theme, count, desc_info=None): \u0026#34;\u0026#34;\u0026#34;叙事热点推送 — 同主题持续冒新币\u0026#34;\u0026#34;\u0026#34; chain_map = {\u0026#39;sol\u0026#39;: \u0026#39;SOL\u0026#39;, \u0026#39;eth\u0026#39;: \u0026#39;ETH\u0026#39;, \u0026#39;bsc\u0026#39;: \u0026#39;BSC\u0026#39;, \u0026#39;base\u0026#39;: \u0026#39;BASE\u0026#39;} ch = chain_map.get(token[\u0026#39;chain\u0026#39;], token[\u0026#39;chain\u0026#39;].upper()) msg = f\u0026#34;链上雷达 — 叙事热点\\n\u0026#34; msg += f\u0026#34;链: {ch}\\n\\n\u0026#34; msg += f\u0026#34;{token[\u0026#39;name\u0026#39;]} ({token[\u0026#39;symbol\u0026#39;]})\\n\u0026#34; msg += f\u0026#34;`{token[\u0026#39;address\u0026#39;]}`\\n\\n\u0026#34; # 叙事故事 desc = (desc_info or {}).get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;) if desc: if len(desc) \u0026gt; 300: desc = desc[:300] + \u0026#39;...\u0026#39; msg += f\u0026#34;故事: {desc}\\n\\n\u0026#34; else: msg += f\u0026#34;叙事主题: {theme}\\n\\n\u0026#34; msg += f\u0026#34;同类概念已出现{count}个币 — 持续有人做\\n\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; msg += f\u0026#34;市值 ${token[\u0026#39;mc\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;流动性 ${token[\u0026#39;liq\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;1h涨幅 {token[\u0026#39;chg_1h\u0026#39;]:\u0026gt;+11.1f}%\\n\u0026#34; if token.get(\u0026#39;sm\u0026#39;, 0) \u0026gt; 0: msg += f\u0026#34;聪明钱 {token[\u0026#39;sm\u0026#39;]:\u0026gt;12d}\\n\u0026#34; msg += f\u0026#34;持有人 {token[\u0026#39;holders\u0026#39;]:\u0026gt;12d}\\n\u0026#34; msg += f\u0026#34;币龄 {token[\u0026#39;age_h\u0026#39;]:\u0026gt;10.1f}h\\n\u0026#34; msg += f\u0026#34;```\u0026#34; # 社交链接 links = [] if (desc_info or {}).get(\u0026#39;twitter\u0026#39;): links.append(f\u0026#34;\\nTwitter: {desc_info[\u0026#39;twitter\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;telegram\u0026#39;): links.append(f\u0026#34;TG: {desc_info[\u0026#39;telegram\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;website\u0026#39;): links.append(f\u0026#34;Web: {desc_info[\u0026#39;website\u0026#39;]}\u0026#34;) if links: msg += \u0026#39;\\n\u0026#39;.join(links) return msg # ============================================================ # 动量追踪器 — 持续上涨+放量检测 # ============================================================ def track_momentum(tokens): \u0026#34;\u0026#34;\u0026#34; 每轮扫描更新币的快照。 连续多轮市值上涨+成交量增加 = 动量信号，直接推。 \u0026#34;\u0026#34;\u0026#34; global MOMENTUM_TRACKER, MOMENTUM_PUSHED now = time.time() alerts = [] # 当前轮所有地址 current_addrs = set() for token in tokens: addr = token[\u0026#39;address\u0026#39;] mc = token[\u0026#39;mc\u0026#39;] vol = token.get(\u0026#39;volume\u0026#39;, 0) or 0 price = token.get(\u0026#39;price\u0026#39;, 0) or 0 buys = token.get(\u0026#39;buys_1h\u0026#39;, 0) or token.get(\u0026#39;buys\u0026#39;, 0) or 0 current_addrs.add(addr) # 基本门槛 if mc \u0026lt; 1000 or token.get(\u0026#39;liq\u0026#39;, 0) \u0026lt; 500 or mc \u0026gt; 10000000: continue # 记录快照 — 只有数据真正变化时才记录（GMGN有缓存） if addr not in MOMENTUM_TRACKER: MOMENTUM_TRACKER[addr] = [] snapshots = MOMENTUM_TRACKER[addr] # 跳过重复数据（跟上一次完全一样就不记录） if snapshots and snapshots[-1][\u0026#39;mc\u0026#39;] == mc and snapshots[-1][\u0026#39;vol\u0026#39;] == vol: continue # 数据没变，跳过 snapshots.append({ \u0026#39;ts\u0026#39;: now, \u0026#39;mc\u0026#39;: mc, \u0026#39;vol\u0026#39;: vol, \u0026#39;price\u0026#39;: price, \u0026#39;buys\u0026#39;: buys, }) # 只保留最近20个快照（约200秒） if len(snapshots) \u0026gt; 20: snapshots[:] = snapshots[-20:] # 至少需要3个快照才能判断 if len(snapshots) \u0026lt; MOMENTUM_CONSECUTIVE_UP: continue # 检测最近N轮是否持续涨 recent = snapshots[-MOMENTUM_CONSECUTIVE_UP:] consecutive_up = True total_gain = 0 for i in range(1, len(recent)): prev_mc = recent[i-1][\u0026#39;mc\u0026#39;] curr_mc = recent[i][\u0026#39;mc\u0026#39;] if prev_mc \u0026lt;= 0: consecutive_up = False break gain = (curr_mc - prev_mc) / prev_mc if gain \u0026lt;= 0: # 任何一轮没涨就不算 consecutive_up = False break total_gain += gain if not consecutive_up: continue # 连续涨了！检查放量（成交量在增） vol_increasing = True for i in range(1, len(recent)): if recent[i][\u0026#39;buys\u0026#39;] \u0026lt; recent[i-1][\u0026#39;buys\u0026#39;] * 0.8: # 允许小幅波动 vol_increasing = False break # 计算总涨幅 first_mc = recent[0][\u0026#39;mc\u0026#39;] last_mc = recent[-1][\u0026#39;mc\u0026#39;] pct_gain = ((last_mc - first_mc) / first_mc * 100) if first_mc \u0026gt; 0 else 0 # 推送条件：连续涨 + 涨幅\u0026gt;5% if pct_gain \u0026lt; 5: continue # 信号计数：同一个币每次触发信号，计数+1 push_info = MOMENTUM_PUSHED.get(addr, {\u0026#39;count\u0026#39;: 0, \u0026#39;last_ts\u0026#39;: 0, \u0026#39;last_mc\u0026#39;: 0}) # 必须比上次推送时市值还高才推（真的还在涨） if push_info[\u0026#39;count\u0026#39;] \u0026gt; 0 and last_mc \u0026lt;= push_info[\u0026#39;last_mc\u0026#39;]: continue push_info[\u0026#39;count\u0026#39;] += 1 push_info[\u0026#39;last_ts\u0026#39;] = now push_info[\u0026#39;last_mc\u0026#39;] = last_mc signal_count = push_info[\u0026#39;count\u0026#39;] # 安全检查 safety = check_token_safety(token[\u0026#39;chain\u0026#39;], addr) if not safety.get(\u0026#39;safe\u0026#39;): continue # 叙事分类 → 星级评分 category, matched_kw = classify_narrative(token[\u0026#39;name\u0026#39;], token[\u0026#39;symbol\u0026#39;], token[\u0026#39;chain\u0026#39;]) is_flap = token.get(\u0026#39;launchpad\u0026#39;) == \u0026#39;flap\u0026#39; if category == \u0026#39;musk_trump\u0026#39;: stars = 3 narrative_tag = f\u0026#34;马斯克/川普概念 ({\u0026#39;, \u0026#39;.join(matched_kw[:3])})\u0026#34; elif category == \u0026#39;binance_cz\u0026#39;: stars = 3 narrative_tag = f\u0026#34;币安/CZ概念 ({\u0026#39;, \u0026#39;.join(matched_kw[:3])})\u0026#34; elif category == \u0026#39;celebrity_viral\u0026#39;: stars = 2 narrative_tag = f\u0026#34;名人/热点 ({\u0026#39;, \u0026#39;.join(matched_kw[:3])})\u0026#34; elif is_flap: stars = 2 narrative_tag = \u0026#34;FLAP社区币\u0026#34; else: # 检查是否全新叙事 theme = normalize_theme(token[\u0026#39;name\u0026#39;], token[\u0026#39;symbol\u0026#39;]) theme_words = [w for w in theme.split() if w not in COMMON_NOISE_WORDS and len(w) \u0026gt; 2] if len(theme_words) \u0026gt;= 2: stars = 2 narrative_tag = f\u0026#34;叙事: {theme}\u0026#34; else: stars = 1 narrative_tag = \u0026#34;无明确叙事\u0026#34; # 生成推送 desc_info = fetch_token_description(token[\u0026#39;chain\u0026#39;], addr) # FLAP币额外标注社区/CTO信息 if is_flap: has_twitter = bool(desc_info.get(\u0026#39;twitter\u0026#39;)) has_tg = bool(desc_info.get(\u0026#39;telegram\u0026#39;)) has_web = bool(desc_info.get(\u0026#39;website\u0026#39;)) community_tags = [] if has_twitter: community_tags.append(\u0026#34;有推特\u0026#34;) if has_tg: community_tags.append(\u0026#34;有TG群\u0026#34;) if has_web: community_tags.append(\u0026#34;有官网\u0026#34;) if community_tags: narrative_tag += f\u0026#34; | {\u0026#39; \u0026#39;.join(community_tags)}\u0026#34; stars = min(3, stars + 1) # 有社区加一星 else: narrative_tag += \u0026#34; | 无社区链接\u0026#34; msg = format_momentum_alert(token, pct_gain, len(recent), vol_increasing, stars, narrative_tag, desc_info, signal_count) alerts.append({\u0026#39;msg\u0026#39;: msg, \u0026#39;token\u0026#39;: token}) MOMENTUM_PUSHED[addr] = push_info log(f\u0026#34;[动量信号{signal_count}] {token[\u0026#39;name\u0026#39;]} ({token[\u0026#39;symbol\u0026#39;]}) on {token[\u0026#39;chain\u0026#39;]} — 连涨{len(recent)}轮 +{pct_gain:.1f}%\u0026#34;) # 清理不再出现的币 stale = [a for a in MOMENTUM_TRACKER if a not in current_addrs] for a in stale: if now - MOMENTUM_TRACKER[a][-1][\u0026#39;ts\u0026#39;] \u0026gt; 600: # 10分钟没出现就清理 del MOMENTUM_TRACKER[a] # 清理推送记录 — 1小时没出现的清掉 MOMENTUM_PUSHED = {k: v for k, v in MOMENTUM_PUSHED.items() if now - v.get(\u0026#39;last_ts\u0026#39;, 0) \u0026lt; 3600} return alerts def format_momentum_alert(token, pct_gain, rounds, vol_up, stars, narrative_tag, desc_info=None, seen_count=0): \u0026#34;\u0026#34;\u0026#34;持续上涨动量推送 — 带叙事星级\u0026#34;\u0026#34;\u0026#34; chain_map = {\u0026#39;sol\u0026#39;: \u0026#39;SOL\u0026#39;, \u0026#39;eth\u0026#39;: \u0026#39;ETH\u0026#39;, \u0026#39;bsc\u0026#39;: \u0026#39;BSC\u0026#39;, \u0026#39;base\u0026#39;: \u0026#39;BASE\u0026#39;} ch = chain_map.get(token[\u0026#39;chain\u0026#39;], token[\u0026#39;chain\u0026#39;].upper()) vol_tag = \u0026#34;放量\u0026#34; if vol_up else \u0026#34;\u0026#34; star_str = \u0026#34;★\u0026#34; * stars + \u0026#34;☆\u0026#34; * (3 - stars) # 信号编号从标题移到下面 msg = f\u0026#34;链上雷达\\n\u0026#34; msg += f\u0026#34;链: {ch}\\n\\n\u0026#34; msg += f\u0026#34;{token[\u0026#39;name\u0026#39;]} ({token[\u0026#39;symbol\u0026#39;]})\\n\u0026#34; msg += f\u0026#34;`{token[\u0026#39;address\u0026#39;]}`\\n\\n\u0026#34; # 故事描述 desc = (desc_info or {}).get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;) if desc: if len(desc) \u0026gt; 200: desc = desc[:200] + \u0026#39;...\u0026#39; msg += f\u0026#34;故事: {desc}\\n\\n\u0026#34; msg += f\u0026#34;叙事: {narrative_tag}\\n\u0026#34; msg += f\u0026#34;连涨{rounds}轮 +{pct_gain:.1f}% {vol_tag}\\n\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; msg += f\u0026#34;市值 ${token[\u0026#39;mc\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;流动性 ${token[\u0026#39;liq\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;1h涨幅 {token[\u0026#39;chg_1h\u0026#39;]:\u0026gt;+11.1f}%\\n\u0026#34; if token.get(\u0026#39;sm\u0026#39;, 0) \u0026gt; 0: msg += f\u0026#34;聪明钱 {token[\u0026#39;sm\u0026#39;]:\u0026gt;12d}\\n\u0026#34; msg += f\u0026#34;币龄 {token[\u0026#39;age_h\u0026#39;]:\u0026gt;10.1f}h\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; msg += f\u0026#34;评星: {star_str} 出现次数: {seen_count}\u0026#34; # 社交链接 links = [] if (desc_info or {}).get(\u0026#39;twitter\u0026#39;): links.append(f\u0026#34;\\nTwitter: {desc_info[\u0026#39;twitter\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;telegram\u0026#39;): links.append(f\u0026#34;TG: {desc_info[\u0026#39;telegram\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;website\u0026#39;): links.append(f\u0026#34;Web: {desc_info[\u0026#39;website\u0026#39;]}\u0026#34;) if links: msg += \u0026#39;\\n\u0026#39;.join(links) return msg def format_celebrity_alert(token, matched_kw, desc_info=None): \u0026#34;\u0026#34;\u0026#34;名人/推特热点推送 ★★\u0026#34;\u0026#34;\u0026#34; chain_map = {\u0026#39;sol\u0026#39;: \u0026#39;SOL\u0026#39;, \u0026#39;eth\u0026#39;: \u0026#39;ETH\u0026#39;, \u0026#39;bsc\u0026#39;: \u0026#39;BSC\u0026#39;, \u0026#39;base\u0026#39;: \u0026#39;BASE\u0026#39;} ch = chain_map.get(token[\u0026#39;chain\u0026#39;], token[\u0026#39;chain\u0026#39;].upper()) msg = f\u0026#34;链上雷达 — 名人/热点 ★★\\n\u0026#34; msg += f\u0026#34;链: {ch}\\n\\n\u0026#34; msg += f\u0026#34;{token[\u0026#39;name\u0026#39;]} ({token[\u0026#39;symbol\u0026#39;]})\\n\u0026#34; msg += f\u0026#34;`{token[\u0026#39;address\u0026#39;]}`\\n\\n\u0026#34; desc = (desc_info or {}).get(\u0026#39;description\u0026#39;, \u0026#39;\u0026#39;) if desc: if len(desc) \u0026gt; 200: desc = desc[:200] + \u0026#39;...\u0026#39; msg += f\u0026#34;故事: {desc}\\n\\n\u0026#34; msg += f\u0026#34;命中关键词: {\u0026#39;, \u0026#39;.join(matched_kw[:5])}\\n\\n\u0026#34; msg += f\u0026#34;```\\n\u0026#34; msg += f\u0026#34;市值 ${token[\u0026#39;mc\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;流动性 ${token[\u0026#39;liq\u0026#39;]:\u0026gt;12,.0f}\\n\u0026#34; msg += f\u0026#34;1h涨幅 {token[\u0026#39;chg_1h\u0026#39;]:\u0026gt;+11.1f}%\\n\u0026#34; if token.get(\u0026#39;sm\u0026#39;, 0) \u0026gt; 0: msg += f\u0026#34;聪明钱 {token[\u0026#39;sm\u0026#39;]:\u0026gt;12d}\\n\u0026#34; msg += f\u0026#34;币龄 {token[\u0026#39;age_h\u0026#39;]:\u0026gt;10.1f}h\\n\u0026#34; msg += f\u0026#34;```\u0026#34; links = [] if (desc_info or {}).get(\u0026#39;twitter\u0026#39;): links.append(f\u0026#34;\\nTwitter: {desc_info[\u0026#39;twitter\u0026#39;]}\u0026#34;) if (desc_info or {}).get(\u0026#39;telegram\u0026#39;): links.append(f\u0026#34;TG: {desc_info[\u0026#39;telegram\u0026#39;]}\u0026#34;) if links: msg += \u0026#39;\\n\u0026#39;.join(links) return msg # ============================================================ # 核心扫描逻辑 # ============================================================ def scan_narratives(): \u0026#34;\u0026#34;\u0026#34;主扫描函数\u0026#34;\u0026#34;\u0026#34; conn = init_db() tokens = fetch_new_tokens() log(f\u0026#34;扫描 {len(tokens)} 个新币...\u0026#34;) # === 动量追踪 — 每轮更新所有币的快照，检测持续上涨 === # 拉FLAP币一起喂进动量追踪器 flap_tokens = [] try: flap_tokens = fetch_flap_tokens() except: pass all_momentum_tokens = tokens + flap_tokens momentum_alerts = track_momentum(all_momentum_tokens) for token in tokens: addr = token[\u0026#39;address\u0026#39;] chain = token[\u0026#39;chain\u0026#39;] name = token[\u0026#39;name\u0026#39;] symbol = token[\u0026#39;symbol\u0026#39;] # 已扫描过的 — 更新seen_count和narratives的token_count，但不重复推 if is_token_seen(conn, addr): # 更新seen_count c = conn.cursor() c.execute(\u0026#39;UPDATE tokens_seen SET seen_count = seen_count + 1, market_cap = ? WHERE address = ?\u0026#39;, (token[\u0026#39;mc\u0026#39;], addr)) # 更新narratives表的token_count（按主题） theme_tmp = normalize_theme(name, symbol) if theme_tmp: c.execute(\u0026#39;UPDATE narratives SET token_count = token_count + 1, last_seen_at = ? WHERE theme = ?\u0026#39;, (int(time.time()), theme_tmp)) conn.commit() continue # 分类叙事 category, matched_kw = classify_narrative(name, symbol, chain) if category == \u0026#39;spam\u0026#39;: record_token(conn, addr, chain, name, symbol, \u0026#39;\u0026#39;, \u0026#39;spam\u0026#39;, token[\u0026#39;mc\u0026#39;]) continue # 基本质量门槛（防止推太多垃圾） min_mc = 1000 min_liq = 500 if token[\u0026#39;mc\u0026#39;] \u0026lt; min_mc or token[\u0026#39;liq\u0026#39;] \u0026lt; min_liq: record_token(conn, addr, chain, name, symbol, \u0026#39;\u0026#39;, \u0026#39;too_small\u0026#39;, token[\u0026#39;mc\u0026#39;]) continue theme = normalize_theme(name, symbol) # 所有分类只记录，不直接推送 — 推送统一走动量引擎 record_token(conn, addr, chain, name, symbol, theme, category, token[\u0026#39;mc\u0026#39;]) check_narrative_novelty(conn, theme, name, symbol, addr, chain) conn.close() # === 推送动量信号 === pushed = 0 for ma in momentum_alerts[:8]: # 单轮最多推8个 if tg_send(ma[\u0026#39;msg\u0026#39;]): pushed += 1 time.sleep(1) # 避免TG限流 return pushed, len(momentum_alerts) # ============================================================ # 主循环 # ============================================================ def main(): log(\u0026#34;=\u0026#34; * 50) log(\u0026#34;链上雷达 v1 启动\u0026#34;) log(f\u0026#34;扫描间隔: {SCAN_INTERVAL}s\u0026#34;) log(f\u0026#34;推送逻辑: 动量优先 — 连涨才推，叙事只做分类标签\u0026#34;) log(\u0026#34;=\u0026#34; * 50) # 初始化DB init_db() # 启动通知 tg_send( \u0026#34;链上雷达 v1 已启动\\n\\n\u0026#34; \u0026#34;核心逻辑: 动量优先\\n\u0026#34; \u0026#34;连涨3轮+涨幅\u0026gt;5%才推送\\n\u0026#34; \u0026#34;叙事只做分类标签:\\n\u0026#34; \u0026#34;★★★ 马斯克/川普 | 币安/CZ | FLAP有社区\\n\u0026#34; \u0026#34;★★ 名人热点 | FLAP无社区 | 有叙事\\n\u0026#34; \u0026#34;★ 无明确叙事\\n\\n\u0026#34; f\u0026#34;扫描频率: 每{SCAN_INTERVAL}秒\u0026#34; ) scan_count = 0 total_pushed = 0 while True: try: scan_count += 1 pushed, found = scan_narratives() total_pushed += pushed if pushed \u0026gt; 0: log(f\u0026#34;第{scan_count}轮: 发现{found}个, 推送{pushed}个 (累计推送{total_pushed})\u0026#34;) else: if scan_count % 20 == 0: # 每20轮报一次无信号 log(f\u0026#34;第{scan_count}轮: 无新信号 (累计推送{total_pushed})\u0026#34;) except Exception as e: log(f\u0026#34;扫描异常: {e}\u0026#34;) time.sleep(SCAN_INTERVAL) if __name__ == \u0026#39;__main__\u0026#39;: main() Bộ quét OI + Funding Rate Ngày: 2026.04.25　Thẻ: Python · Binance Futures · Telegram\nPhát hiện funding rate đổi dấu + OI tăng mạnh\nScanner dựa trên snapshot: phát hiện funding rate chuyển từ dương sang âm trong khi OI đang tăng. Chạy mỗi 5 phút.\nMã nguồn đầy đủ #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; OI持续放大 + 费率由正转负 扫描器 - 每分钟运行一次 - 检测: OI持续放大(4段递增, 总涨幅\u0026gt;8%) + 费率由正转负 - 去重: 同一币种24小时内只推一次 - 纯API零成本 \u0026#34;\u0026#34;\u0026#34; import requests import json import os import time import sys from datetime import datetime, timedelta from pathlib import Path # ============ 配置 ============ SCRIPT_DIR = Path(__file__).parent ENV_FILE = SCRIPT_DIR / \u0026#34;.env.oi\u0026#34; ALERT_HISTORY_FILE = SCRIPT_DIR / \u0026#34;oi_funding_alerts.json\u0026#34; FR_SNAPSHOT_FILE = SCRIPT_DIR / \u0026#34;fr_snapshot.json\u0026#34; # 上一次费率快照 # 信号参数 MIN_OI_CHANGE_PCT = 8 # OI总涨幅最低8% MIN_VOLUME_USDT = 0 # 无门槛，全扫 MIN_FR_PERIODS_POSITIVE = 2 # 转负前至少2期为正 DEDUP_HOURS = 24 # 去重窗口24小时 # ============ 加载TG配置 ============ def load_env(): env = {} if ENV_FILE.exists(): for line in ENV_FILE.read_text().strip().split(\u0026#39;\\n\u0026#39;): if \u0026#39;=\u0026#39; in line and not line.startswith(\u0026#39;#\u0026#39;): k, v = line.split(\u0026#39;=\u0026#39;, 1) env[k.strip()] = v.strip() return env env = load_env() TG_BOT_TOKEN = env.get(\u0026#39;TG_BOT_TOKEN\u0026#39;, \u0026#39;\u0026#39;) TG_CHAT_ID = env.get(\u0026#39;TG_CHAT_ID\u0026#39;, \u0026#39;\u0026#39;) # ============ TG推送 ============ def send_tg(text): if not TG_BOT_TOKEN or not TG_CHAT_ID: print(\u0026#34;[TG] 未配置, 仅打印:\u0026#34;) print(text) return url = f\u0026#34;https://api.telegram.org/bot{TG_BOT_TOKEN}/sendMessage\u0026#34; # 分段发送(TG限制4096字) chunks = [text[i:i+4000] for i in range(0, len(text), 4000)] for chunk in chunks: try: resp = requests.post(url, json={ \u0026#39;chat_id\u0026#39;: TG_CHAT_ID, \u0026#39;text\u0026#39;: chunk, \u0026#39;parse_mode\u0026#39;: \u0026#39;Markdown\u0026#39; }, timeout=10) if resp.status_code != 200: # fallback无格式 requests.post(url, json={ \u0026#39;chat_id\u0026#39;: TG_CHAT_ID, \u0026#39;text\u0026#39;: chunk }, timeout=10) except Exception as e: print(f\u0026#34;[TG] 发送失败: {e}\u0026#34;) # ============ 去重 ============ def load_alert_history(): if ALERT_HISTORY_FILE.exists(): try: return json.loads(ALERT_HISTORY_FILE.read_text()) except: return {} return {} def save_alert_history(history): ALERT_HISTORY_FILE.write_text(json.dumps(history)) def is_duplicate(symbol, history): if symbol not in history: return False last_alert = datetime.fromisoformat(history[symbol]) return (datetime.now() - last_alert).total_seconds() \u0026lt; DEDUP_HOURS * 3600 def mark_alerted(symbol, history): history[symbol] = datetime.now().isoformat() # 清理过期记录 cutoff = datetime.now() - timedelta(hours=DEDUP_HOURS * 2) history = {k: v for k, v in history.items() if datetime.fromisoformat(v) \u0026gt; cutoff} return history # ============ 费率快照 ============ def load_fr_snapshot(): if FR_SNAPSHOT_FILE.exists(): try: return json.loads(FR_SNAPSHOT_FILE.read_text()) except: pass return {} def save_fr_snapshot(snapshot): FR_SNAPSHOT_FILE.write_text(json.dumps(snapshot)) # ============ 核心扫描 ============ def scan(): ts_start = time.time() # 1. 获取所有永续合约 try: info = requests.get(\u0026#39;https://fapi.binance.com/fapi/v1/exchangeInfo\u0026#39;, timeout=10).json() symbols = [s[\u0026#39;symbol\u0026#39;] for s in info[\u0026#39;symbols\u0026#39;] if s[\u0026#39;contractType\u0026#39;] == \u0026#39;PERPETUAL\u0026#39; and s[\u0026#39;quoteAsset\u0026#39;] == \u0026#39;USDT\u0026#39; and s[\u0026#39;status\u0026#39;] == \u0026#39;TRADING\u0026#39;] except Exception as e: print(f\u0026#34;[ERROR] exchangeInfo: {e}\u0026#34;) return [] # 2. 批量获取24h行情(过滤低量币) try: tickers = requests.get(\u0026#39;https://fapi.binance.com/fapi/v1/ticker/24hr\u0026#39;, timeout=10).json() ticker_map = {t[\u0026#39;symbol\u0026#39;]: t for t in tickers} except Exception as e: print(f\u0026#34;[ERROR] ticker: {e}\u0026#34;) return [] active = [s for s in symbols if float(ticker_map.get(s, {}).get(\u0026#39;quoteVolume\u0026#39;, 0)) \u0026gt; MIN_VOLUME_USDT] # 3. 批量获取当前费率 (一次拿全部) try: fr_all = requests.get(\u0026#39;https://fapi.binance.com/fapi/v1/premiumIndex\u0026#39;, timeout=10).json() fr_current = {item[\u0026#39;symbol\u0026#39;]: float(item[\u0026#39;lastFundingRate\u0026#39;]) for item in fr_all} except: fr_current = {} # 4. 加载上次快照，对比找\u0026#34;刚转负\u0026#34;的 prev_snapshot = load_fr_snapshot() # 保存本次快照(供下次对比) save_fr_snapshot(fr_current) if not prev_snapshot: print(f\u0026#34;[{datetime.now().strftime(\u0026#39;%H:%M:%S\u0026#39;)}] 首次运行，保存快照，下次开始对比\u0026#34;) return [] # 找出: 上次\u0026gt;=0, 这次\u0026lt;0 的币 just_turned_negative = [] for sym in active: prev_fr = prev_snapshot.get(sym) curr_fr = fr_current.get(sym) if prev_fr is None or curr_fr is None: continue if prev_fr \u0026gt;= 0 and curr_fr \u0026lt; 0: just_turned_negative.append(sym) if not just_turned_negative: elapsed = time.time() - ts_start print(f\u0026#34;[{datetime.now().strftime(\u0026#39;%H:%M:%S\u0026#39;)}] 扫描完成: {len(active)}币/{elapsed:.1f}s, 无新转负\u0026#34;) return [] print(f\u0026#34;[{datetime.now().strftime(\u0026#39;%H:%M:%S\u0026#39;)}] 发现 {len(just_turned_negative)} 个刚转负: {just_turned_negative}\u0026#34;) # 5. 只对刚转负的币查OI signals = [] for sym in just_turned_negative: try: # OI历史 oi_hist = requests.get(\u0026#39;https://fapi.binance.com/futures/data/openInterestHist\u0026#39;, params={\u0026#39;symbol\u0026#39;: sym, \u0026#39;period\u0026#39;: \u0026#39;1h\u0026#39;, \u0026#39;limit\u0026#39;: 48}, timeout=10).json() oi_chg = 0 segs = [] oi_rising = False if oi_hist and len(oi_hist) \u0026gt;= 12: oi_values = [float(x[\u0026#39;sumOpenInterestValue\u0026#39;]) for x in oi_hist] seg_len = len(oi_values) // 4 if seg_len \u0026gt;= 3: segs = [ sum(oi_values[:seg_len]) / seg_len, sum(oi_values[seg_len:seg_len*2]) / seg_len, sum(oi_values[seg_len*2:seg_len*3]) / seg_len, sum(oi_values[seg_len*3:]) / max(1, len(oi_values[seg_len*3:])) ] oi_chg = (segs[3] - segs[0]) / segs[0] * 100 if segs[0] \u0026gt; 0 else 0 oi_rising = oi_chg \u0026gt; 0 t = ticker_map.get(sym, {}) signals.append({ \u0026#39;symbol\u0026#39;: sym, \u0026#39;price\u0026#39;: float(t.get(\u0026#39;lastPrice\u0026#39;, 0)), \u0026#39;price_chg_24h\u0026#39;: float(t.get(\u0026#39;priceChangePercent\u0026#39;, 0)), \u0026#39;volume\u0026#39;: float(t.get(\u0026#39;quoteVolume\u0026#39;, 0)), \u0026#39;oi_change\u0026#39;: oi_chg, \u0026#39;oi_segments\u0026#39;: segs, \u0026#39;oi_rising\u0026#39;: oi_rising, \u0026#39;current_fr\u0026#39;: fr_current.get(sym, 0), \u0026#39;prev_fr\u0026#39;: prev_snapshot.get(sym, 0), }) except: continue elapsed = time.time() - ts_start print(f\u0026#34;[{datetime.now().strftime(\u0026#39;%H:%M:%S\u0026#39;)}] 扫描完成: {len(active)}币/{elapsed:.1f}s, 信号: {len(signals)}\u0026#34;) return signals # ============ 附加信息 ============ def get_square_discussion(coin): \u0026#34;\u0026#34;\u0026#34;查询币安广场该币的帖子数和浏览量\u0026#34;\u0026#34;\u0026#34; try: r = requests.get( \u0026#34;https://www.binance.com/bapi/composite/v4/friendly/pgc/content/queryByHashtag\u0026#34;, params={\u0026#34;hashtag\u0026#34;: f\u0026#34;#{coin.lower()}\u0026#34;, \u0026#34;pageIndex\u0026#34;: 1, \u0026#34;pageSize\u0026#34;: 1, \u0026#34;orderBy\u0026#34;: \u0026#34;HOT\u0026#34;}, headers={\u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;https://www.binance.com/en/square\u0026#34;}, timeout=8 ) if r.status_code == 200: ht = r.json().get(\u0026#34;data\u0026#34;, {}).get(\u0026#34;hashtag\u0026#34;, {}) return ht.get(\u0026#34;contentCount\u0026#34;, 0), ht.get(\u0026#34;viewCount\u0026#34;, 0) except: pass return 0, 0 def get_market_caps(): \u0026#34;\u0026#34;\u0026#34;获取币安流通市值\u0026#34;\u0026#34;\u0026#34; mcap = {} try: r = requests.get( \u0026#34;https://www.binance.com/bapi/composite/v1/public/marketing/symbol/list\u0026#34;, timeout=10 ) if r.status_code == 200: for item in r.json().get(\u0026#34;data\u0026#34;, []): name = item.get(\u0026#34;name\u0026#34;, \u0026#34;\u0026#34;) mc = item.get(\u0026#34;marketCap\u0026#34;, 0) if name and mc: mcap[name] = float(mc) except: pass return mcap def get_spot_symbols(): \u0026#34;\u0026#34;\u0026#34;获取有现货的币种\u0026#34;\u0026#34;\u0026#34; try: info = requests.get(\u0026#34;https://api.binance.com/api/v3/exchangeInfo\u0026#34;, timeout=10).json() return {s[\u0026#34;baseAsset\u0026#34;] for s in info[\u0026#34;symbols\u0026#34;] if s[\u0026#34;quoteAsset\u0026#34;] == \u0026#34;USDT\u0026#34; and s[\u0026#34;status\u0026#34;] == \u0026#34;TRADING\u0026#34;} except: return set() def fmt_mcap(v): if v \u0026gt;= 1e9: return f\u0026#34;${v/1e9:.2f}B\u0026#34; if v \u0026gt;= 1e6: return f\u0026#34;${v/1e6:.1f}M\u0026#34; if v \u0026gt;= 1e3: return f\u0026#34;${v/1e3:.0f}K\u0026#34; return f\u0026#34;${v:.0f}\u0026#34; def fmt_views(v): if v \u0026gt;= 1e6: return f\u0026#34;{v/1e6:.1f}M\u0026#34; if v \u0026gt;= 1e3: return f\u0026#34;{v/1e3:.0f}K\u0026#34; return str(v) # ============ 格式化推送 ============ def format_alert(signals): if not signals: return None # OI在涨的排前面，同组内按费率绝对值排序 signals.sort(key=lambda x: (-int(x.get(\u0026#39;oi_rising\u0026#39;, False)), x[\u0026#39;current_fr\u0026#39;])) # 批量获取附加信息 mcap_map = get_market_caps() spot_set = get_spot_symbols() now = datetime.now().strftime(\u0026#39;%m-%d %H:%M\u0026#39;) lines = [f\u0026#34;*[ 费率刚转负+OI涨 ]* {now}\\n\u0026#34;] for s in signals: coin = s[\u0026#39;symbol\u0026#39;].replace(\u0026#39;USDT\u0026#39;, \u0026#39;\u0026#39;) # 费率: 上期→本期 fr_change = f\u0026#34;{s[\u0026#39;prev_fr\u0026#39;]:+.4%} -\u0026gt; {s[\u0026#39;current_fr\u0026#39;]:+.4%}\u0026#34; # 附加信息 mcap = mcap_map.get(coin, 0) has_spot = coin in spot_set sq_posts, sq_views = get_square_discussion(coin) lines.append(f\u0026#34;```\u0026#34;) lines.append(f\u0026#34;{coin}\u0026#34;) lines.append(f\u0026#34; 价格: {s[\u0026#39;price\u0026#39;]:.4f} 24h: {s[\u0026#39;price_chg_24h\u0026#39;]:+.1f}%\u0026#34;) lines.append(f\u0026#34; 费率: {fr_change}\u0026#34;) if s[\u0026#39;oi_segments\u0026#39;]: oi_segs = \u0026#39; \u0026gt; \u0026#39;.join([f\u0026#34;{v/1e6:.1f}M\u0026#34; for v in s[\u0026#39;oi_segments\u0026#39;]]) lines.append(f\u0026#34; OI: +{s[\u0026#39;oi_change\u0026#39;]:.1f}% ({oi_segs})\u0026#34;) lines.append(f\u0026#34; 成交额: ${s[\u0026#39;volume\u0026#39;]/1e6:.1f}M\u0026#34;) lines.append(f\u0026#34; 市值: {fmt_mcap(mcap) if mcap \u0026gt; 0 else \u0026#39;未知\u0026#39;} 现货: {\u0026#39;有\u0026#39; if has_spot else \u0026#39;仅合约\u0026#39;}\u0026#34;) if sq_posts \u0026gt; 0: lines.append(f\u0026#34; 广场: {sq_posts}帖 / {fmt_views(sq_views)}浏览\u0026#34;) else: lines.append(f\u0026#34; 广场: 无讨论\u0026#34;) lines.append(f\u0026#34;```\u0026#34;) return \u0026#39;\\n\u0026#39;.join(lines) # ============ 主逻辑 ============ def main(): signals = scan() if signals: # 只推: 费率当前为负 + OI在涨 (最强组合) strong = [s for s in signals if s[\u0026#39;current_fr\u0026#39;] \u0026lt; 0 and s.get(\u0026#39;oi_rising\u0026#39;)] if strong: msg = format_alert(strong) if msg: send_tg(msg) print(f\u0026#34; 推送 {len(strong)} 个信号 (总{len(signals)}个转负, {len(strong)}个OI也涨)\u0026#34;) else: print(f\u0026#34; {len(signals)} 个转负但无OI在涨的, 跳过\u0026#34;) else: print(f\u0026#34; 无信号\u0026#34;) if __name__ == \u0026#39;__main__\u0026#39;: main() Accumulation Radar (Radar tích lũy) Ngày: 2026.04.25　Thẻ: Python · Binance · CoinGlass · Telegram\nĐộng lượng + OI bất thường + Cảnh báo thông minh\nQuét theo giờ: theo dõi động lượng các đồng tăng giá hàng đầu, phát hiện OI bất thường, đẩy Telegram. Python thuần, chi phí AI = 0.\nMã nguồn đầy đủ #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; 热度做多雷达 v2 — 热度+费率+OI 三维扫描 核心逻辑（拉哪模式）： 1. 热度先行 → CG热搜+放量=资金涌入信号 2. 负费率=空头燃料，庄家拉盘爆空单 3. OI暴涨=大资金建仓=即将拉盘 单策略：发现热度→小仓做多→严格止损→拿住赢家 数据源：币安合约API + CoinGecko Trending（零成本） \u0026#34;\u0026#34;\u0026#34; import json import os import sys import time import requests from datetime import datetime, timezone, timedelta from pathlib import Path from square_heat import get_square_heat # === 加载 .env === env_file = Path(__file__).parent / \u0026#34;.env.oi\u0026#34; if env_file.exists(): with open(env_file) as f: for line in f: line = line.strip() if line and not line.startswith(\u0026#34;#\u0026#34;) and \u0026#34;=\u0026#34; in line: k, v = line.split(\u0026#34;=\u0026#34;, 1) os.environ.setdefault(k.strip(), v.strip()) # === 配置 === TG_BOT_TOKEN = os.getenv(\u0026#34;TG_BOT_TOKEN\u0026#34;, \u0026#34;\u0026#34;) TG_CHAT_ID = os.getenv(\u0026#34;TG_CHAT_ID\u0026#34;, \u0026#34;YOUR_CHAT_ID\u0026#34;) FAPI = \u0026#34;https://fapi.binance.com\u0026#34; # 热度历史记录（用于检测首次上榜） HEAT_HISTORY_FILE = Path(__file__).parent / \u0026#34;heat_history.json\u0026#34; # 热度参数 VOL_SURGE_MULT = 2.5 # 成交量放大2.5倍以上=放量 MIN_VOL_USD = 20_000_000 # 日均成交\u0026gt;$20M才检测放量 # OI异动参数 MIN_OI_DELTA_PCT = 3.0 # OI变化至少3% MIN_OI_USD = 2_000_000 # 最低OI门槛 $2M def api_get(endpoint, params=None): \u0026#34;\u0026#34;\u0026#34;币安API请求\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;{FAPI}{endpoint}\u0026#34; for attempt in range(3): try: resp = requests.get(url, params=params, timeout=10) if resp.status_code == 200: return resp.json() elif resp.status_code == 429: time.sleep(2) else: return None except: time.sleep(1) return None def format_usd(v): if v \u0026gt;= 1e9: return f\u0026#34;${v/1e9:.1f}B\u0026#34; if v \u0026gt;= 1e6: return f\u0026#34;${v/1e6:.1f}M\u0026#34; if v \u0026gt;= 1e3: return f\u0026#34;${v/1e3:.0f}K\u0026#34; return f\u0026#34;${v:.0f}\u0026#34; def mcap_str(v): if v \u0026gt;= 1e9: return f\u0026#34;${v/1e9:.1f}B\u0026#34; if v \u0026gt;= 1e6: return f\u0026#34;${v/1e6:.0f}M\u0026#34; if v \u0026gt;= 1e3: return f\u0026#34;${v/1e3:.0f}K\u0026#34; return f\u0026#34;${v:.0f}\u0026#34; def send_telegram(text): \u0026#34;\u0026#34;\u0026#34;发送TG消息\u0026#34;\u0026#34;\u0026#34; if not TG_BOT_TOKEN: print(\u0026#34;\\n[TG] No token, stdout:\\n\u0026#34;) print(text) return url = f\u0026#34;https://api.telegram.org/bot{TG_BOT_TOKEN}/sendMessage\u0026#34; # 分段发送（TG限制4096字） chunks = [] current = \u0026#34;\u0026#34; for line in text.split(\u0026#34;\\n\u0026#34;): if len(current) + len(line) + 1 \u0026gt; 3800: chunks.append(current) current = line else: current += \u0026#34;\\n\u0026#34; + line if current else line if current: chunks.append(current) for chunk in chunks: try: resp = requests.post(url, json={ \u0026#34;chat_id\u0026#34;: TG_CHAT_ID, \u0026#34;text\u0026#34;: chunk, \u0026#34;parse_mode\u0026#34;: \u0026#34;Markdown\u0026#34; }, timeout=10) if resp.status_code == 200: print(f\u0026#34;[TG] Sent ✓ ({len(chunk)} chars)\u0026#34;) else: # Markdown失败就用纯文本 resp2 = requests.post(url, json={ \u0026#34;chat_id\u0026#34;: TG_CHAT_ID, \u0026#34;text\u0026#34;: chunk.replace(\u0026#34;*\u0026#34;, \u0026#34;\u0026#34;).replace(\u0026#34;_\u0026#34;, \u0026#34;\u0026#34;), }, timeout=10) print(f\u0026#34;[TG] Sent plain ({\u0026#39;✓\u0026#39; if resp2.status_code == 200 else \u0026#39;✗\u0026#39;})\u0026#34;) except Exception as e: print(f\u0026#34;[TG] Error: {e}\u0026#34;) time.sleep(0.5) def main(): print(f\u0026#34;🔥 热度做多雷达 v2 — {datetime.now().strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;)}\\n\u0026#34;) # 1. 全市场行情+费率 tickers_raw = api_get(\u0026#34;/fapi/v1/ticker/24hr\u0026#34;) premiums_raw = api_get(\u0026#34;/fapi/v1/premiumIndex\u0026#34;) if not tickers_raw or not premiums_raw: print(\u0026#34;❌ API失败\u0026#34;) return ticker_map = {} for t in tickers_raw: if t[\u0026#34;symbol\u0026#34;].endswith(\u0026#34;USDT\u0026#34;): ticker_map[t[\u0026#34;symbol\u0026#34;]] = { \u0026#34;px_chg\u0026#34;: float(t[\u0026#34;priceChangePercent\u0026#34;]), \u0026#34;vol\u0026#34;: float(t[\u0026#34;quoteVolume\u0026#34;]), \u0026#34;price\u0026#34;: float(t[\u0026#34;lastPrice\u0026#34;]), } funding_map = {} for p in premiums_raw: if p[\u0026#34;symbol\u0026#34;].endswith(\u0026#34;USDT\u0026#34;): funding_map[p[\u0026#34;symbol\u0026#34;]] = float(p[\u0026#34;lastFundingRate\u0026#34;]) # 2. 真实流通市值（币安现货API） mcap_map = {} try: r = requests.get( \u0026#34;https://www.binance.com/bapi/composite/v1/public/marketing/symbol/list\u0026#34;, timeout=10 ) if r.status_code == 200: for item in r.json().get(\u0026#34;data\u0026#34;, []): name = item.get(\u0026#34;name\u0026#34;, \u0026#34;\u0026#34;) mc = item.get(\u0026#34;marketCap\u0026#34;, 0) if name and mc: mcap_map[name] = float(mc) print(f\u0026#34;✅ 真实市值: {len(mcap_map)}个币\u0026#34;) except Exception as e: print(f\u0026#34;⚠️ 市值API失败: {e}\u0026#34;) # 3. 热度检测：币安广场热搜 + CoinGecko Trending + 成交量暴增 heat_map = {} cg_trending = set() square_trending = set() # 3a. 币安广场热搜（6H）— 最重要！币安用户=交易用户 sq_coins = get_square_heat() if sq_coins: for i, c in enumerate(sq_coins): coin = c[\u0026#34;coin\u0026#34;] square_trending.add(coin) # 排名越靠前分越高，急速上升额外加分 rank_score = max(50 - i * 4, 10) if c.get(\u0026#34;rapidRiser\u0026#34;): rank_score += 15 heat_map[coin] = heat_map.get(coin, 0) + rank_score print(f\u0026#34;🏦 广场热搜: {len(square_trending)}个币 {[c[\u0026#39;coin\u0026#39;] for c in sq_coins[:5]]}\u0026#34;) # 3b. CoinGecko Trending try: r = requests.get(\u0026#34;https://api.coingecko.com/api/v3/search/trending\u0026#34;, timeout=10) if r.status_code == 200: for item in r.json().get(\u0026#34;coins\u0026#34;, []): sym = item[\u0026#34;item\u0026#34;][\u0026#34;symbol\u0026#34;].upper() rank = item[\u0026#34;item\u0026#34;].get(\u0026#34;score\u0026#34;, 99) cg_trending.add(sym) heat_map[sym] = heat_map.get(sym, 0) + max(50 - rank * 3, 10) print(f\u0026#34;🌐 CG Trending: {len(cg_trending)}个币\u0026#34;) except Exception as e: print(f\u0026#34;⚠️ CG Trending失败: {e}\u0026#34;) # 成交量暴增检测 vol_surge_coins = set() for sym, tk in ticker_map.items(): coin = sym.replace(\u0026#34;USDT\u0026#34;, \u0026#34;\u0026#34;) vol_24h = tk[\u0026#34;vol\u0026#34;] if vol_24h \u0026gt; MIN_VOL_USD: kl = api_get(\u0026#34;/fapi/v1/klines\u0026#34;, {\u0026#34;symbol\u0026#34;: sym, \u0026#34;interval\u0026#34;: \u0026#34;1d\u0026#34;, \u0026#34;limit\u0026#34;: 8}) if kl and len(kl) \u0026gt;= 5: avg_prev = sum(float(k[7]) for k in kl[:-1]) / (len(kl) - 1) if avg_prev \u0026gt; 0: ratio = vol_24h / avg_prev if ratio \u0026gt;= VOL_SURGE_MULT: vol_surge_coins.add(coin) heat_map[coin] = heat_map.get(coin, 0) + min(ratio * 10, 50) time.sleep(0.05) print(f\u0026#34;📈 放量(≥{VOL_SURGE_MULT}x): {len(vol_surge_coins)}个币\u0026#34;) # 双重/三重热度 dual_heat = cg_trending \u0026amp; vol_surge_coins square_vol = square_trending \u0026amp; vol_surge_coins triple_heat = cg_trending \u0026amp; vol_surge_coins \u0026amp; square_trending all_multi_heat = dual_heat | square_vol if all_multi_heat: for coin in all_multi_heat: heat_map[coin] = heat_map.get(coin, 0) + 20 if triple_heat: for coin in triple_heat: heat_map[coin] = heat_map.get(coin, 0) + 30 # 三重热度超级加分 print(f\u0026#34;🔥🔥🔥 三重热度: {triple_heat}\u0026#34;) else: print(f\u0026#34;🔥🔥 双重热度: {all_multi_heat}\u0026#34;) # 4. OI扫描（Top100成交量 + 热度币） scan_syms = set() # 热度币必扫 for coin in heat_map: sym = coin + \u0026#34;USDT\u0026#34; if sym in ticker_map: scan_syms.add(sym) # Top100成交量 top_by_vol = sorted(ticker_map.items(), key=lambda x: x[1][\u0026#34;vol\u0026#34;], reverse=True)[:100] for sym, _ in top_by_vol: scan_syms.add(sym) oi_map = {} for i, sym in enumerate(scan_syms): oi_hist = api_get(\u0026#34;/futures/data/openInterestHist\u0026#34;, { \u0026#34;symbol\u0026#34;: sym, \u0026#34;period\u0026#34;: \u0026#34;1h\u0026#34;, \u0026#34;limit\u0026#34;: 6 }) if oi_hist and len(oi_hist) \u0026gt;= 2: curr = float(oi_hist[-1][\u0026#34;sumOpenInterestValue\u0026#34;]) prev_1h = float(oi_hist[-2][\u0026#34;sumOpenInterestValue\u0026#34;]) prev_6h = float(oi_hist[0][\u0026#34;sumOpenInterestValue\u0026#34;]) d1h = ((curr - prev_1h) / prev_1h * 100) if prev_1h \u0026gt; 0 else 0 d6h = ((curr - prev_6h) / prev_6h * 100) if prev_6h \u0026gt; 0 else 0 oi_map[sym] = {\u0026#34;oi_usd\u0026#34;: curr, \u0026#34;d1h\u0026#34;: d1h, \u0026#34;d6h\u0026#34;: d6h} if (i + 1) % 10 == 0: time.sleep(0.5) print(f\u0026#34;📊 OI扫描: {len(oi_map)}个币\u0026#34;) # 5. 整合所有数据 all_syms = set(list(ticker_map.keys())) coin_data = {} for sym in all_syms: tk = ticker_map.get(sym, {}) if not tk: continue oi = oi_map.get(sym, {}) fr = funding_map.get(sym, 0) coin = sym.replace(\u0026#34;USDT\u0026#34;, \u0026#34;\u0026#34;) d6h = oi.get(\u0026#34;d6h\u0026#34;, 0) fr_pct = fr * 100 oi_usd = oi.get(\u0026#34;oi_usd\u0026#34;, 0) # 真实市值优先，fallback粗估 if coin in mcap_map: est_mcap = mcap_map[coin] else: est_mcap = max(tk[\u0026#34;vol\u0026#34;] * 0.3, oi_usd * 2) if oi_usd \u0026gt; 0 else tk[\u0026#34;vol\u0026#34;] * 0.3 heat = heat_map.get(coin, 0) coin_data[sym] = { \u0026#34;coin\u0026#34;: coin, \u0026#34;sym\u0026#34;: sym, \u0026#34;px_chg\u0026#34;: tk[\u0026#34;px_chg\u0026#34;], \u0026#34;vol\u0026#34;: tk[\u0026#34;vol\u0026#34;], \u0026#34;fr_pct\u0026#34;: fr_pct, \u0026#34;d6h\u0026#34;: d6h, \u0026#34;oi_usd\u0026#34;: oi_usd, \u0026#34;est_mcap\u0026#34;: est_mcap, \u0026#34;heat\u0026#34;: heat, \u0026#34;in_cg\u0026#34;: coin in cg_trending, \u0026#34;in_sq\u0026#34;: coin in square_trending, \u0026#34;vol_surge\u0026#34;: coin in vol_surge_coins, } # ═══════════════════════════════════════ # 热度榜 # ═══════════════════════════════════════ hot_coins = sorted( [d for d in coin_data.values() if d[\u0026#34;heat\u0026#34;] \u0026gt; 0], key=lambda x: x[\u0026#34;heat\u0026#34;], reverse=True ) # 检测首次上榜 heat_history = {} if HEAT_HISTORY_FILE.exists(): try: heat_history = json.loads(HEAT_HISTORY_FILE.read_text()) except: pass now_ts = datetime.now(timezone(timedelta(hours=8))).strftime(\u0026#34;%Y-%m-%d %H:%M\u0026#34;) new_entries = [] # 首次上榜的币 for s in hot_coins: coin = s[\u0026#34;coin\u0026#34;] if coin not in heat_history: # 首次上榜！ heat_history[coin] = {\u0026#34;first_seen\u0026#34;: now_ts, \u0026#34;price\u0026#34;: s.get(\u0026#34;px_chg\u0026#34;, 0)} sources = [] if s[\u0026#34;in_sq\u0026#34;]: sources.append(\u0026#34;广场\u0026#34;) if s[\u0026#34;in_cg\u0026#34;]: sources.append(\u0026#34;CG\u0026#34;) if s[\u0026#34;vol_surge\u0026#34;]: sources.append(\u0026#34;放量\u0026#34;) new_entries.append({\u0026#34;coin\u0026#34;: coin, \u0026#34;sources\u0026#34;: sources, \u0026#34;data\u0026#34;: s}) # 清理超过7天的历史（避免文件无限增长） cutoff = (datetime.now(timezone(timedelta(hours=8))) - timedelta(days=7)).strftime(\u0026#34;%Y-%m-%d\u0026#34;) heat_history = {k: v for k, v in heat_history.items() if v.get(\u0026#34;first_seen\u0026#34;, \u0026#34;9999\u0026#34;) \u0026gt;= cutoff} # 保存历史 HEAT_HISTORY_FILE.write_text(json.dumps(heat_history, indent=2, ensure_ascii=False)) # ═══════════════════════════════════════ # 追多：负费率+在涨 # ═══════════════════════════════════════ chase = [] for sym, d in coin_data.items(): if d[\u0026#34;px_chg\u0026#34;] \u0026gt; 3 and d[\u0026#34;fr_pct\u0026#34;] \u0026lt; -0.005 and d[\u0026#34;vol\u0026#34;] \u0026gt; 1_000_000: fr_hist = api_get(\u0026#34;/fapi/v1/fundingRate\u0026#34;, {\u0026#34;symbol\u0026#34;: sym, \u0026#34;limit\u0026#34;: 5}) fr_rates = [float(f[\u0026#34;fundingRate\u0026#34;]) * 100 for f in fr_hist] if fr_hist else [d[\u0026#34;fr_pct\u0026#34;]] fr_prev = fr_rates[-2] if len(fr_rates) \u0026gt;= 2 else d[\u0026#34;fr_pct\u0026#34;] fr_delta = d[\u0026#34;fr_pct\u0026#34;] - fr_prev trend = \u0026#34;加速恶化\u0026#34; if fr_delta \u0026lt; -0.05 else \u0026#34;转负\u0026#34; if fr_delta \u0026lt; -0.01 else \u0026#34;持平\u0026#34; if abs(fr_delta) \u0026lt; 0.01 else \u0026#34;回升\u0026#34; chase.append({**d, \u0026#34;fr_delta\u0026#34;: fr_delta, \u0026#34;trend\u0026#34;: trend, \u0026#34;rates\u0026#34;: \u0026#34; → \u0026#34;.join([f\u0026#34;{x:.3f}\u0026#34; for x in fr_rates[-3:]])}) time.sleep(0.2) chase.sort(key=lambda x: x[\u0026#34;fr_pct\u0026#34;]) # ═══════════════════════════════════════ # 生成推送 # ═══════════════════════════════════════ now = datetime.now(timezone(timedelta(hours=8))) lines = [ f\u0026#34;**热度做多雷达**\u0026#34;, f\u0026#34;{now.strftime(\u0026#39;%Y-%m-%d %H:%M\u0026#39;)} CST\u0026#34;, ] # 热度榜（表格） # 首次上榜放最前面 if new_entries: lines.append(f\u0026#34;\\n**[ 首次上榜 ]** 新出现的热度币，重点关注\u0026#34;) tbl = [\u0026#34;```\u0026#34;] tbl.append(f\u0026#34;{\u0026#39;币种\u0026#39;:\u0026lt;10} {\u0026#39;市值\u0026#39;:\u0026gt;8} {\u0026#39;涨幅\u0026#39;:\u0026gt;7} {\u0026#39;来源\u0026#39;}\u0026#34;) tbl.append(f\u0026#34;{\u0026#39;-\u0026#39;*10} {\u0026#39;-\u0026#39;*8} {\u0026#39;-\u0026#39;*7} {\u0026#39;-\u0026#39;*20}\u0026#34;) for e in new_entries: s = e[\u0026#34;data\u0026#34;] src_str = \u0026#34;/\u0026#34;.join(e[\u0026#34;sources\u0026#34;]) tbl.append(f\u0026#34;{s[\u0026#39;coin\u0026#39;]:\u0026lt;10} {mcap_str(s[\u0026#39;est_mcap\u0026#39;]):\u0026gt;8} {s[\u0026#39;px_chg\u0026#39;]:\u0026gt;+6.0f}% {src_str}\u0026#34;) tbl.append(\u0026#34;```\u0026#34;) lines.append(\u0026#34;\\n\u0026#34;.join(tbl)) if hot_coins: lines.append(f\u0026#34;\\n**[ 热度榜 ]**\u0026#34;) tbl = [\u0026#34;```\u0026#34;] tbl.append(f\u0026#34;{\u0026#39;币种\u0026#39;:\u0026lt;10} {\u0026#39;市值\u0026#39;:\u0026gt;8} {\u0026#39;涨幅\u0026#39;:\u0026gt;7} {\u0026#39;来源\u0026#39;}\u0026#34;) tbl.append(f\u0026#34;{\u0026#39;-\u0026#39;*10} {\u0026#39;-\u0026#39;*8} {\u0026#39;-\u0026#39;*7} {\u0026#39;-\u0026#39;*20}\u0026#34;) for s in hot_coins[:10]: sources = [] if s[\u0026#34;in_sq\u0026#34;]: sources.append(\u0026#34;广场\u0026#34;) if s[\u0026#34;in_cg\u0026#34;]: sources.append(\u0026#34;CG\u0026#34;) if s[\u0026#34;vol_surge\u0026#34;]: sources.append(\u0026#34;放量\u0026#34;) extra = [] if abs(s[\u0026#34;d6h\u0026#34;]) \u0026gt;= 3: extra.append(f\u0026#34;OI{s[\u0026#39;d6h\u0026#39;]:+.0f}%\u0026#34;) if s[\u0026#34;fr_pct\u0026#34;] \u0026lt; -0.03: extra.append(f\u0026#34;费率{s[\u0026#39;fr_pct\u0026#39;]:.2f}%\u0026#34;) src_str = \u0026#34;/\u0026#34;.join(sources) if extra: src_str += \u0026#34; \u0026#34; + \u0026#34; \u0026#34;.join(extra) coin_name = s[\u0026#39;coin\u0026#39;] tbl.append(f\u0026#34;{coin_name:\u0026lt;10} {mcap_str(s[\u0026#39;est_mcap\u0026#39;]):\u0026gt;8} {s[\u0026#39;px_chg\u0026#39;]:\u0026gt;+6.0f}% {src_str}\u0026#34;) tbl.append(\u0026#34;```\u0026#34;) lines.append(\u0026#34;\\n\u0026#34;.join(tbl)) else: lines.append(\u0026#34;\\n**[ 热度榜 ]** 暂无热点\u0026#34;) # 追多（表格） lines.append(f\u0026#34;\\n**[ 追多 ]** 涨了+费率负=空头燃料\u0026#34;) if chase: tbl = [\u0026#34;```\u0026#34;] tbl.append(f\u0026#34;{\u0026#39;币种\u0026#39;:\u0026lt;10} {\u0026#39;费率\u0026#39;:\u0026gt;10} {\u0026#39;趋势\u0026#39;:\u0026gt;8} {\u0026#39;涨幅\u0026#39;:\u0026gt;7} {\u0026#39;市值\u0026#39;:\u0026gt;8}\u0026#34;) tbl.append(f\u0026#34;{\u0026#39;-\u0026#39;*10} {\u0026#39;-\u0026#39;*10} {\u0026#39;-\u0026#39;*8} {\u0026#39;-\u0026#39;*7} {\u0026#39;-\u0026#39;*8}\u0026#34;) for s in chase[:8]: tbl.append( f\u0026#34;{s[\u0026#39;coin\u0026#39;]:\u0026lt;10} {s[\u0026#39;fr_pct\u0026#39;]:\u0026gt;+9.3f}% {s[\u0026#39;trend\u0026#39;]:\u0026gt;8} {s[\u0026#39;px_chg\u0026#39;]:\u0026gt;+6.0f}% {mcap_str(s[\u0026#39;est_mcap\u0026#39;]):\u0026gt;7}\u0026#34; ) tbl.append(\u0026#34;```\u0026#34;) lines.append(\u0026#34;\\n\u0026#34;.join(tbl)) else: lines.append(\u0026#34; 暂无符合条件的标的\u0026#34;) # OI异动（表格） oi_alerts = [] for sym, oi in oi_map.items(): if abs(oi[\u0026#34;d6h\u0026#34;]) \u0026gt;= 8: d = coin_data.get(sym) if d and d[\u0026#34;heat\u0026#34;] == 0: oi_alerts.append(d) oi_alerts.sort(key=lambda x: abs(x[\u0026#34;d6h\u0026#34;]), reverse=True) if oi_alerts: lines.append(f\u0026#34;\\n**[ OI异动 ]** 6小时持仓变化\u0026gt;=8%\u0026#34;) tbl = [\u0026#34;```\u0026#34;] tbl.append(f\u0026#34;{\u0026#39;币种\u0026#39;:\u0026lt;10} {\u0026#39;方向\u0026#39;:\u0026gt;4} {\u0026#39;OI变化\u0026#39;:\u0026gt;8} {\u0026#39;涨幅\u0026#39;:\u0026gt;7} {\u0026#39;市值\u0026#39;:\u0026gt;8}\u0026#34;) tbl.append(f\u0026#34;{\u0026#39;-\u0026#39;*10} {\u0026#39;-\u0026#39;*4} {\u0026#39;-\u0026#39;*8} {\u0026#39;-\u0026#39;*7} {\u0026#39;-\u0026#39;*8}\u0026#34;) for s in oi_alerts[:6]: direction = \u0026#34;增仓\u0026#34; if s[\u0026#34;d6h\u0026#34;] \u0026gt; 0 else \u0026#34;减仓\u0026#34; tbl.append( f\u0026#34;{s[\u0026#39;coin\u0026#39;]:\u0026lt;10} {direction:\u0026gt;4} {s[\u0026#39;d6h\u0026#39;]:\u0026gt;+7.1f}% {s[\u0026#39;px_chg\u0026#39;]:\u0026gt;+6.0f}% {mcap_str(s[\u0026#39;est_mcap\u0026#39;]):\u0026gt;7}\u0026#34; ) tbl.append(\u0026#34;```\u0026#34;) lines.append(\u0026#34;\\n\u0026#34;.join(tbl)) # 值得关注 highlights = [] hot_oi = [d for d in coin_data.values() if d[\u0026#34;heat\u0026#34;] \u0026gt; 0 and d[\u0026#34;d6h\u0026#34;] \u0026gt; 5] for s in sorted(hot_oi, key=lambda x: x[\u0026#34;d6h\u0026#34;], reverse=True)[:3]: highlights.append(f\u0026#34;{s[\u0026#39;coin\u0026#39;]} — 热度高+OI涨{s[\u0026#39;d6h\u0026#39;]:+.0f}%，资金涌入\u0026#34;) hot_fuel = [d for d in coin_data.values() if d[\u0026#34;heat\u0026#34;] \u0026gt; 0 and d[\u0026#34;fr_pct\u0026#34;] \u0026lt; -0.03] for s in sorted(hot_fuel, key=lambda x: x[\u0026#34;fr_pct\u0026#34;])[:2]: if s[\u0026#34;coin\u0026#34;] not in \u0026#34; \u0026#34;.join(highlights): highlights.append(f\u0026#34;{s[\u0026#39;coin\u0026#39;]} — 热度高+费率{s[\u0026#39;fr_pct\u0026#39;]:.2f}%，空头燃料足\u0026#34;) chase_fire = [s for s in chase[:5] if \u0026#34;加速\u0026#34; in s.get(\u0026#34;trend\u0026#34;, \u0026#34;\u0026#34;)] for s in chase_fire[:2]: if s[\u0026#34;coin\u0026#34;] not in \u0026#34; \u0026#34;.join(highlights): highlights.append(f\u0026#34;{s[\u0026#39;coin\u0026#39;]} — 费率{s[\u0026#39;fr_pct\u0026#39;]:.3f}%持续恶化，逼空在即\u0026#34;) if highlights: lines.append(f\u0026#34;\\n**[ 值得关注 ]**\u0026#34;) for h in highlights[:5]: lines.append(f\u0026#34; {h}\u0026#34;) lines.append(f\u0026#34;\\n广场=币安站内搜索 / CG=CoinGecko全球热度\u0026#34;) lines.append(f\u0026#34;费率负=做空多，庄家拉盘爆空头\u0026#34;) report = \u0026#34;\\n\u0026#34;.join(lines) send_telegram(report) print(\u0026#34;\\n✅ 完成\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Binance Alpha Monitor (Giám sát Binance Alpha) Ngày: 2026.04.23　Thẻ: Python · Binance · Claude AI · Telegram\nWebSocket realtime · Phân tích AI · Đẩy Telegram\nTự động phát hiện token mới được niêm yết trên Binance Alpha, phân tích chất lượng token bằng Claude AI và gửi cảnh báo qua Telegram.\nMã nguồn đầy đủ #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; Binance Alpha Monitor v2 — 稳定版 REST轮询 + 智能过滤 + 评级 + TG推送 零API Key，零AI成本（评级用规则引擎） 运行: python3 alpha_monitor.py \u0026#34;\u0026#34;\u0026#34; import asyncio import hashlib import json import logging import os import re import sqlite3 import time from dataclasses import dataclass, field from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Optional import httpx # ============================================================ # 配置 # ============================================================ BASE_DIR = Path(__file__).parent DB_PATH = str(BASE_DIR / \u0026#34;data\u0026#34; / \u0026#34;alpha.db\u0026#34;) # TG TG_BOT_TOKEN = os.environ.get(\u0026#34;TG_BOT_TOKEN\u0026#34;, \u0026#34;\u0026#34;) TG_CHAT_ID = os.environ.get(\u0026#34;TG_CHAT_ID\u0026#34;, \u0026#34;\u0026#34;) # LLM (可选，用于抽取叙事，不配置则降级为规则) # 优先从hermes auth.json读credential pool（与scanner一致） def _load_anthropic_key(): \u0026#34;\u0026#34;\u0026#34;从auth.json或环境变量获取API key\u0026#34;\u0026#34;\u0026#34; # 1. hermes auth.json credential pool try: auth_file = os.path.expanduser(\u0026#34;~/.hermes/auth.json\u0026#34;) if os.path.exists(auth_file): with open(auth_file) as f: auth = json.load(f) pool = auth.get(\u0026#34;credential_pool\u0026#34;, {}).get(\u0026#34;anthropic\u0026#34;, []) if pool: key = pool[0].get(\u0026#34;access_token\u0026#34;, \u0026#34;\u0026#34;) if key and key != \u0026#34;***\u0026#34;: return key except Exception: pass # 2. 环境变量 fallback return os.environ.get(\u0026#34;ANTHROPIC_API_KEY\u0026#34;, \u0026#34;\u0026#34;) ANTHROPIC_API_KEY = _load_anthropic_key() ANTHROPIC_BASE_URL = os.environ.get(\u0026#34;ANTHROPIC_BASE_URL\u0026#34;, \u0026#34;https://api.anthropic.com\u0026#34;) ANTHROPIC_MODEL = os.environ.get(\u0026#34;ANTHROPIC_MODEL\u0026#34;, \u0026#34;claude-sonnet-4-6\u0026#34;) # 轮询间隔 ANNOUNCEMENT_POLL_INTERVAL = 30 # 公告轮询30秒 AGGREGATION_POLL_INTERVAL = 15 # 聚合工作者15秒 MONITOR_POLL_INTERVAL = 120 # 上线后监控2分钟 # HTTP HEADERS = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-US,en;q=0.9\u0026#34;, } BINANCE_ANNOUNCEMENT_API = \u0026#34;https://www.binance.com/bapi/composite/v1/public/cms/article/list/query\u0026#34; # ============================================================ # 日志 # ============================================================ logging.basicConfig( level=logging.INFO, format=\u0026#34;%(asctime)s | %(levelname)s | %(name)s | %(message)s\u0026#34;, datefmt=\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;, ) logger = logging.getLogger(\u0026#34;alpha\u0026#34;) # ============================================================ # 过滤规则 # ============================================================ # 触发关键词 — 命中任意一个就触发 TRIGGER_KEYWORDS = [ \u0026#34;alpha\u0026#34;, \u0026#34;空投\u0026#34;, \u0026#34;airdrop\u0026#34;, \u0026#34;tge\u0026#34;, \u0026#34;token generation\u0026#34;, \u0026#34;将上线\u0026#34;, \u0026#34;will list\u0026#34;, \u0026#34;will launch\u0026#34;, \u0026#34;独家\u0026#34;, \u0026#34;exclusive\u0026#34;, \u0026#34;binance wallet\u0026#34;, \u0026#34;hodler\u0026#34;, ] # 排除关键词 — 命中任意一个直接排除 EXCLUDE_KEYWORDS = [ \u0026#34;delisting\u0026#34;, \u0026#34;delist\u0026#34;, \u0026#34;下架\u0026#34;, \u0026#34;deprecate\u0026#34;, \u0026#34;退市\u0026#34;, \u0026#34;maintenance\u0026#34;, \u0026#34;维护\u0026#34;, \u0026#34;launchpool\u0026#34;, \u0026#34;megadrop\u0026#34;, \u0026#34;buyback\u0026#34;, \u0026#34;回购\u0026#34;, \u0026#34;已完成\u0026#34;, \u0026#34;完成结算\u0026#34;, \u0026#34;perpetual contract\u0026#34;, # 永续合约 \u0026#34;futures will launch\u0026#34;, # 期货上新 \u0026#34;usdⓢ-margined\u0026#34;, # U本位合约 \u0026#34;coin-margined\u0026#34;, # 币本位合约 \u0026#34;margin will add\u0026#34;, # 杠杆上新 \u0026#34;trading bots services\u0026#34;, # 交易机器人 \u0026#34;trading pairs\u0026#34;, # 交易对调整(非新币) ] # Alpha Box 盲盒 ALPHA_BOX_KEYWORDS = [\u0026#34;alpha box\u0026#34;, \u0026#34;盲盒\u0026#34;, \u0026#34;mystery box\u0026#34;] # ============================================================ # VC 白名单 # ============================================================ TIER1_VCS = [ \u0026#34;binance labs\u0026#34;, \u0026#34;yzi labs\u0026#34;, \u0026#34;coinbase ventures\u0026#34;, \u0026#34;a16z\u0026#34;, \u0026#34;andreessen horowitz\u0026#34;, \u0026#34;paradigm\u0026#34;, \u0026#34;polychain\u0026#34;, \u0026#34;polychain capital\u0026#34;, \u0026#34;sequoia\u0026#34;, \u0026#34;sequoia china\u0026#34;, \u0026#34;sequoia capital\u0026#34;, \u0026#34;multicoin\u0026#34;, \u0026#34;multicoin capital\u0026#34;, \u0026#34;pantera\u0026#34;, \u0026#34;pantera capital\u0026#34;, \u0026#34;dragonfly\u0026#34;, \u0026#34;dragonfly capital\u0026#34;, \u0026#34;founders fund\u0026#34;, ] TIER2_VCS = [ \u0026#34;abcde\u0026#34;, \u0026#34;iosg\u0026#34;, \u0026#34;hashkey\u0026#34;, \u0026#34;okx ventures\u0026#34;, \u0026#34;sevenx\u0026#34;, \u0026#34;folius\u0026#34;, \u0026#34;foresight\u0026#34;, \u0026#34;hashed\u0026#34;, \u0026#34;bitkraft\u0026#34;, \u0026#34;framework\u0026#34;, \u0026#34;framework ventures\u0026#34;, \u0026#34;delphi\u0026#34;, \u0026#34;delphi digital\u0026#34;, \u0026#34;electric capital\u0026#34;, \u0026#34;variant\u0026#34;, \u0026#34;1kx\u0026#34;, \u0026#34;placeholder\u0026#34;, \u0026#34;animoca\u0026#34;, \u0026#34;animoca brands\u0026#34;, \u0026#34;jump\u0026#34;, \u0026#34;jump crypto\u0026#34;, \u0026#34;hack vc\u0026#34;, \u0026#34;bain capital\u0026#34;, ] # 叙事热度 HOT_NARRATIVES = [\u0026#34;defi_perp\u0026#34;, \u0026#34;ai_agent\u0026#34;, \u0026#34;ai_defi\u0026#34;, \u0026#34;defai\u0026#34;, \u0026#34;zk_proof\u0026#34;] WEAK_NARRATIVES = [\u0026#34;gamefi\u0026#34;, \u0026#34;meme\u0026#34;, \u0026#34;social\u0026#34;] # 币安亲儿子信号 BINANCE_DARLING_KEYWORDS = [\u0026#34;yzi labs\u0026#34;, \u0026#34;binance labs\u0026#34;] # ============================================================ # 评级引擎 # ============================================================ TIER_ICONS = {\u0026#34;S\u0026#34;: \u0026#34;🟢🟢🟢\u0026#34;, \u0026#34;A\u0026#34;: \u0026#34;🟡🟡\u0026#34;, \u0026#34;B\u0026#34;: \u0026#34;🟠\u0026#34;, \u0026#34;C\u0026#34;: \u0026#34;⚪\u0026#34;} TIER_LABELS = {\u0026#34;S\u0026#34;: \u0026#34;S 级(必研究)\u0026#34;, \u0026#34;A\u0026#34;: \u0026#34;A 级(值得看)\u0026#34;, \u0026#34;B\u0026#34;: \u0026#34;B 级(正常)\u0026#34;, \u0026#34;C\u0026#34;: \u0026#34;C 级(了解)\u0026#34;} def count_vc_tier(vcs: list, vc_list: list) -\u0026gt; int: count = 0 vcs_lower = [v.lower() for v in vcs] for t in vc_list: if any(t in v for v in vcs_lower): count += 1 return count def rate_project(circ_mcap: float, fdv: float, vcs: list, narrative: str, is_darling: bool) -\u0026gt; dict: \u0026#34;\u0026#34;\u0026#34;S/A/B/C 评级\u0026#34;\u0026#34;\u0026#34; t1 = count_vc_tier(vcs, TIER1_VCS) t2 = count_vc_tier(vcs, TIER2_VCS) hot = narrative in HOT_NARRATIVES weak = narrative in WEAK_NARRATIVES circ_mcap = circ_mcap or 0 fdv = fdv or 0 warnings = [] if weak: warnings.append(f\u0026#34;⚠️ {narrative} 历史破发率较高\u0026#34;) # S级 5条路径 if is_darling: return {\u0026#34;tier\u0026#34;: \u0026#34;S\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;币安亲儿子(YZi/Binance Labs/CZ)\u0026#34;, \u0026#34;warnings\u0026#34;: warnings} if hot and t1 \u0026gt;= 1 and fdv \u0026lt; 500_000_000: return {\u0026#34;tier\u0026#34;: \u0026#34;S\u0026#34;, \u0026#34;reason\u0026#34;: f\u0026#34;热叙事({narrative})+ Tier1 VC\u0026#34;, \u0026#34;warnings\u0026#34;: warnings} if t1 \u0026gt;= 2 and circ_mcap \u0026lt; 50_000_000 and fdv \u0026lt; 300_000_000: return {\u0026#34;tier\u0026#34;: \u0026#34;S\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;≥2家 Tier1 中盘\u0026#34;, \u0026#34;warnings\u0026#34;: warnings} if t1 \u0026gt;= 1 and circ_mcap \u0026lt; 10_000_000 and fdv \u0026lt; 100_000_000: return {\u0026#34;tier\u0026#34;: \u0026#34;S\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Tier1 微盘\u0026#34;, \u0026#34;warnings\u0026#34;: warnings} if hot and circ_mcap \u0026lt; 10_000_000 and fdv \u0026lt; 50_000_000: return {\u0026#34;tier\u0026#34;: \u0026#34;S\u0026#34;, \u0026#34;reason\u0026#34;: f\u0026#34;热叙事({narrative})微盘\u0026#34;, \u0026#34;warnings\u0026#34;: warnings} # A级 if t1 \u0026gt;= 1 and circ_mcap \u0026lt; 20_000_000 and fdv \u0026lt; 200_000_000: return {\u0026#34;tier\u0026#34;: \u0026#34;A\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Tier1 小盘\u0026#34;, \u0026#34;warnings\u0026#34;: warnings} # B级 if circ_mcap \u0026lt; 50_000_000 and fdv \u0026lt; 500_000_000: return {\u0026#34;tier\u0026#34;: \u0026#34;B\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;中盘\u0026#34;, \u0026#34;warnings\u0026#34;: warnings} return {\u0026#34;tier\u0026#34;: \u0026#34;C\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;大盘/弱信号\u0026#34;, \u0026#34;warnings\u0026#34;: warnings} # ============================================================ # 数据库 # ============================================================ def init_db(): Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute(\u0026#34;\u0026#34;\u0026#34; CREATE TABLE IF NOT EXISTS projects ( id TEXT PRIMARY KEY, symbol TEXT NOT NULL, name TEXT, launch_time TEXT, source TEXT, raw_text TEXT, tier TEXT DEFAULT \u0026#39;PENDING\u0026#39;, tier_reason TEXT, narrative TEXT, narrative_desc TEXT, vcs_json TEXT DEFAULT \u0026#39;[]\u0026#39;, is_darling INTEGER DEFAULT 0, open_price REAL, total_supply REAL, circulating_supply REAL, fdv REAL, circulating_mcap REAL, excluded INTEGER DEFAULT 0, exclude_reason TEXT, discovered_at TEXT, updated_at TEXT )\u0026#34;\u0026#34;\u0026#34;) c.execute(\u0026#34;\u0026#34;\u0026#34; CREATE TABLE IF NOT EXISTS pushes ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_id TEXT NOT NULL, push_type TEXT, sent_at TEXT, content TEXT )\u0026#34;\u0026#34;\u0026#34;) c.execute(\u0026#34;\u0026#34;\u0026#34; CREATE TABLE IF NOT EXISTS snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, project_id TEXT NOT NULL, timestamp TEXT NOT NULL, price REAL, circulating_mcap REAL, fdv REAL )\u0026#34;\u0026#34;\u0026#34;) conn.commit() conn.close() def project_id(symbol: str, date_str: str) -\u0026gt; str: return hashlib.md5(f\u0026#34;{symbol.upper()}_{date_str}\u0026#34;.encode()).hexdigest()[:16] def project_exists(pid: str) -\u0026gt; bool: conn = sqlite3.connect(DB_PATH) exists = conn.execute(\u0026#34;SELECT 1 FROM projects WHERE id=?\u0026#34;, (pid,)).fetchone() is not None conn.close() return exists def save_project(project: dict): conn = sqlite3.connect(DB_PATH) now = datetime.utcnow().isoformat() conn.execute(\u0026#34;\u0026#34;\u0026#34; INSERT OR IGNORE INTO projects (id, symbol, name, launch_time, source, raw_text, tier, tier_reason, narrative, narrative_desc, vcs_json, is_darling, open_price, total_supply, circulating_supply, fdv, circulating_mcap, excluded, exclude_reason, discovered_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) \u0026#34;\u0026#34;\u0026#34;, ( project[\u0026#34;id\u0026#34;], project[\u0026#34;symbol\u0026#34;], project.get(\u0026#34;name\u0026#34;), project.get(\u0026#34;launch_time\u0026#34;), project.get(\u0026#34;source\u0026#34;), project.get(\u0026#34;raw_text\u0026#34;), project.get(\u0026#34;tier\u0026#34;, \u0026#34;PENDING\u0026#34;), project.get(\u0026#34;tier_reason\u0026#34;), project.get(\u0026#34;narrative\u0026#34;), project.get(\u0026#34;narrative_desc\u0026#34;), json.dumps(project.get(\u0026#34;vcs\u0026#34;, [])), int(project.get(\u0026#34;is_darling\u0026#34;, False)), project.get(\u0026#34;open_price\u0026#34;), project.get(\u0026#34;total_supply\u0026#34;), project.get(\u0026#34;circulating_supply\u0026#34;), project.get(\u0026#34;fdv\u0026#34;), project.get(\u0026#34;circulating_mcap\u0026#34;), int(project.get(\u0026#34;excluded\u0026#34;, 0)), project.get(\u0026#34;exclude_reason\u0026#34;), now, now, )) conn.commit() conn.close() def update_project(pid: str, fields: dict): if not fields: return conn = sqlite3.connect(DB_PATH) fields[\u0026#34;updated_at\u0026#34;] = datetime.utcnow().isoformat() set_parts = [f\u0026#34;{k}=?\u0026#34; for k in fields] values = list(fields.values()) + [pid] conn.execute(f\u0026#34;UPDATE projects SET {\u0026#39;,\u0026#39;.join(set_parts)} WHERE id=?\u0026#34;, values) conn.commit() conn.close() def get_project(pid: str) -\u0026gt; Optional[dict]: conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row row = conn.execute(\u0026#34;SELECT * FROM projects WHERE id=?\u0026#34;, (pid,)).fetchone() conn.close() return dict(row) if row else None def list_pending() -\u0026gt; list: conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row rows = conn.execute( \u0026#34;SELECT * FROM projects WHERE excluded=0 AND tier=\u0026#39;PENDING\u0026#39; ORDER BY discovered_at\u0026#34; ).fetchall() conn.close() return [dict(r) for r in rows] def list_active() -\u0026gt; list: \u0026#34;\u0026#34;\u0026#34;上线后需要监控的项目（非PENDING非EXCLUDED，有launch_time）\u0026#34;\u0026#34;\u0026#34; conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row rows = conn.execute(\u0026#34;\u0026#34;\u0026#34; SELECT * FROM projects WHERE excluded=0 AND launch_time IS NOT NULL AND launch_time != \u0026#39;\u0026#39; AND tier NOT IN (\u0026#39;PENDING\u0026#39;, \u0026#39;EXCLUDED\u0026#39;, \u0026#39;ERROR\u0026#39;) \u0026#34;\u0026#34;\u0026#34;).fetchall() conn.close() return [dict(r) for r in rows] def has_pushed(pid: str, push_type: str) -\u0026gt; bool: conn = sqlite3.connect(DB_PATH) exists = conn.execute( \u0026#34;SELECT 1 FROM pushes WHERE project_id=? AND push_type=?\u0026#34;, (pid, push_type) ).fetchone() is not None conn.close() return exists def log_push(pid: str, push_type: str, content: str): conn = sqlite3.connect(DB_PATH) conn.execute( \u0026#34;INSERT INTO pushes (project_id, push_type, sent_at, content) VALUES (?,?,?,?)\u0026#34;, (pid, push_type, datetime.utcnow().isoformat(), content) ) conn.commit() conn.close() def save_snapshot(pid: str, price: float, mcap: float, fdv: float): conn = sqlite3.connect(DB_PATH) conn.execute( \u0026#34;INSERT INTO snapshots (project_id, timestamp, price, circulating_mcap, fdv) VALUES (?,?,?,?,?)\u0026#34;, (pid, datetime.utcnow().isoformat(), price, mcap, fdv) ) conn.commit() conn.close() # ============================================================ # TG 推送 # ============================================================ async def send_tg(text: str, silent: bool = False) -\u0026gt; bool: if not TG_BOT_TOKEN or not TG_CHAT_ID: logger.error(\u0026#34;TG_BOT_TOKEN 或 TG_CHAT_ID 未配置\u0026#34;) return False url = f\u0026#34;https://api.telegram.org/bot{TG_BOT_TOKEN}/sendMessage\u0026#34; payload = { \u0026#34;chat_id\u0026#34;: TG_CHAT_ID, \u0026#34;text\u0026#34;: text, \u0026#34;parse_mode\u0026#34;: \u0026#34;HTML\u0026#34;, \u0026#34;disable_notification\u0026#34;: silent, } try: async with httpx.AsyncClient(timeout=15, headers=HEADERS) as client: resp = await client.post(url, json=payload) if resp.status_code != 200: logger.error(f\u0026#34;TG发送失败 {resp.status_code}: {resp.text[:200]}\u0026#34;) return False return True except Exception as e: logger.error(f\u0026#34;TG发送异常: {e}\u0026#34;) return False # ============================================================ # 公告标题解析 # ============================================================ def is_trigger(title: str) -\u0026gt; tuple[bool, Optional[str]]: t = title.lower() for kw in EXCLUDE_KEYWORDS: if kw.lower() in t: return False, f\u0026#34;排除: {kw}\u0026#34; for kw in ALPHA_BOX_KEYWORDS: if kw.lower() in t: return False, \u0026#34;Alpha Box 盲盒\u0026#34; for kw in TRIGGER_KEYWORDS: if kw.lower() in t: return True, None return False, None def extract_symbol(title: str) -\u0026gt; Optional[str]: # 英文括号: \u0026#34;Chip (CHIP)\u0026#34; m = re.search(r\u0026#34;\\(([A-Z0-9]{2,10})\\)\u0026#34;, title) if m: return m.group(1) # 中文括号 m = re.search(r\u0026#34;（([A-Z0-9]{2,10})）\u0026#34;, title) if m: return m.group(1) return None def extract_name(title: str) -\u0026gt; Optional[str]: patterns = [ r\u0026#34;(?:上线|List|list|Launch|launch|featured)\\s+([A-Za-z0-9 ]+?)\\s*[\\(（]\u0026#34;, ] for p in patterns: m = re.search(p, title, re.IGNORECASE) if m: return m.group(1).strip() return None # ============================================================ # 币安公告抓取 # ============================================================ async def fetch_announcements(limit: int = 20) -\u0026gt; list: \u0026#34;\u0026#34;\u0026#34;抓取币安最新公告\u0026#34;\u0026#34;\u0026#34; all_articles = [] # 48: New Cryptocurrency Listing, 161: Latest Activities, 93: Latest News for catalog_id in [48, 161, 93]: params = {\u0026#34;type\u0026#34;: 1, \u0026#34;catalogId\u0026#34;: catalog_id, \u0026#34;pageNo\u0026#34;: 1, \u0026#34;pageSize\u0026#34;: limit} try: async with httpx.AsyncClient(timeout=15, headers=HEADERS) as client: resp = await client.get(BINANCE_ANNOUNCEMENT_API, params=params) resp.raise_for_status() data = resp.json() for catalog in data.get(\u0026#34;data\u0026#34;, {}).get(\u0026#34;catalogs\u0026#34;, []): for a in catalog.get(\u0026#34;articles\u0026#34;, []): a[\u0026#34;_catalog_id\u0026#34;] = catalog_id all_articles.append(a) except Exception as e: logger.warning(f\u0026#34;抓取分类 {catalog_id} 失败: {e}\u0026#34;) # 去重 seen = set() unique = [] for a in all_articles: code = a.get(\u0026#34;code\u0026#34;) if code and code not in seen: seen.add(code) unique.append(a) return unique # ============================================================ # CoinGecko 数据 # ============================================================ async def fetch_coingecko(symbol: str, project_name: str = \u0026#34;\u0026#34;) -\u0026gt; dict: \u0026#34;\u0026#34;\u0026#34;查CoinGecko代币经济数据。双重匹配：symbol精确匹配 + 项目名模糊匹配\u0026#34;\u0026#34;\u0026#34; result = {\u0026#34;found\u0026#34;: False, \u0026#34;price\u0026#34;: None, \u0026#34;fdv\u0026#34;: None, \u0026#34;mcap\u0026#34;: None, \u0026#34;total_supply\u0026#34;: None, \u0026#34;circ_supply\u0026#34;: None, \u0026#34;chain\u0026#34;: None, \u0026#34;contract\u0026#34;: None} try: async with httpx.AsyncClient(timeout=15, headers=HEADERS) as client: # 搜索策略：先搜symbol，如果项目名不同再搜项目名 queries = [symbol] if project_name and project_name.upper() != symbol.upper(): queries.append(project_name) coin_id = None best_rank = 999999 name_exact_match = None # 项目名精确匹配优先 for query in queries: resp = await client.get(\u0026#34;https://api.coingecko.com/api/v3/search\u0026#34;, params={\u0026#34;query\u0026#34;: query}) if resp.status_code != 200: continue coins = resp.json().get(\u0026#34;coins\u0026#34;, []) for c in coins: c_sym = c.get(\u0026#34;symbol\u0026#34;, \u0026#34;\u0026#34;).upper() c_name = c.get(\u0026#34;name\u0026#34;, \u0026#34;\u0026#34;).lower() c_rank = c.get(\u0026#34;market_cap_rank\u0026#34;) or 999999 # 项目名精确匹配（最高优先级，如MegaETH -\u0026gt; megaeth） if project_name and c_name == project_name.lower(): name_exact_match = c[\u0026#34;id\u0026#34;] # 精确symbol匹配 — 取market_cap_rank最高（最小）的 if c_sym == symbol.upper(): if c_rank \u0026lt; best_rank: coin_id = c[\u0026#34;id\u0026#34;] best_rank = c_rank # 项目名模糊匹配（如MegaETH搜mega） if project_name and project_name.lower() in c_name: if c_rank \u0026lt; best_rank: coin_id = c[\u0026#34;id\u0026#34;] best_rank = c_rank # 项目名精确匹配 \u0026gt; 其他匹配 if name_exact_match: coin_id = name_exact_match if not coin_id: logger.info(f\u0026#34;CoinGecko未找到 {symbol}/{project_name}\u0026#34;) return result logger.info(f\u0026#34;CoinGecko匹配: {symbol} -\u0026gt; {coin_id} (rank={best_rank})\u0026#34;) resp2 = await client.get( f\u0026#34;https://api.coingecko.com/api/v3/coins/{coin_id}\u0026#34;, params={\u0026#34;localization\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;tickers\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;market_data\u0026#34;: \u0026#34;true\u0026#34;, \u0026#34;community_data\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;developer_data\u0026#34;: \u0026#34;false\u0026#34;} ) if resp2.status_code == 429: await asyncio.sleep(5) resp2 = await client.get( f\u0026#34;https://api.coingecko.com/api/v3/coins/{coin_id}\u0026#34;, params={\u0026#34;localization\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;tickers\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;market_data\u0026#34;: \u0026#34;true\u0026#34;, \u0026#34;community_data\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;developer_data\u0026#34;: \u0026#34;false\u0026#34;} ) if resp2.status_code != 200: return result d = resp2.json() md = d.get(\u0026#34;market_data\u0026#34;, {}) result.update({ \u0026#34;found\u0026#34;: True, \u0026#34;price\u0026#34;: (md.get(\u0026#34;current_price\u0026#34;) or {}).get(\u0026#34;usd\u0026#34;), \u0026#34;fdv\u0026#34;: (md.get(\u0026#34;fully_diluted_valuation\u0026#34;) or {}).get(\u0026#34;usd\u0026#34;), \u0026#34;mcap\u0026#34;: (md.get(\u0026#34;market_cap\u0026#34;) or {}).get(\u0026#34;usd\u0026#34;), \u0026#34;total_supply\u0026#34;: md.get(\u0026#34;total_supply\u0026#34;), \u0026#34;circ_supply\u0026#34;: md.get(\u0026#34;circulating_supply\u0026#34;), }) # 提取categories（含VC信息如\u0026#34;YZi Labs Portfolio\u0026#34;）和description result[\u0026#34;categories\u0026#34;] = d.get(\u0026#34;categories\u0026#34;, []) result[\u0026#34;description\u0026#34;] = (d.get(\u0026#34;description\u0026#34;) or {}).get(\u0026#34;en\u0026#34;, \u0026#34;\u0026#34;)[:500] platforms = d.get(\u0026#34;platforms\u0026#34;, {}) for chain, addr in platforms.items(): if addr: result[\u0026#34;chain\u0026#34;] = chain result[\u0026#34;contract\u0026#34;] = addr break # CoinGecko没价格时（新币常见），从币安合约/现货补充 if not result[\u0026#34;price\u0026#34;]: binance_price = await _fetch_binance_price(symbol, client) if binance_price: result[\u0026#34;price\u0026#34;] = binance_price ts = result.get(\u0026#34;total_supply\u0026#34;) or 0 cs = result.get(\u0026#34;circ_supply\u0026#34;) or 0 if ts \u0026gt; 0: result[\u0026#34;fdv\u0026#34;] = binance_price * ts if cs \u0026gt; 0: result[\u0026#34;mcap\u0026#34;] = binance_price * cs logger.info(f\u0026#34;币安补充价格 {symbol}: ${binance_price}, FDV=${result.get(\u0026#39;fdv\u0026#39;,0):,.0f}\u0026#34;) except Exception as e: logger.warning(f\u0026#34;CoinGecko查询失败 {symbol}: {e}\u0026#34;) return result async def _fetch_binance_price(symbol: str, client: httpx.AsyncClient) -\u0026gt; float: \u0026#34;\u0026#34;\u0026#34;从币安现货或合约获取价格\u0026#34;\u0026#34;\u0026#34; pair = f\u0026#34;{symbol.upper()}USDT\u0026#34; # 1. 现货 try: resp = await client.get(f\u0026#34;https://api.binance.com/api/v3/ticker/price\u0026#34;, params={\u0026#34;symbol\u0026#34;: pair}) if resp.status_code == 200: return float(resp.json()[\u0026#34;price\u0026#34;]) except Exception: pass # 2. 合约 try: resp = await client.get(f\u0026#34;https://fapi.binance.com/fapi/v1/ticker/price\u0026#34;, params={\u0026#34;symbol\u0026#34;: pair}) if resp.status_code == 200: return float(resp.json()[\u0026#34;price\u0026#34;]) except Exception: pass return 0.0 # ============================================================ # LLM 叙事抽取（可选，降级为规则） # ============================================================ async def llm_extract(raw_text: str, symbol: str, name: str = \u0026#34;\u0026#34;, cg_data: dict = None) -\u0026gt; dict: \u0026#34;\u0026#34;\u0026#34;用LLM从公告+CoinGecko数据抽取叙事/VC/是否亲儿子\u0026#34;\u0026#34;\u0026#34; fallback = { \u0026#34;narrative\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;narrative_desc\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;vcs\u0026#34;: [], \u0026#34;is_darling\u0026#34;: False, \u0026#34;exclude_reason\u0026#34;: None, } # 从CoinGecko categories自动提取信息 cg_data = cg_data or {} categories = cg_data.get(\u0026#34;categories\u0026#34;, []) description = cg_data.get(\u0026#34;description\u0026#34;, \u0026#34;\u0026#34;) # 自动检测亲儿子（从categories） darling_cats = [c for c in categories if any(kw in c.lower() for kw in [\u0026#34;yzi labs\u0026#34;, \u0026#34;binance labs\u0026#34;])] if darling_cats: fallback[\u0026#34;is_darling\u0026#34;] = True if not ANTHROPIC_API_KEY: # 降级：从标题+categories关键词猜 t = raw_text.lower() for kw in BINANCE_DARLING_KEYWORDS: if kw in t: fallback[\u0026#34;is_darling\u0026#34;] = True # 从categories猜叙事 cat_str = \u0026#34; \u0026#34;.join(categories).lower() if \u0026#34;defi\u0026#34; in cat_str: fallback[\u0026#34;narrative\u0026#34;] = \u0026#34;defi\u0026#34; elif \u0026#34;ai\u0026#34; in cat_str: fallback[\u0026#34;narrative\u0026#34;] = \u0026#34;ai_agent\u0026#34; elif \u0026#34;gaming\u0026#34; in cat_str or \u0026#34;gamefi\u0026#34; in cat_str: fallback[\u0026#34;narrative\u0026#34;] = \u0026#34;gamefi\u0026#34; elif \u0026#34;meme\u0026#34; in cat_str: fallback[\u0026#34;narrative\u0026#34;] = \u0026#34;meme\u0026#34; elif \u0026#34;rwa\u0026#34; in cat_str or \u0026#34;real world\u0026#34; in cat_str: fallback[\u0026#34;narrative\u0026#34;] = \u0026#34;rwa\u0026#34; return fallback # 构建丰富的上下文 extra_context = \u0026#34;\u0026#34; if categories: extra_context += f\u0026#34;\\nCoinGecko分类: {\u0026#39;, \u0026#39;.join(categories)}\u0026#34; if description: extra_context += f\u0026#34;\\n项目描述: {description[:300]}\u0026#34; if cg_data.get(\u0026#34;found\u0026#34;): extra_context += f\u0026#34;\\n市场数据: FDV=${cg_data.get(\u0026#39;fdv\u0026#39;,0):,.0f}, MCap=${cg_data.get(\u0026#39;mcap\u0026#39;,0):,.0f}, 价格=${cg_data.get(\u0026#39;price\u0026#39;,0)}\u0026#34; if cg_data.get(\u0026#34;chain\u0026#34;): extra_context += f\u0026#34;, 链={cg_data[\u0026#39;chain\u0026#39;]}\u0026#34; system = \u0026#34;你是加密货币研究员，从币安公告和项目数据中提取关键信息。只返回JSON，无其他文字。\u0026#34; user = f\u0026#34;\u0026#34;\u0026#34;分析这个币安上新项目： 代币: {symbol}, 项目名: {name or \u0026#34;未知\u0026#34;} 公告原文: {raw_text} {extra_context} 返回JSON: {{ \u0026#34;narrative\u0026#34;: \u0026#34;defi_perp|ai_agent|ai_defi|defai|zk_proof|infra|defi|rwa|gamefi|meme|social|stablecoin|unknown\u0026#34;, \u0026#34;narrative_desc\u0026#34;: \u0026#34;一句话中文描述这个项目做什么、有什么特点\u0026#34;, \u0026#34;vcs\u0026#34;: [\u0026#34;从CoinGecko分类和公告中提取的投资机构列表\u0026#34;], \u0026#34;is_darling\u0026#34;: true/false, \u0026#34;exclude_reason\u0026#34;: null|\u0026#34;already_tge\u0026#34;|\u0026#34;meme_only\u0026#34; }} 判断规则: - narrative: 选最主要的一个类别 - vcs: CoinGecko分类里如果有 \u0026#34;XXX Portfolio\u0026#34; 就提取XXX作为机构 - is_darling: 如果有YZi Labs/Binance Labs投资 或 CZ/何一站台 则true - exclude_reason: 只有当项目在其他主要CEX(如Coinbase/OKX/Bybit)上线超过3个月才算\u0026#34;already_tge\u0026#34;。如果只是在DEX或刚在币安上线，不算already_tge。CoinGecko有价格数据不代表already_tge。纯meme无叙事则\u0026#34;meme_only\u0026#34; \u0026#34;\u0026#34;\u0026#34; try: async with httpx.AsyncClient(timeout=30, headers=HEADERS) as client: resp = await client.post( f\u0026#34;{ANTHROPIC_BASE_URL.rstrip(\u0026#39;/\u0026#39;)}/v1/messages\u0026#34;, headers={ \u0026#34;x-api-key\u0026#34;: ANTHROPIC_API_KEY, \u0026#34;anthropic-version\u0026#34;: \u0026#34;2023-06-01\u0026#34;, \u0026#34;content-type\u0026#34;: \u0026#34;application/json\u0026#34;, }, json={ \u0026#34;model\u0026#34;: ANTHROPIC_MODEL, \u0026#34;max_tokens\u0026#34;: 800, \u0026#34;temperature\u0026#34;: 0, \u0026#34;system\u0026#34;: system, \u0026#34;messages\u0026#34;: [{\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: user}], } ) if resp.status_code != 200: logger.warning(f\u0026#34;LLM调用失败 {resp.status_code}\u0026#34;) return fallback data = resp.json() text = \u0026#34;\u0026#34; for block in data.get(\u0026#34;content\u0026#34;, []): if block.get(\u0026#34;type\u0026#34;) == \u0026#34;text\u0026#34;: text = block.get(\u0026#34;text\u0026#34;, \u0026#34;\u0026#34;) break text = text.strip() if text.startswith(\u0026#34;```\u0026#34;): lines = text.split(\u0026#34;\\n\u0026#34;) text = \u0026#34;\\n\u0026#34;.join(lines[1:-1]) return json.loads(text) except Exception as e: logger.warning(f\u0026#34;LLM抽取异常: {e}\u0026#34;) return fallback # ============================================================ # 消息格式化 # ============================================================ def _fmt_mcap(v): if not v: return \u0026#34;N/A\u0026#34; if v \u0026gt;= 1e9: return f\u0026#34;${v/1e9:.1f}B\u0026#34; if v \u0026gt;= 1e6: return f\u0026#34;${v/1e6:.1f}M\u0026#34; if v \u0026gt;= 1e3: return f\u0026#34;${v/1e3:.0f}K\u0026#34; return f\u0026#34;${v:.0f}\u0026#34; def _fmt_price(v): if not v: return \u0026#34;N/A\u0026#34; if v \u0026gt;= 1: return f\u0026#34;${v:.2f}\u0026#34; if v \u0026gt;= 0.01: return f\u0026#34;${v:.4f}\u0026#34; return f\u0026#34;${v:.6f}\u0026#34; def fmt_discovery(p: dict) -\u0026gt; str: tier = p.get(\u0026#34;tier\u0026#34;, \u0026#34;C\u0026#34;) icon = TIER_ICONS.get(tier, \u0026#34;⚪\u0026#34;) label = TIER_LABELS.get(tier, \u0026#34;\u0026#34;) symbol = p[\u0026#34;symbol\u0026#34;] name = p.get(\u0026#34;name\u0026#34;) or \u0026#34;\u0026#34; vcs = json.loads(p.get(\u0026#34;vcs_json\u0026#34;, \u0026#34;[]\u0026#34;)) if isinstance(p.get(\u0026#34;vcs_json\u0026#34;), str) else p.get(\u0026#34;vcs\u0026#34;, []) lines = [ f\u0026#34;{icon} \u0026lt;b\u0026gt;Alpha 首发 · ${symbol}\u0026lt;/b\u0026gt; {icon}\u0026#34;, f\u0026#34;📋 {label}\u0026#34;, \u0026#34;\u0026#34;, f\u0026#34;\u0026lt;b\u0026gt;{name}\u0026lt;/b\u0026gt;\u0026#34; if name else \u0026#34;\u0026#34;, ] if p.get(\u0026#34;narrative_desc\u0026#34;): lines.append(f\u0026#34;💡 {p[\u0026#39;narrative_desc\u0026#39;]}\u0026#34;) if p.get(\u0026#34;narrative\u0026#34;) and p[\u0026#34;narrative\u0026#34;] != \u0026#34;unknown\u0026#34;: lines.append(f\u0026#34;🏷 叙事: {p[\u0026#39;narrative\u0026#39;]}\u0026#34;) lines.append(\u0026#34;\u0026#34;) if p.get(\u0026#34;fdv\u0026#34;): lines.append(f\u0026#34;📊 FDV: {_fmt_mcap(p[\u0026#39;fdv\u0026#39;])}\u0026#34;) if p.get(\u0026#34;circulating_mcap\u0026#34;): lines.append(f\u0026#34;📊 流通市值: {_fmt_mcap(p[\u0026#39;circulating_mcap\u0026#39;])}\u0026#34;) if p.get(\u0026#34;open_price\u0026#34;): lines.append(f\u0026#34;💰 预估开盘价: {_fmt_price(p[\u0026#39;open_price\u0026#39;])}\u0026#34;) if p.get(\u0026#34;total_supply\u0026#34;) and p.get(\u0026#34;circulating_supply\u0026#34;): pct = p[\u0026#34;circulating_supply\u0026#34;] / p[\u0026#34;total_supply\u0026#34;] * 100 lines.append(f\u0026#34;📦 初始流通: {pct:.1f}%\u0026#34;) if vcs: lines.append(\u0026#34;\u0026#34;) lines.append(\u0026#34;🏛 \u0026lt;b\u0026gt;机构\u0026lt;/b\u0026gt;\u0026#34;) for v in vcs[:5]: is_t1 = any(t in v.lower() for t in TIER1_VCS) lines.append(f\u0026#34; {\u0026#39;⭐\u0026#39; if is_t1 else \u0026#39;·\u0026#39;} {v}\u0026#34;) if p.get(\u0026#34;is_darling\u0026#34;): lines.append(\u0026#34;\u0026#34;) lines.append(\u0026#34;🔥 \u0026lt;b\u0026gt;币安亲儿子\u0026lt;/b\u0026gt;\u0026#34;) if p.get(\u0026#34;tier_reason\u0026#34;): lines.append(\u0026#34;\u0026#34;) lines.append(f\u0026#34;🎯 {p[\u0026#39;tier_reason\u0026#39;]}\u0026#34;) lines.append(\u0026#34;\u0026#34;) lines.append(f\u0026#34;\u0026lt;i\u0026gt;📌 来源: {p.get(\u0026#39;source\u0026#39;, \u0026#39;binance\u0026#39;)}\u0026lt;/i\u0026gt;\u0026#34;) if p.get(\u0026#34;raw_text\u0026#34;): lines.append(f\u0026#34;\u0026lt;i\u0026gt;{p[\u0026#39;raw_text\u0026#39;][:120]}\u0026lt;/i\u0026gt;\u0026#34;) return \u0026#34;\\n\u0026#34;.join(l for l in lines if l is not None) def fmt_countdown(p: dict, minutes: int) -\u0026gt; str: icon = TIER_ICONS.get(p.get(\u0026#34;tier\u0026#34;, \u0026#34;C\u0026#34;), \u0026#34;⚪\u0026#34;) t = f\u0026#34;{minutes//60}h{minutes%60}m\u0026#34; if minutes \u0026gt;= 60 else f\u0026#34;{minutes}m\u0026#34; lines = [ f\u0026#34;{icon} \u0026lt;b\u0026gt;倒计时提醒\u0026lt;/b\u0026gt;\u0026#34;, f\u0026#34;\u0026lt;b\u0026gt;${p[\u0026#39;symbol\u0026#39;]}\u0026lt;/b\u0026gt; · {p.get(\u0026#39;name\u0026#39;, \u0026#39;\u0026#39;)}\u0026#34;, f\u0026#34;⏰ 距上线还有 \u0026lt;b\u0026gt;{t}\u0026lt;/b\u0026gt;\u0026#34;, ] if p.get(\u0026#34;fdv\u0026#34;): lines.append(f\u0026#34;FDV: {_fmt_mcap(p[\u0026#39;fdv\u0026#39;])}\u0026#34;) if minutes \u0026lt;= 30: lines.append(\u0026#34;🔔 \u0026lt;b\u0026gt;准备下单\u0026lt;/b\u0026gt;\u0026#34;) return \u0026#34;\\n\u0026#34;.join(lines) def fmt_launch(p: dict, price: float, mcap: float, fdv: float) -\u0026gt; str: lines = [ f\u0026#34;🚀 \u0026lt;b\u0026gt;${p[\u0026#39;symbol\u0026#39;]} 已上线\u0026lt;/b\u0026gt;\u0026#34;, f\u0026#34;开盘价: \u0026lt;b\u0026gt;{_fmt_price(price)}\u0026lt;/b\u0026gt;\u0026#34;, f\u0026#34;流通市值: \u0026lt;b\u0026gt;{_fmt_mcap(mcap)}\u0026lt;/b\u0026gt;\u0026#34;, f\u0026#34;FDV: \u0026lt;b\u0026gt;{_fmt_mcap(fdv)}\u0026lt;/b\u0026gt;\u0026#34;, ] return \u0026#34;\\n\u0026#34;.join(lines) def fmt_periodic(p: dict, idx: int, price: float, mcap: float, change_pct: float) -\u0026gt; str: arrow = \u0026#34;📈\u0026#34; if change_pct \u0026gt; 0 else \u0026#34;📉\u0026#34; minutes = 30 * idx lines = [ f\u0026#34;⏱ \u0026lt;b\u0026gt;${p[\u0026#39;symbol\u0026#39;]} · +{minutes}min\u0026lt;/b\u0026gt;\u0026#34;, f\u0026#34;流通市值: {_fmt_mcap(mcap)} ({arrow} {change_pct:+.1f}%)\u0026#34;, f\u0026#34;当前价: {_fmt_price(price)}\u0026#34;, ] if change_pct \u0026gt;= 100: lines.append(\u0026#34;💡 \u0026lt;b\u0026gt;已翻倍，考虑分批止盈\u0026lt;/b\u0026gt;\u0026#34;) elif change_pct \u0026lt;= -30: lines.append(\u0026#34;⚠️ 跌幅较大，评估是否止损\u0026#34;) return \u0026#34;\\n\u0026#34;.join(lines) def fmt_anomaly(p: dict, atype: str, price: float, change_pct: float) -\u0026gt; str: emoji = {\u0026#34;double\u0026#34;: \u0026#34;🚀\u0026#34;, \u0026#34;halve\u0026#34;: \u0026#34;🔻\u0026#34;}.get(atype, \u0026#34;⚡\u0026#34;) desc = {\u0026#34;double\u0026#34;: \u0026#34;市值翻倍\u0026#34;, \u0026#34;halve\u0026#34;: \u0026#34;市值腰斩\u0026#34;}.get(atype, \u0026#34;异动\u0026#34;) return f\u0026#34;{emoji} \u0026lt;b\u0026gt;${p[\u0026#39;symbol\u0026#39;]} {desc}\u0026lt;/b\u0026gt;\\n变化: {change_pct:+.1f}%\\n当前价: {_fmt_price(price)}\u0026#34; # ============================================================ # 核心逻辑: 公告监听 # ============================================================ async def announcement_listener(): \u0026#34;\u0026#34;\u0026#34;轮询币安公告，发现新Alpha项目\u0026#34;\u0026#34;\u0026#34; logger.info(f\u0026#34;📡 公告监听启动 · 轮询 {ANNOUNCEMENT_POLL_INTERVAL}s\u0026#34;) while True: try: articles = await fetch_announcements() new_count = 0 for art in articles: title = art.get(\u0026#34;title\u0026#34;, \u0026#34;\u0026#34;) triggered, reason = is_trigger(title) if not triggered: continue symbol = extract_symbol(title) if not symbol: continue # 用发布日期去重 release_ts = art.get(\u0026#34;releaseDate\u0026#34;) release_iso = datetime.fromtimestamp(release_ts / 1000).isoformat() if release_ts else \u0026#34;\u0026#34; launch_date = release_iso[:10] if release_iso else datetime.utcnow().date().isoformat() pid = project_id(symbol, launch_date) if project_exists(pid): continue project = { \u0026#34;id\u0026#34;: pid, \u0026#34;symbol\u0026#34;: symbol, \u0026#34;name\u0026#34;: extract_name(title), \u0026#34;launch_time\u0026#34;: release_iso, \u0026#34;source\u0026#34;: \u0026#34;binance_announcement\u0026#34;, \u0026#34;raw_text\u0026#34;: title, \u0026#34;tier\u0026#34;: \u0026#34;PENDING\u0026#34;, \u0026#34;vcs\u0026#34;: [], \u0026#34;is_darling\u0026#34;: False, \u0026#34;excluded\u0026#34;: 0, } save_project(project) new_count += 1 logger.info(f\u0026#34;🆕 发现 ${symbol}: {title[:80]}\u0026#34;) if new_count: logger.info(f\u0026#34;本轮发现 {new_count} 个新项目\u0026#34;) except Exception as e: logger.error(f\u0026#34;公告监听异常: {e}\u0026#34;, exc_info=True) await asyncio.sleep(ANNOUNCEMENT_POLL_INTERVAL) # ============================================================ # 核心逻辑: 聚合 + 推送 # ============================================================ async def aggregation_worker(): \u0026#34;\u0026#34;\u0026#34;对PENDING项目做数据聚合、评级、推送\u0026#34;\u0026#34;\u0026#34; logger.info(f\u0026#34;🧠 聚合工作者启动 · 轮询 {AGGREGATION_POLL_INTERVAL}s\u0026#34;) while True: try: pending = list_pending() for p in pending: symbol = p[\u0026#34;symbol\u0026#34;] try: logger.info(f\u0026#34;📦 聚合 ${symbol}\u0026#34;) # 1. CoinGecko cg = await fetch_coingecko(symbol, project_name=p.get(\u0026#39;name\u0026#39;, \u0026#39;\u0026#39;)) await asyncio.sleep(1) # 避免限流 # 2. LLM抽取叙事（传入CoinGecko数据增强分析） llm = await llm_extract(p.get(\u0026#34;raw_text\u0026#34;, \u0026#34;\u0026#34;), symbol, p.get(\u0026#34;name\u0026#34;), cg_data=cg) await asyncio.sleep(1) # 3. 判断是否排除 if llm.get(\u0026#34;exclude_reason\u0026#34;) in (\u0026#34;already_tge\u0026#34;, \u0026#34;meme_only\u0026#34;): update_project(p[\u0026#34;id\u0026#34;], { \u0026#34;excluded\u0026#34;: 1, \u0026#34;exclude_reason\u0026#34;: llm[\u0026#34;exclude_reason\u0026#34;], \u0026#34;tier\u0026#34;: \u0026#34;EXCLUDED\u0026#34;, }) logger.info(f\u0026#34;⏭ ${symbol} 排除: {llm[\u0026#39;exclude_reason\u0026#39;]}\u0026#34;) continue # 4. 数据校验 — CoinGecko数据可能匹配错或项目太新没数据 cg_fdv = cg.get(\u0026#34;fdv\u0026#34;, 0) or 0 cg_mcap = cg.get(\u0026#34;mcap\u0026#34;, 0) or 0 data_suspect = False data_warnings = [] # 上币安的项目FDV不可能低于$100K（除非CoinGecko匹配错了） if cg.get(\u0026#34;found\u0026#34;) and cg_fdv \u0026gt; 0 and cg_fdv \u0026lt; 100_000: data_suspect = True data_warnings.append(f\u0026#34;FDV=${cg_fdv:,.0f}异常低，可能CoinGecko匹配错误\u0026#34;) logger.warning(f\u0026#34;⚠️ {symbol} FDV=${cg_fdv:,.0f} 异常低，数据可能不准\u0026#34;) # 流通100%的大项目也要警惕（新代币通常不会100%流通） if (cg.get(\u0026#34;circ_supply\u0026#34;) and cg.get(\u0026#34;total_supply\u0026#34;) and cg[\u0026#34;circ_supply\u0026#34;] == cg[\u0026#34;total_supply\u0026#34;] and cg_fdv \u0026lt; 1_000_000): data_suspect = True data_warnings.append(\u0026#34;流通=总量且FDV极低，可能是同名垃圾币\u0026#34;) if data_suspect: # 标记数据不可靠，评级时不使用CoinGecko的FDV/MCap cg_fdv = 0 cg_mcap = 0 logger.warning(f\u0026#34;⚠️ {symbol} CoinGecko数据不可靠，评级使用LLM判断为主\u0026#34;) # 5. 评级 is_darling = llm.get(\u0026#34;is_darling\u0026#34;, False) vcs = llm.get(\u0026#34;vcs\u0026#34;, []) narrative = llm.get(\u0026#34;narrative\u0026#34;, \u0026#34;unknown\u0026#34;) rating = rate_project( cg_mcap, cg_fdv, vcs, narrative, is_darling ) # 6. 更新DB（data_suspect时不存CoinGecko的错误数据） update_project(p[\u0026#34;id\u0026#34;], { \u0026#34;tier\u0026#34;: rating[\u0026#34;tier\u0026#34;], \u0026#34;tier_reason\u0026#34;: rating[\u0026#34;reason\u0026#34;], \u0026#34;narrative\u0026#34;: narrative, \u0026#34;narrative_desc\u0026#34;: llm.get(\u0026#34;narrative_desc\u0026#34;, \u0026#34;\u0026#34;), \u0026#34;vcs_json\u0026#34;: json.dumps(vcs), \u0026#34;is_darling\u0026#34;: int(is_darling), \u0026#34;open_price\u0026#34;: cg.get(\u0026#34;price\u0026#34;) if not data_suspect else None, \u0026#34;total_supply\u0026#34;: cg.get(\u0026#34;total_supply\u0026#34;) if not data_suspect else None, \u0026#34;circulating_supply\u0026#34;: cg.get(\u0026#34;circ_supply\u0026#34;) if not data_suspect else None, \u0026#34;fdv\u0026#34;: cg_fdv if cg_fdv else None, \u0026#34;circulating_mcap\u0026#34;: cg_mcap if cg_mcap else None, }) # 6. 推送discovery full = get_project(p[\u0026#34;id\u0026#34;]) if full and not has_pushed(p[\u0026#34;id\u0026#34;], \u0026#34;discovery\u0026#34;): text = fmt_discovery(full) silent = rating[\u0026#34;tier\u0026#34;] in (\u0026#34;B\u0026#34;, \u0026#34;C\u0026#34;) ok = await send_tg(text, silent=silent) if ok: log_push(p[\u0026#34;id\u0026#34;], \u0026#34;discovery\u0026#34;, text) logger.info(f\u0026#34;✅ 推送 ${symbol} [{rating[\u0026#39;tier\u0026#39;]}]\u0026#34;) except Exception as e: logger.error(f\u0026#34;聚合 {symbol} 失败: {e}\u0026#34;, exc_info=True) update_project(p[\u0026#34;id\u0026#34;], {\u0026#34;tier\u0026#34;: \u0026#34;ERROR\u0026#34;, \u0026#34;tier_reason\u0026#34;: str(e)[:100]}) except Exception as e: logger.error(f\u0026#34;聚合循环异常: {e}\u0026#34;, exc_info=True) await asyncio.sleep(AGGREGATION_POLL_INTERVAL) # ============================================================ # 核心逻辑: 上线后监控 # ============================================================ async def post_launch_monitor(): \u0026#34;\u0026#34;\u0026#34;倒计时提醒 + 上线瞬间 + 30min×4跟踪 + 异动\u0026#34;\u0026#34;\u0026#34; logger.info(f\u0026#34;📊 上线监控启动 · 轮询 {MONITOR_POLL_INTERVAL}s\u0026#34;) while True: try: projects = list_active() for p in projects: try: await _monitor_project(p) except Exception as e: logger.error(f\u0026#34;监控 {p[\u0026#39;symbol\u0026#39;]} 异常: {e}\u0026#34;) except Exception as e: logger.error(f\u0026#34;监控循环异常: {e}\u0026#34;, exc_info=True) await asyncio.sleep(MONITOR_POLL_INTERVAL) async def _monitor_project(p: dict): pid = p[\u0026#34;id\u0026#34;] symbol = p[\u0026#34;symbol\u0026#34;] launch_str = p.get(\u0026#34;launch_time\u0026#34;, \u0026#34;\u0026#34;) if not launch_str: return try: launch = datetime.fromisoformat(launch_str.replace(\u0026#34;Z\u0026#34;, \u0026#34;\u0026#34;).split(\u0026#34;+\u0026#34;)[0]) except: return now = datetime.utcnow() delta_sec = (launch - now).total_seconds() # T-3h if 3*3600 - 300 \u0026lt;= delta_sec \u0026lt;= 3*3600 + 300: if not has_pushed(pid, \u0026#34;t_minus_3h\u0026#34;): text = fmt_countdown(p, int(delta_sec / 60)) ok = await send_tg(text, silent=p.get(\u0026#34;tier\u0026#34;) in (\u0026#34;B\u0026#34;, \u0026#34;C\u0026#34;)) if ok: log_push(pid, \u0026#34;t_minus_3h\u0026#34;, text) # T-30m elif 30*60 - 150 \u0026lt;= delta_sec \u0026lt;= 30*60 + 150: if not has_pushed(pid, \u0026#34;t_minus_30m\u0026#34;): text = fmt_countdown(p, int(delta_sec / 60)) ok = await send_tg(text, silent=False) if ok: log_push(pid, \u0026#34;t_minus_30m\u0026#34;, text) # 上线瞬间 elif -300 \u0026lt;= delta_sec \u0026lt;= 0: if not has_pushed(pid, \u0026#34;at_launch\u0026#34;): cg = await fetch_coingecko(symbol, project_name=p.get(\u0026#39;name\u0026#39;, \u0026#39;\u0026#39;)) if cg.get(\u0026#34;price\u0026#34;): text = fmt_launch(p, cg[\u0026#34;price\u0026#34;], cg.get(\u0026#34;mcap\u0026#34;, 0), cg.get(\u0026#34;fdv\u0026#34;, 0)) ok = await send_tg(text, silent=False) if ok: log_push(pid, \u0026#34;at_launch\u0026#34;, text) save_snapshot(pid, cg[\u0026#34;price\u0026#34;], cg.get(\u0026#34;mcap\u0026#34;, 0), cg.get(\u0026#34;fdv\u0026#34;, 0)) # 上线后30min × 4 elif 0 \u0026lt; -delta_sec \u0026lt;= 2.5 * 3600: minutes_after = int(-delta_sec / 60) for idx, target in enumerate([30, 60, 90, 120], 1): if abs(minutes_after - target) \u0026lt;= 5: ptype = f\u0026#34;post_30m_{idx}\u0026#34; if not has_pushed(pid, ptype): cg = await fetch_coingecko(symbol, project_name=p.get(\u0026#39;name\u0026#39;, \u0026#39;\u0026#39;)) if cg.get(\u0026#34;price\u0026#34;): open_price = p.get(\u0026#34;open_price\u0026#34;) or cg[\u0026#34;price\u0026#34;] change = ((cg[\u0026#34;price\u0026#34;] - open_price) / open_price * 100) if open_price else 0 text = fmt_periodic(p, idx, cg[\u0026#34;price\u0026#34;], cg.get(\u0026#34;mcap\u0026#34;, 0), change) ok = await send_tg(text, silent=p.get(\u0026#34;tier\u0026#34;) in (\u0026#34;B\u0026#34;, \u0026#34;C\u0026#34;) and idx \u0026gt; 1) if ok: log_push(pid, ptype, text) save_snapshot(pid, cg[\u0026#34;price\u0026#34;], cg.get(\u0026#34;mcap\u0026#34;, 0), cg.get(\u0026#34;fdv\u0026#34;, 0)) # 异动 if change \u0026gt;= 100 and not has_pushed(pid, \u0026#34;anomaly_double\u0026#34;): t = fmt_anomaly(p, \u0026#34;double\u0026#34;, cg[\u0026#34;price\u0026#34;], change) if await send_tg(t): log_push(pid, \u0026#34;anomaly_double\u0026#34;, t) elif change \u0026lt;= -50 and not has_pushed(pid, \u0026#34;anomaly_halve\u0026#34;): t = fmt_anomaly(p, \u0026#34;halve\u0026#34;, cg[\u0026#34;price\u0026#34;], change) if await send_tg(t): log_push(pid, \u0026#34;anomaly_halve\u0026#34;, t) break # ============================================================ # 启动 # ============================================================ async def main(): init_db() logger.info(f\u0026#34;📂 数据库: {DB_PATH}\u0026#34;) # 测试TG ok = await send_tg(\u0026#34;🎉 \u0026lt;b\u0026gt;Alpha Monitor v2 启动\u0026lt;/b\u0026gt;\\n\\n📡 币安公告监听中...\\n🔔 有新Alpha会立即推送\u0026#34;) if ok: logger.info(\u0026#34;✅ TG推送正常\u0026#34;) else: logger.warning(\u0026#34;⚠️ TG推送失败，检查配置\u0026#34;) tasks = [ asyncio.create_task(announcement_listener(), name=\u0026#34;announcements\u0026#34;), asyncio.create_task(aggregation_worker(), name=\u0026#34;aggregator\u0026#34;), asyncio.create_task(post_launch_monitor(), name=\u0026#34;monitor\u0026#34;), ] logger.info(\u0026#34;=\u0026#34; * 50) logger.info(\u0026#34;🚀 Alpha Monitor v2 启动完成\u0026#34;) logger.info(f\u0026#34; 📡 公告轮询: {ANNOUNCEMENT_POLL_INTERVAL}s\u0026#34;) logger.info(f\u0026#34; 🧠 LLM: {\u0026#39;Sonnet\u0026#39; if ANTHROPIC_API_KEY else \u0026#39;降级(规则)\u0026#39;}\u0026#34;) logger.info(f\u0026#34; 🔔 TG: {\u0026#39;✅\u0026#39; if TG_BOT_TOKEN else \u0026#39;❌\u0026#39;}\u0026#34;) logger.info(\u0026#34;=\u0026#34; * 50) try: await asyncio.gather(*tasks) except KeyboardInterrupt: for t in tasks: t.cancel() if __name__ == \u0026#34;__main__\u0026#34;: try: asyncio.run(main()) except KeyboardInterrupt: pass AI Giao Dịch Tự Động Giao dịch tự động Futures + Alpha v1 Ngày: 2026.04.29　Thẻ: Python · Binance Futures · Autonomous · Telegram\nAI quét thị trường → phân tích → giao dịch ảo → theo dõi → review — hoàn toàn tự động\n⚠️ Cảnh báo rủi ro: Code này chỉ được kiểm tra trên giao dịch ảo/paper. Không có kinh nghiệm giao dịch thực và KHÔNG nên dùng làm cơ sở cho giao dịch thực. Chưa được xác thực với tiền thực — sử dụng tự chịu rủi ro.\nMột AI tự động quét toàn bộ thị trường Binance Futures mỗi 30 giây, phát hiện bất thường và mở vị thế ảo. 4 chiến lược phát hiện tín hiệu (funding rate âm cực đoan → squeeze long, funding dương cực đoan → short, short sau khi pump, bật sau khi sập). Trước khi mở vị thế, chạy kiểm tra môi trường đa chiều (xu hướng BTC + Fear\u0026amp;Greed + OI attention + khối lượng) — cần ≥3/7 điểm. Tự động giám sát cắt lỗ/chốt lời mỗi 30 giây.\nKết quả hiện tại (công bố trung thực): 4 lệnh đã đóng, win rate 75%, +13.94U. Nhưng lợi nhuận tập trung ở một lệnh (IR short +45%), phần còn lại về cơ bản hòa vốn. Đa dạng hóa vị thế chưa đủ — có xu hướng chồng các lệnh cùng hướng cùng logic. Vẫn là chấm điểm dựa trên rule, chưa phải tư duy độc lập thực sự.\nLộ trình huấn luyện: scan → trade → close → review → tìm lỗi → cải thiện → lặp. Mục tiêu: tiến hóa từ trình thực thi rule thành trader có tư duy độc lập.\nMã nguồn đầy đủ #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; 市场扫描器 - 每分钟运行 纯Python零AI成本，发现异常信号自动开仓 \u0026#34;\u0026#34;\u0026#34; import json import os import sys import time import requests from datetime import datetime, timezone, timedelta SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_FILE = os.path.join(SCRIPT_DIR, \u0026#34;trades.json\u0026#34;) SCANNER_STATE = os.path.join(SCRIPT_DIR, \u0026#34;scanner_state.json\u0026#34;) SCANNER_LOG = os.path.join(SCRIPT_DIR, \u0026#34;scanner.log\u0026#34;) INITIAL_BALANCE = 100.0 TZ_UTC8 = timezone(timedelta(hours=8)) # === 配置 === MAX_OPEN_POSITIONS = 3 # 最多同时持仓 POSITION_PCT = 30 # 每笔仓位占比% LEVERAGE = 3 # 杠杆 COOLDOWN_HOURS = 4 # 同一币种冷却时间 MIN_VOLUME_M = 10 # 最小24h成交额(百万U) # === TG推送 === def load_tg_config(): \u0026#34;\u0026#34;\u0026#34;Load TG config from environment variables or .env file\u0026#34;\u0026#34;\u0026#34; env = {} # Try .env in script directory, then current directory for env_path in [ os.path.join(SCRIPT_DIR, \u0026#34;.env\u0026#34;), os.path.join(os.getcwd(), \u0026#34;.env\u0026#34;), ]: if os.path.exists(env_path): with open(env_path) as f: for line in f: line = line.strip() if \u0026#39;=\u0026#39; in line and not line.startswith(\u0026#39;#\u0026#39;): k, v = line.split(\u0026#39;=\u0026#39;, 1) env[k] = v.strip().strip(\u0026#39;\u0026#34;\u0026#39;).strip(\u0026#34;\u0026#39;\u0026#34;) break # OS environment variables override file for key in [\u0026#39;TG_BOT_TOKEN\u0026#39;, \u0026#39;TELEGRAM_BOT_TOKEN\u0026#39;, \u0026#39;TG_CHAT_ID\u0026#39;]: val = os.environ.get(key) if val: env[key] = val return env def send_tg(text): try: env = load_tg_config() token = env.get(\u0026#39;TG_BOT_TOKEN\u0026#39;, env.get(\u0026#39;TELEGRAM_BOT_TOKEN\u0026#39;, \u0026#39;\u0026#39;)) if not token: return chat_id = env.get(\u0026#39;TG_CHAT_ID\u0026#39;, \u0026#39;\u0026#39;) if not chat_id: return url = f\u0026#34;https://api.telegram.org/bot{token}/sendMessage\u0026#34; requests.post(url, json={ \u0026#34;chat_id\u0026#34;: chat_id, \u0026#34;text\u0026#34;: text, \u0026#34;parse_mode\u0026#34;: \u0026#34;Markdown\u0026#34; }, timeout=10) except: pass # === 数据加载 === def load_trades(): if os.path.exists(DATA_FILE): with open(DATA_FILE, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: return json.load(f) return {\u0026#34;initial_balance\u0026#34;: INITIAL_BALANCE, \u0026#34;trades\u0026#34;: []} def save_trades(data): with open(DATA_FILE, \u0026#34;w\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as f: json.dump(data, f, ensure_ascii=False, indent=2) def load_state(): if os.path.exists(SCANNER_STATE): with open(SCANNER_STATE, \u0026#34;r\u0026#34;) as f: return json.load(f) return {\u0026#34;last_opens\u0026#34;: {}, \u0026#34;signals_seen\u0026#34;: {}} def save_state(state): with open(SCANNER_STATE, \u0026#34;w\u0026#34;) as f: json.dump(state, f, ensure_ascii=False, indent=2) def get_balance(data): balance = data.get(\u0026#34;initial_balance\u0026#34;, INITIAL_BALANCE) for t in data[\u0026#34;trades\u0026#34;]: if t[\u0026#34;status\u0026#34;] == \u0026#34;closed\u0026#34; and t[\u0026#34;pnl_usd\u0026#34;] is not None: balance += t[\u0026#34;pnl_usd\u0026#34;] return balance def next_id(data): if not data[\u0026#34;trades\u0026#34;]: return \u0026#34;001\u0026#34; max_id = max(int(t[\u0026#34;id\u0026#34;]) for t in data[\u0026#34;trades\u0026#34;]) return f\u0026#34;{max_id + 1:03d}\u0026#34; def now_str(): return datetime.now(TZ_UTC8).strftime(\u0026#34;%Y-%m-%dT%H:%M:%S\u0026#34;) def log(msg): ts = datetime.now(TZ_UTC8).strftime(\u0026#34;%m-%d %H:%M:%S\u0026#34;) line = f\u0026#34;[{ts}] {msg}\u0026#34; print(line) with open(SCANNER_LOG, \u0026#34;a\u0026#34;) as f: f.write(line + \u0026#34;\\n\u0026#34;) # === 币安API === def get_all_tickers(): url = \u0026#34;https://fapi.binance.com/fapi/v1/ticker/24hr\u0026#34; resp = requests.get(url, timeout=10) return resp.json() def get_funding_rates(): \u0026#34;\u0026#34;\u0026#34;获取所有币种最新费率\u0026#34;\u0026#34;\u0026#34; url = \u0026#34;https://fapi.binance.com/fapi/v1/premiumIndex\u0026#34; resp = requests.get(url, timeout=10) return {item[\u0026#39;symbol\u0026#39;]: float(item[\u0026#39;lastFundingRate\u0026#39;]) * 100 for item in resp.json()} def get_funding_history(symbol, limit=8): url = f\u0026#34;https://fapi.binance.com/fapi/v1/fundingRate?symbol={symbol}\u0026amp;limit={limit}\u0026#34; resp = requests.get(url, timeout=10) return [float(item[\u0026#39;fundingRate\u0026#39;]) * 100 for item in resp.json()] def get_open_interest(symbol): url = f\u0026#34;https://fapi.binance.com/fapi/v1/openInterest?symbol={symbol}\u0026#34; resp = requests.get(url, timeout=10) data = resp.json() return float(data[\u0026#39;openInterest\u0026#39;]) def get_klines(symbol, interval=\u0026#34;4h\u0026#34;, limit=6): url = f\u0026#34;https://fapi.binance.com/fapi/v1/klines?symbol={symbol}\u0026amp;interval={interval}\u0026amp;limit={limit}\u0026#34; resp = requests.get(url, timeout=10) return resp.json() # === 信号检测 === def detect_extreme_negative_funding(symbol, funding_rate, funding_rates_map): \u0026#34;\u0026#34;\u0026#34; 策略1: 费率极端深负 → 做多(逼空) 条件: 当前费率\u0026lt;-0.08% 且 连续多期为负 \u0026#34;\u0026#34;\u0026#34; if funding_rate \u0026gt;= -0.08: return None try: history = get_funding_history(symbol, 8) neg_count = sum(1 for r in history if r \u0026lt; -0.03) if neg_count \u0026lt; 4: return None avg_rate = sum(history) / len(history) # 费率越极端，信号越强 strength = \u0026#34;S\u0026#34; if avg_rate \u0026lt; -0.15 else \u0026#34;A\u0026#34; if avg_rate \u0026lt; -0.10 else \u0026#34;B\u0026#34; return { \u0026#34;type\u0026#34;: \u0026#34;extreme_neg_funding\u0026#34;, \u0026#34;direction\u0026#34;: \u0026#34;long\u0026#34;, \u0026#34;strength\u0026#34;: strength, \u0026#34;reason\u0026#34;: f\u0026#34;费率极端深负 avg:{avg_rate:.4f}% 连续{neg_count}/8期为负 逼空概率高\u0026#34;, \u0026#34;sl_pct\u0026#34;: 0.08, # 止损8% \u0026#34;tp_pct\u0026#34;: 0.12, # 止盈12% } except: return None def detect_extreme_positive_funding(symbol, funding_rate, funding_rates_map): \u0026#34;\u0026#34;\u0026#34; 策略2: 费率极端正 → 做空(多头拥挤) 条件: 当前费率\u0026gt;0.10% 且 连续多期高正 \u0026#34;\u0026#34;\u0026#34; if funding_rate \u0026lt;= 0.10: return None try: history = get_funding_history(symbol, 8) pos_count = sum(1 for r in history if r \u0026gt; 0.05) if pos_count \u0026lt; 4: return None avg_rate = sum(history) / len(history) strength = \u0026#34;S\u0026#34; if avg_rate \u0026gt; 0.20 else \u0026#34;A\u0026#34; if avg_rate \u0026gt; 0.12 else \u0026#34;B\u0026#34; return { \u0026#34;type\u0026#34;: \u0026#34;extreme_pos_funding\u0026#34;, \u0026#34;direction\u0026#34;: \u0026#34;short\u0026#34;, \u0026#34;strength\u0026#34;: strength, \u0026#34;reason\u0026#34;: f\u0026#34;费率极端正 avg:{avg_rate:.4f}% 连续{pos_count}/8期高正 多头过度拥挤\u0026#34;, \u0026#34;sl_pct\u0026#34;: 0.10, \u0026#34;tp_pct\u0026#34;: 0.15, } except: return None def detect_crash_bounce(ticker): \u0026#34;\u0026#34;\u0026#34; 策略3: 暴跌后反弹(超跌反弹) 条件: 24h跌\u0026gt;25% 但最近4h企稳/反弹 \u0026#34;\u0026#34;\u0026#34; change_pct = float(ticker[\u0026#39;priceChangePercent\u0026#39;]) if change_pct \u0026gt;= -25: return None symbol = ticker[\u0026#39;symbol\u0026#39;] try: klines = get_klines(symbol, \u0026#34;1h\u0026#34;, 6) # 最近2根K线 recent_closes = [float(k[4]) for k in klines[-3:]] # 企稳: 最近K线收盘 \u0026gt;= 前一根 if len(recent_closes) \u0026gt;= 2 and recent_closes[-1] \u0026gt;= recent_closes[-2]: return { \u0026#34;type\u0026#34;: \u0026#34;crash_bounce\u0026#34;, \u0026#34;direction\u0026#34;: \u0026#34;long\u0026#34;, \u0026#34;strength\u0026#34;: \u0026#34;B\u0026#34;, # 风险较高给B级 \u0026#34;reason\u0026#34;: f\u0026#34;24h暴跌{change_pct:.1f}%后企稳 超跌反弹\u0026#34;, \u0026#34;sl_pct\u0026#34;: 0.10, \u0026#34;tp_pct\u0026#34;: 0.15, } except: pass return None def detect_pump_short(ticker): \u0026#34;\u0026#34;\u0026#34; 策略4: 暴涨后做空(ATH回落) 条件: 24h涨\u0026gt;40% — 根据生命周期模型，暴涨后回调概率\u0026gt;85% 需要确认已经开始回落(不在最高点做空) \u0026#34;\u0026#34;\u0026#34; change_pct = float(ticker[\u0026#39;priceChangePercent\u0026#39;]) if change_pct \u0026lt;= 40: return None symbol = ticker[\u0026#39;symbol\u0026#39;] try: klines = get_klines(symbol, \u0026#34;1h\u0026#34;, 6) highs = [float(k[2]) for k in klines] closes = [float(k[4]) for k in klines] current = closes[-1] peak = max(highs) # 从最高点回落超过10%才做空 pullback = (peak - current) / peak * 100 if pullback \u0026lt; 10: return None strength = \u0026#34;A\u0026#34; if change_pct \u0026gt; 80 else \u0026#34;B\u0026#34; return { \u0026#34;type\u0026#34;: \u0026#34;pump_short\u0026#34;, \u0026#34;direction\u0026#34;: \u0026#34;short\u0026#34;, \u0026#34;strength\u0026#34;: strength, \u0026#34;reason\u0026#34;: f\u0026#34;24h暴涨{change_pct:.1f}%后回落{pullback:.1f}% 历史回调概率\u0026gt;85%\u0026#34;, \u0026#34;sl_pct\u0026#34;: 0.15, # 暴涨币波动大，止损宽一些 \u0026#34;tp_pct\u0026#34;: 0.20, } except: pass return None # === 综合环境检查 === def check_environment(symbol, signal): \u0026#34;\u0026#34;\u0026#34; 开仓前综合检查：不是单一信号触发就开，要多维度对齐 返回 (pass/fail, analysis_dict, adjusted_strength) \u0026#34;\u0026#34;\u0026#34; analysis = { \u0026#34;btc_env\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;sentiment\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;oi_check\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;volume_check\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;verdict\u0026#34;: \u0026#34;\u0026#34; } score = 0 # 综合得分，\u0026gt;=3才开仓 try: # 1. BTC环境 — 做多需要BTC不在暴跌，做空需要BTC不在暴涨 btc_url = \u0026#34;https://fapi.binance.com/fapi/v1/ticker/24hr?symbol=BTCUSDT\u0026#34; btc = requests.get(btc_url, timeout=5).json() btc_chg = float(btc[\u0026#39;priceChangePercent\u0026#39;]) if signal[\u0026#34;direction\u0026#34;] == \u0026#34;long\u0026#34;: if btc_chg \u0026gt; -2: score += 1 analysis[\u0026#34;btc_env\u0026#34;] = f\u0026#34;BTC {btc_chg:+.1f}% 环境正常 +1\u0026#34; elif btc_chg \u0026lt; -5: score -= 1 analysis[\u0026#34;btc_env\u0026#34;] = f\u0026#34;BTC {btc_chg:+.1f}% 暴跌中做多危险 -1\u0026#34; else: analysis[\u0026#34;btc_env\u0026#34;] = f\u0026#34;BTC {btc_chg:+.1f}% 偏弱 0\u0026#34; else: # short if btc_chg \u0026lt; 2: score += 1 analysis[\u0026#34;btc_env\u0026#34;] = f\u0026#34;BTC {btc_chg:+.1f}% 环境正常 +1\u0026#34; elif btc_chg \u0026gt; 5: score -= 1 analysis[\u0026#34;btc_env\u0026#34;] = f\u0026#34;BTC {btc_chg:+.1f}% 暴涨中做空危险 -1\u0026#34; else: analysis[\u0026#34;btc_env\u0026#34;] = f\u0026#34;BTC {btc_chg:+.1f}% 偏强 0\u0026#34; # 2. 市场情绪(Fear \u0026amp; Greed) try: fng = requests.get(\u0026#34;https://api.alternative.me/fng/\u0026#34;, timeout=5).json() fng_val = int(fng[\u0026#39;data\u0026#39;][0][\u0026#39;value\u0026#39;]) if signal[\u0026#34;direction\u0026#34;] == \u0026#34;long\u0026#34;: if fng_val \u0026lt;= 25: score += 1 analysis[\u0026#34;sentiment\u0026#34;] = f\u0026#34;FGI={fng_val}极度恐惧 逆向做多 +1\u0026#34; elif fng_val \u0026gt;= 75: score -= 1 analysis[\u0026#34;sentiment\u0026#34;] = f\u0026#34;FGI={fng_val}极度贪婪 做多风险 -1\u0026#34; else: analysis[\u0026#34;sentiment\u0026#34;] = f\u0026#34;FGI={fng_val}中性 0\u0026#34; else: if fng_val \u0026gt;= 75: score += 1 analysis[\u0026#34;sentiment\u0026#34;] = f\u0026#34;FGI={fng_val}极度贪婪 逆向做空 +1\u0026#34; elif fng_val \u0026lt;= 25: score -= 1 analysis[\u0026#34;sentiment\u0026#34;] = f\u0026#34;FGI={fng_val}极度恐惧 做空风险 -1\u0026#34; else: analysis[\u0026#34;sentiment\u0026#34;] = f\u0026#34;FGI={fng_val}中性 0\u0026#34; except: analysis[\u0026#34;sentiment\u0026#34;] = \u0026#34;FGI获取失败 0\u0026#34; # 3. OI变化 — 看该币OI是否支持方向 try: oi = get_open_interest(symbol) ticker = requests.get(f\u0026#34;https://fapi.binance.com/fapi/v1/ticker/24hr?symbol={symbol}\u0026#34;, timeout=5).json() price = float(ticker[\u0026#39;lastPrice\u0026#39;]) oi_usd = oi * price if oi_usd \u0026gt; 5_000_000: # OI \u0026gt; 5M说明有关注度 score += 1 analysis[\u0026#34;oi_check\u0026#34;] = f\u0026#34;OI={oi_usd/1e6:.1f}M 有关注度 +1\u0026#34; else: analysis[\u0026#34;oi_check\u0026#34;] = f\u0026#34;OI={oi_usd/1e6:.1f}M 关注度低 0\u0026#34; except: analysis[\u0026#34;oi_check\u0026#34;] = \u0026#34;OI获取失败 0\u0026#34; # 4. 成交量 — 量能是否活跃 try: vol = float(ticker.get(\u0026#39;quoteVolume\u0026#39;, 0)) if vol \u0026gt; 50_000_000: score += 1 analysis[\u0026#34;volume_check\u0026#34;] = f\u0026#34;24h量={vol/1e6:.0f}M 活跃 +1\u0026#34; elif vol \u0026gt; 20_000_000: analysis[\u0026#34;volume_check\u0026#34;] = f\u0026#34;24h量={vol/1e6:.0f}M 一般 0\u0026#34; else: score -= 1 analysis[\u0026#34;volume_check\u0026#34;] = f\u0026#34;24h量={vol/1e6:.0f}M 冷清 -1\u0026#34; except: analysis[\u0026#34;volume_check\u0026#34;] = \u0026#34;量能获取失败 0\u0026#34; # 5. 信号本身的强度加分 if signal[\u0026#34;strength\u0026#34;] == \u0026#34;S\u0026#34;: score += 2 elif signal[\u0026#34;strength\u0026#34;] == \u0026#34;A\u0026#34;: score += 1 # 综合判定: \u0026gt;=3通过 analysis[\u0026#34;verdict\u0026#34;] = f\u0026#34;综合得分:{score}/7\u0026#34; if score \u0026gt;= 3: return True, analysis, signal[\u0026#34;strength\u0026#34;] else: return False, analysis, signal[\u0026#34;strength\u0026#34;] except Exception as e: analysis[\u0026#34;verdict\u0026#34;] = f\u0026#34;检查异常:{e} 保守不开\u0026#34; return False, analysis, signal[\u0026#34;strength\u0026#34;] # === 开仓执行 === def execute_open(data, state, symbol, price, signal): \u0026#34;\u0026#34;\u0026#34;执行虚拟开仓 — 先过综合环境检查\u0026#34;\u0026#34;\u0026#34; # 综合环境检查 passed, env_analysis, strength = check_environment(symbol, signal) env_summary = \u0026#34; | \u0026#34;.join(v for v in env_analysis.values() if v) if not passed: log(f\u0026#34;综合检查未通过 {symbol}: {env_summary}\u0026#34;) return log(f\u0026#34;综合检查通过 {symbol}: {env_summary}\u0026#34;) balance = get_balance(data) position_usd = balance * POSITION_PCT / 100 if signal[\u0026#34;direction\u0026#34;] == \u0026#34;long\u0026#34;: sl = round(price * (1 - signal[\u0026#34;sl_pct\u0026#34;]), 6) tp = round(price * (1 + signal[\u0026#34;tp_pct\u0026#34;]), 6) else: sl = round(price * (1 + signal[\u0026#34;sl_pct\u0026#34;]), 6) tp = round(price * (1 - signal[\u0026#34;tp_pct\u0026#34;]), 6) trade = { \u0026#34;id\u0026#34;: next_id(data), \u0026#34;symbol\u0026#34;: symbol, \u0026#34;direction\u0026#34;: signal[\u0026#34;direction\u0026#34;], \u0026#34;leverage\u0026#34;: LEVERAGE, \u0026#34;position_pct\u0026#34;: POSITION_PCT, \u0026#34;position_usd\u0026#34;: round(position_usd, 4), \u0026#34;notional_usd\u0026#34;: round(position_usd * LEVERAGE, 4), \u0026#34;entry_price\u0026#34;: price, \u0026#34;stop_loss\u0026#34;: sl, \u0026#34;take_profit\u0026#34;: tp, \u0026#34;entry_time\u0026#34;: now_str(), \u0026#34;exit_price\u0026#34;: None, \u0026#34;exit_time\u0026#34;: None, \u0026#34;exit_reason\u0026#34;: None, \u0026#34;pnl_pct\u0026#34;: None, \u0026#34;pnl_usd\u0026#34;: None, \u0026#34;status\u0026#34;: \u0026#34;open\u0026#34;, \u0026#34;pre_analysis\u0026#34;: { \u0026#34;btc_env\u0026#34;: env_analysis.get(\u0026#34;btc_env\u0026#34;, \u0026#34;\u0026#34;), \u0026#34;sentiment\u0026#34;: env_analysis.get(\u0026#34;sentiment\u0026#34;, \u0026#34;\u0026#34;), \u0026#34;oi\u0026#34;: env_analysis.get(\u0026#34;oi_check\u0026#34;, \u0026#34;\u0026#34;), \u0026#34;volume\u0026#34;: env_analysis.get(\u0026#34;volume_check\u0026#34;, \u0026#34;\u0026#34;), \u0026#34;key_reason\u0026#34;: f\u0026#34;[{signal[\u0026#39;strength\u0026#39;]}级] {signal[\u0026#39;reason\u0026#39;]}\u0026#34;, \u0026#34;risk\u0026#34;: f\u0026#34;综合得分:{env_analysis.get(\u0026#39;verdict\u0026#39;,\u0026#39;\u0026#39;)} 策略:{signal[\u0026#39;type\u0026#39;]}\u0026#34; }, \u0026#34;post_review\u0026#34;: None } data[\u0026#34;trades\u0026#34;].append(trade) save_trades(data) # 记录冷却 state[\u0026#34;last_opens\u0026#34;][symbol] = now_str() save_state(state) direction_cn = \u0026#34;做多\u0026#34; if signal[\u0026#34;direction\u0026#34;] == \u0026#34;long\u0026#34; else \u0026#34;做空\u0026#34; msg = f\u0026#34;\u0026#34;\u0026#34;``` [扫描开仓] #{trade[\u0026#39;id\u0026#39;]} 币种: {symbol} 方向: {direction_cn} {LEVERAGE}x 入场: {price} 止损: {sl} 止盈: {tp} 仓位: {position_usd:.2f}U 信号: [{signal[\u0026#39;strength\u0026#39;]}] {signal[\u0026#39;reason\u0026#39;]} 时间: {trade[\u0026#39;entry_time\u0026#39;]} ```\u0026#34;\u0026#34;\u0026#34; log(f\u0026#34;开仓 #{trade[\u0026#39;id\u0026#39;]} {symbol} {direction_cn} @ {price} | {signal[\u0026#39;reason\u0026#39;]}\u0026#34;) send_tg(msg) print(msg) # === 换仓逻辑 === def swap_weakest(data, state, open_positions, new_signal, tickers): \u0026#34;\u0026#34;\u0026#34;满仓时遇到S级信号，平掉浮亏最大的持仓，开新仓\u0026#34;\u0026#34;\u0026#34; ticker_map = {t[\u0026#39;symbol\u0026#39;]: float(t[\u0026#39;lastPrice\u0026#39;]) for t in tickers} # 计算每个持仓的浮盈% worst_trade = None worst_pnl = float(\u0026#39;inf\u0026#39;) for t in open_positions: price = ticker_map.get(t[\u0026#34;symbol\u0026#34;]) if price is None: continue if t[\u0026#34;direction\u0026#34;] == \u0026#34;long\u0026#34;: pnl_pct = (price - t[\u0026#34;entry_price\u0026#34;]) / t[\u0026#34;entry_price\u0026#34;] * 100 else: pnl_pct = (t[\u0026#34;entry_price\u0026#34;] - price) / t[\u0026#34;entry_price\u0026#34;] * 100 if pnl_pct \u0026lt; worst_pnl: worst_pnl = pnl_pct worst_trade = t worst_price = price if worst_trade is None: return # 只换掉亏损的仓位，盈利的不动 if worst_pnl \u0026gt; 0: log(f\u0026#34;满仓但所有持仓盈利，不换仓 | 新信号: {new_signal[\u0026#39;symbol\u0026#39;]}\u0026#34;) return # 平掉最弱的 if worst_trade[\u0026#34;direction\u0026#34;] == \u0026#34;long\u0026#34;: pnl_pct_lev = (worst_price - worst_trade[\u0026#34;entry_price\u0026#34;]) / worst_trade[\u0026#34;entry_price\u0026#34;] * 100 * worst_trade[\u0026#34;leverage\u0026#34;] else: pnl_pct_lev = (worst_trade[\u0026#34;entry_price\u0026#34;] - worst_price) / worst_trade[\u0026#34;entry_price\u0026#34;] * 100 * worst_trade[\u0026#34;leverage\u0026#34;] pos_usd = worst_trade.get(\u0026#34;position_usd\u0026#34;, worst_trade.get(\u0026#34;position_pct\u0026#34;, 30)) pnl_usd = round(pnl_pct_lev / 100 * pos_usd, 4) worst_trade[\u0026#34;exit_price\u0026#34;] = worst_price worst_trade[\u0026#34;exit_time\u0026#34;] = now_str() worst_trade[\u0026#34;exit_reason\u0026#34;] = f\u0026#34;换仓→{new_signal[\u0026#39;symbol\u0026#39;]}\u0026#34; worst_trade[\u0026#34;pnl_pct\u0026#34;] = round(pnl_pct_lev, 2) worst_trade[\u0026#34;pnl_usd\u0026#34;] = pnl_usd worst_trade[\u0026#34;status\u0026#34;] = \u0026#34;closed\u0026#34; save_trades(data) direction_cn = \u0026#34;多\u0026#34; if worst_trade[\u0026#34;direction\u0026#34;] == \u0026#34;long\u0026#34; else \u0026#34;空\u0026#34; msg = f\u0026#34;\u0026#34;\u0026#34;``` [换仓平仓] #{worst_trade[\u0026#39;id\u0026#39;]} 平掉: {worst_trade[\u0026#39;symbol\u0026#39;]} {direction_cn} 入场: {worst_trade[\u0026#39;entry_price\u0026#39;]} 出场: {worst_price} 盈亏: {pnl_pct_lev:+.2f}% ({pnl_usd:+.2f}U) 原因: S级信号{new_signal[\u0026#39;symbol\u0026#39;]}更强 ```\u0026#34;\u0026#34;\u0026#34; log(f\u0026#34;换仓平 #{worst_trade[\u0026#39;id\u0026#39;]} {worst_trade[\u0026#39;symbol\u0026#39;]} {pnl_usd:+.2f}U → 开 {new_signal[\u0026#39;symbol\u0026#39;]}\u0026#34;) send_tg(msg) # 开新仓 execute_open(data, state, new_signal[\u0026#34;symbol\u0026#34;], new_signal[\u0026#34;price\u0026#34;], new_signal) # === 主扫描逻辑 === def scan(): data = load_trades() state = load_state() now = datetime.now(TZ_UTC8) # 检查持仓数 open_positions = [t for t in data[\u0026#34;trades\u0026#34;] if t[\u0026#34;status\u0026#34;] == \u0026#34;open\u0026#34;] open_symbols = set(t[\u0026#34;symbol\u0026#34;] for t in open_positions) if len(open_positions) \u0026gt;= MAX_OPEN_POSITIONS: return # 获取市场数据 try: tickers = get_all_tickers() funding_rates = get_funding_rates() except Exception as e: log(f\u0026#34;API错误: {e}\u0026#34;) return # 过滤USDT合约 + 最小成交量 exclude = {\u0026#34;BTCUSDT\u0026#34;, \u0026#34;ETHUSDT\u0026#34;, \u0026#34;USDCUSDT\u0026#34;, \u0026#34;FDUSDUSDT\u0026#34;, \u0026#34;BTCDOMUSDT\u0026#34;, \u0026#34;BTCSTUSDT\u0026#34;} candidates = [t for t in tickers if t[\u0026#39;symbol\u0026#39;].endswith(\u0026#39;USDT\u0026#39;) and t[\u0026#39;symbol\u0026#39;] not in exclude and float(t[\u0026#39;quoteVolume\u0026#39;]) \u0026gt; MIN_VOLUME_M * 1e6] signals = [] for ticker in candidates: symbol = ticker[\u0026#39;symbol\u0026#39;] # 跳过已持仓的币 if symbol in open_symbols: continue # 冷却检查 last_open = state.get(\u0026#34;last_opens\u0026#34;, {}).get(symbol) if last_open: try: last_dt = datetime.fromisoformat(last_open) if last_dt.tzinfo is None: last_dt = last_dt.replace(tzinfo=TZ_UTC8) if (now - last_dt).total_seconds() \u0026lt; COOLDOWN_HOURS * 3600: continue except: pass fr = funding_rates.get(symbol, 0) # 运行所有策略检测 for detect_fn in [ lambda s, f, m: detect_extreme_negative_funding(s, f, m), lambda s, f, m: detect_extreme_positive_funding(s, f, m), lambda s, f, m: detect_crash_bounce(ticker), lambda s, f, m: detect_pump_short(ticker), ]: try: signal = detect_fn(symbol, fr, funding_rates) if signal: signal[\u0026#34;symbol\u0026#34;] = symbol signal[\u0026#34;price\u0026#34;] = float(ticker[\u0026#39;lastPrice\u0026#39;]) signal[\u0026#34;volume_m\u0026#34;] = float(ticker[\u0026#39;quoteVolume\u0026#39;]) / 1e6 signals.append(signal) except: continue if not signals: return # 按信号强度排序 S\u0026gt;A\u0026gt;B strength_order = {\u0026#34;S\u0026#34;: 0, \u0026#34;A\u0026#34;: 1, \u0026#34;B\u0026#34;: 2} signals.sort(key=lambda x: strength_order.get(x[\u0026#34;strength\u0026#34;], 3)) # 只取最强的信号开仓(一次最多开1笔) best = signals[0] # B级信号跳过，只开S和A级 if best[\u0026#34;strength\u0026#34;] == \u0026#34;B\u0026#34;: log(f\u0026#34;B级信号跳过: {best[\u0026#39;symbol\u0026#39;]} {best[\u0026#39;reason\u0026#39;]}\u0026#34;) return slots = MAX_OPEN_POSITIONS - len(open_positions) if slots \u0026gt; 0: execute_open(data, state, best[\u0026#34;symbol\u0026#34;], best[\u0026#34;price\u0026#34;], best) elif best[\u0026#34;strength\u0026#34;] == \u0026#34;S\u0026#34;: # 满仓但遇到S级信号 → 换掉最弱的持仓 swap_weakest(data, state, open_positions, best, tickers) if __name__ == \u0026#34;__main__\u0026#34;: scan() Công Cụ Tiện Ích VoiceKey — Xác thực giọng nói Ngày: 2026.04.27　Thẻ: Python · Security · Telegram · Speaker Verification\nBảo vệ AI agent của bạn bằng xác thực giọng nói\nXác minh người nói cho bảo mật bot Telegram. Sử dụng resemblyzer (mô hình GE2E) để trích xuất embedding giọng nói và so sánh độ tương đồng cosine. Đăng nhập bằng giọng nói thay cho mật khẩu — chỉ những người nói đã biết mới có thể đăng nhập. Phụ thuộc nhẹ, Python thuần.\nMã nguồn đầy đủ #!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; VoiceKey — 声纹密钥 (Voiceprint Authentication) Speaker verification for Telegram bot security. Uses resemblyzer (GE2E model) for speaker embedding extraction and cosine similarity for verification. Zero AI cost — runs entirely on local CPU. Usage: # Register voiceprint from audio files python voicekey.py register --audio voice1.ogg voice2.ogg --owner \u0026#34;YourName\u0026#34; # Verify a voice against stored voiceprint python voicekey.py verify --audio test.ogg # As a Python module from voicekey import VoiceKey vk = VoiceKey() vk.register([\u0026#34;voice1.ogg\u0026#34;, \u0026#34;voice2.ogg\u0026#34;], owner=\u0026#34;YourName\u0026#34;) is_owner, score = vk.verify(\u0026#34;test.ogg\u0026#34;) \u0026#34;\u0026#34;\u0026#34; import os import json import tempfile import argparse import numpy as np from pathlib import Path from datetime import datetime # Lazy imports to speed up module load when not needed _encoder = None def _get_encoder(): \u0026#34;\u0026#34;\u0026#34;Lazy-load the voice encoder model (first call takes ~1s).\u0026#34;\u0026#34;\u0026#34; global _encoder if _encoder is None: from resemblyzer import VoiceEncoder _encoder = VoiceEncoder() return _encoder def _audio_to_wav(audio_path: str) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;Convert any audio format to 16kHz mono WAV for processing.\u0026#34;\u0026#34;\u0026#34; from pydub import AudioSegment ext = Path(audio_path).suffix.lower() if ext in (\u0026#39;.ogg\u0026#39;, \u0026#39;.oga\u0026#39;): audio = AudioSegment.from_ogg(audio_path) elif ext == \u0026#39;.mp3\u0026#39;: audio = AudioSegment.from_mp3(audio_path) elif ext == \u0026#39;.wav\u0026#39;: return audio_path # already wav elif ext in (\u0026#39;.m4a\u0026#39;, \u0026#39;.aac\u0026#39;): audio = AudioSegment.from_file(audio_path, format=ext.lstrip(\u0026#39;.\u0026#39;)) else: audio = AudioSegment.from_file(audio_path) # Convert to 16kHz mono audio = audio.set_frame_rate(16000).set_channels(1) tmp = tempfile.NamedTemporaryFile(suffix=\u0026#39;.wav\u0026#39;, delete=False) audio.export(tmp.name, format=\u0026#39;wav\u0026#39;) return tmp.name def _extract_embedding(audio_path: str) -\u0026gt; np.ndarray: \u0026#34;\u0026#34;\u0026#34;Extract voice embedding from an audio file.\u0026#34;\u0026#34;\u0026#34; from resemblyzer import preprocess_wav encoder = _get_encoder() wav_path = _audio_to_wav(audio_path) wav = preprocess_wav(wav_path) # Cleanup temp file if wav_path != audio_path: os.unlink(wav_path) if len(wav) \u0026lt; 1600: # Less than 0.1s raise ValueError(f\u0026#34;Audio too short: {len(wav)/16000:.1f}s (need \u0026gt;0.1s)\u0026#34;) return encoder.embed_utterance(wav) class VoiceKey: \u0026#34;\u0026#34;\u0026#34; Speaker verification using voice embeddings. Attributes: data_dir: Directory to store voiceprint data threshold: Cosine similarity threshold for verification (default: 0.75) voiceprint: The registered owner\u0026#39;s voice embedding (256-dim vector) \u0026#34;\u0026#34;\u0026#34; def __init__(self, data_dir: str = None, threshold: float = 0.75): if data_dir is None: data_dir = os.path.expanduser(\u0026#34;~/.hermes/voiceprint\u0026#34;) self.data_dir = Path(data_dir) self.data_dir.mkdir(parents=True, exist_ok=True) self.threshold = threshold self.voiceprint = None self.metadata = {} self._load() def _load(self): \u0026#34;\u0026#34;\u0026#34;Load existing voiceprint if available.\u0026#34;\u0026#34;\u0026#34; vp_path = self.data_dir / \u0026#34;voiceprint.npy\u0026#34; meta_path = self.data_dir / \u0026#34;voiceprint_meta.json\u0026#34; if vp_path.exists(): self.voiceprint = np.load(str(vp_path)) if meta_path.exists(): with open(meta_path) as f: self.metadata = json.load(f) self.threshold = self.metadata.get(\u0026#34;threshold\u0026#34;, self.threshold) @property def is_registered(self) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34;Check if a voiceprint is registered.\u0026#34;\u0026#34;\u0026#34; return self.voiceprint is not None def register(self, audio_paths: list, owner: str = \u0026#34;owner\u0026#34;) -\u0026gt; dict: \u0026#34;\u0026#34;\u0026#34; Register a voiceprint from multiple audio samples. Args: audio_paths: List of audio file paths (ogg, mp3, wav, etc.) owner: Name of the voiceprint owner Returns: dict with registration results \u0026#34;\u0026#34;\u0026#34; embeddings = [] results = [] for path in audio_paths: try: embed = _extract_embedding(path) embeddings.append(embed) results.append({\u0026#34;file\u0026#34;: os.path.basename(path), \u0026#34;status\u0026#34;: \u0026#34;ok\u0026#34;}) except Exception as e: results.append({\u0026#34;file\u0026#34;: os.path.basename(path), \u0026#34;status\u0026#34;: \u0026#34;error\u0026#34;, \u0026#34;error\u0026#34;: str(e)}) if not embeddings: raise ValueError(\u0026#34;No valid audio samples provided\u0026#34;) # Average embeddings and normalize voiceprint = np.mean(embeddings, axis=0) voiceprint = voiceprint / np.linalg.norm(voiceprint) # Save np.save(str(self.data_dir / \u0026#34;voiceprint.npy\u0026#34;), voiceprint) self.metadata = { \u0026#34;owner\u0026#34;: owner, \u0026#34;samples_used\u0026#34;: len(embeddings), \u0026#34;embedding_dim\u0026#34;: int(voiceprint.shape[0]), \u0026#34;threshold\u0026#34;: self.threshold, \u0026#34;created\u0026#34;: datetime.now().isoformat(), } with open(self.data_dir / \u0026#34;voiceprint_meta.json\u0026#34;, \u0026#34;w\u0026#34;) as f: json.dump(self.metadata, f, indent=2) self.voiceprint = voiceprint # Self-test similarities = [] for embed in embeddings: sim = float(np.dot(voiceprint, embed / np.linalg.norm(embed))) similarities.append(sim) return { \u0026#34;owner\u0026#34;: owner, \u0026#34;samples\u0026#34;: len(embeddings), \u0026#34;self_test_scores\u0026#34;: similarities, \u0026#34;min_score\u0026#34;: min(similarities), \u0026#34;details\u0026#34;: results, } def verify(self, audio_path: str) -\u0026gt; tuple: \u0026#34;\u0026#34;\u0026#34; Verify if an audio sample matches the registered voiceprint. Args: audio_path: Path to audio file to verify Returns: tuple: (is_verified: bool, similarity_score: float) \u0026#34;\u0026#34;\u0026#34; if not self.is_registered: raise RuntimeError(\u0026#34;No voiceprint registered. Call register() first.\u0026#34;) embed = _extract_embedding(audio_path) embed = embed / np.linalg.norm(embed) similarity = float(np.dot(self.voiceprint, embed)) is_verified = similarity \u0026gt;= self.threshold return is_verified, similarity def get_info(self) -\u0026gt; dict: \u0026#34;\u0026#34;\u0026#34;Get voiceprint registration info.\u0026#34;\u0026#34;\u0026#34; if not self.is_registered: return {\u0026#34;registered\u0026#34;: False} return { \u0026#34;registered\u0026#34;: True, **self.metadata, } def main(): parser = argparse.ArgumentParser( description=\u0026#34;VoiceKey — Speaker verification for security\u0026#34; ) sub = parser.add_subparsers(dest=\u0026#34;command\u0026#34;) # Register reg = sub.add_parser(\u0026#34;register\u0026#34;, help=\u0026#34;Register voiceprint from audio files\u0026#34;) reg.add_argument(\u0026#34;--audio\u0026#34;, nargs=\u0026#34;+\u0026#34;, required=True, help=\u0026#34;Audio files (ogg/mp3/wav)\u0026#34;) reg.add_argument(\u0026#34;--owner\u0026#34;, default=\u0026#34;owner\u0026#34;, help=\u0026#34;Owner name\u0026#34;) reg.add_argument(\u0026#34;--data-dir\u0026#34;, default=None, help=\u0026#34;Data directory\u0026#34;) reg.add_argument(\u0026#34;--threshold\u0026#34;, type=float, default=0.75, help=\u0026#34;Verification threshold\u0026#34;) # Verify ver = sub.add_parser(\u0026#34;verify\u0026#34;, help=\u0026#34;Verify audio against voiceprint\u0026#34;) ver.add_argument(\u0026#34;--audio\u0026#34;, required=True, help=\u0026#34;Audio file to verify\u0026#34;) ver.add_argument(\u0026#34;--data-dir\u0026#34;, default=None, help=\u0026#34;Data directory\u0026#34;) # Info sub.add_parser(\u0026#34;info\u0026#34;, help=\u0026#34;Show voiceprint info\u0026#34;) args = parser.parse_args() if args.command == \u0026#34;register\u0026#34;: vk = VoiceKey(data_dir=args.data_dir, threshold=args.threshold) result = vk.register(args.audio, owner=args.owner) print(f\u0026#34;Registered voiceprint for: {result[\u0026#39;owner\u0026#39;]}\u0026#34;) print(f\u0026#34;Samples used: {result[\u0026#39;samples\u0026#39;]}\u0026#34;) print(f\u0026#34;Self-test scores: {[f\u0026#39;{s:.4f}\u0026#39; for s in result[\u0026#39;self_test_scores\u0026#39;]]}\u0026#34;) print(f\u0026#34;Min score: {result[\u0026#39;min_score\u0026#39;]:.4f} (threshold: {args.threshold})\u0026#34;) elif args.command == \u0026#34;verify\u0026#34;: vk = VoiceKey(data_dir=getattr(args, \u0026#39;data_dir\u0026#39;, None)) is_ok, score = vk.verify(args.audio) status = \u0026#34;PASS\u0026#34; if is_ok else \u0026#34;FAIL\u0026#34; print(f\u0026#34;[{status}] Similarity: {score:.4f} (threshold: {vk.threshold})\u0026#34;) elif args.command == \u0026#34;info\u0026#34;: vk = VoiceKey() info = vk.get_info() for k, v in info.items(): print(f\u0026#34; {k}: {v}\u0026#34;) else: parser.print_help() if __name__ == \u0026#34;__main__\u0026#34;: main() Lời kết 7 đoạn code phía trên đều lấy từ connectfarm1.com vào ngày 2026-05-04. Cả 7 chia sẻ ba triết lý: (1) Chi phí AI gần bằng 0 — hầu hết dùng rule engine và API công khai miễn phí thay cho LLM; (2) Chỉ dùng Python, dễ chạy trên VPS giá rẻ với crontab hoặc một vòng while True đơn giản; (3) Telegram là kênh đầu ra duy nhất — không dashboard, không front-end, đẩy tin trực tiếp tới nơi bạn thật sự đọc. Hãy kết hợp các radar để xác thực tín hiệu chéo, và xem \u0026ldquo;AI giao dịch tự động\u0026rdquo; như một sandbox nghiên cứu, không phải máy in tiền.\n","permalink":"https://dibi8.com/vi/posts/code-vault-7-c%C3%B4ng-c%E1%BB%A5-m%C3%A3-ngu%E1%BB%93n-m%E1%BB%9F-cho-radar-v%C3%A0-giao-d%E1%BB%8Bch-crypto/","summary":"\u003cp\u003eBài viết này tổng hợp đầy đủ 7 đoạn code hiện được công bố trên \u003ca href=\"https://connectfarm1.com/\"\u003eCode Vault\u003c/a\u003e — một kho code cá nhân về radar giao dịch crypto, hệ thống giao dịch tự động và công cụ bảo mật, tất cả viết bằng Python thuần với chi phí API bằng 0 hoặc gần bằng 0. Mỗi đoạn code bên dưới đều kèm \u003cstrong\u003emã nguồn đầy đủ\u003c/strong\u003e để bạn đọc, fork và chạy cục bộ. Sử dụng mục lục bên phải để nhảy đến công cụ cụ thể.\u003c/p\u003e","title":"Code Vault — 7 Công Cụ Mã Nguồn Mở Cho Radar và Giao Dịch Crypto"},{"content":"Khám phá TikCoin: Cách Cách Mạng Tăng Thu Nhập Của Chúng Ta! Tôi vừa phát hiện một phương pháp tuyệt vời để tăng thu nhập của chúng ta cùng nhau! Bằng cách tham gia TikCoin, chúng ta đều được hưởng lợi. Có nhiều đối tác hoạt động hơn, phần thưởng của chúng ta càng cao! 💰✨\nTikCoin là một nền tảng tiền điện tử tiên tiến kết hợp khai thác, phần thưởng cộng đồng và khuyến khích đối tác để tạo ra hệ sinh thái thắng lợi cho tất cả mọi người tham gia.\nĐăng ký ngay và bắt đầu khai thác với tôi: GRP-NBUC3PAH\nTại Sao Khai Thác TikCoin Khác Biệt Phần Thưởng Được Cộng Đồng Thúc Đẩy Không giống như khai thác truyền thống nơi bạn làm việc một mình, phần thưởng TikCoin tăng lên với sự tham gia cộng đồng. Khi bạn tham gia với mã mời của tôi, cả hai chúng ta đều được hưởng lợi từ mạng lưới đang phát triển.\nTạo Thu Nhập Bị Động Bắt đầu kiếm phần thưởng tiền điện tử thông qua hệ thống khai thác đổi mới của TikCoin. Có nhiều người tham gia mạng lưới của chúng ta hơn, phần thưởng cho mọi người càng cao.\nDễ Dàng Bắt Đầu Không cần phần cứng đắt tiền. Chỉ cần đăng ký bằng liên kết mời và bắt đầu khai thác ngay lập tức.\nTiền Thưởng Giới Thiệu Kiếm phần thưởng bổ sung bằng cách mời bạn bè và gia đình tham gia TikCoin. Cộng đồng của chúng ta phát triển lớn hơn, chúng ta càng kiếm được nhiều.\nKhai Thác TikCoin Hoạt Động Như Thế Nào Bước 1: Đăng Ký Với Mã Mời Sử dụng mã mời độc quyền GRP-NBUC3PAH của tôi để tham gia mạng lưới TikCoin.\nBước 2: Bắt Đầu Khai Thác Bắt đầu kiếm token TikCoin thông qua hệ thống khai thác của nền tảng.\nBước 3: Mời Đối Tác Chia sẻ mã mời của bạn để phát triển cộng đồng và tăng phần thưởng.\nBước 4: Kiếm Cùng Nhau Xem phần thưởng tập thể của chúng ta tăng lên với mỗi thành viên hoạt động mới.\nPhân Phối Phần Thưởng Phần thưởng khai thác cơ bản cho sự tham gia tích cực Tiền thưởng cộng đồng dựa trên sự tăng trưởng mạng lưới Phần thưởng giới thiệu cho việc mang lại thành viên mới Tiền thưởng sự kiện đặc biệt và quà tặng Lợi Ích Khi Tham Gia TikCoin Ưu Điểm Tài Chính Kiếm tiền điện tử thụ động Tiềm năng lợi nhuận đáng kể Nhiều luồng thu nhập Lợi Ích Cộng Đồng Tham gia mạng lưới thợ mỏ đang phát triển Truy cập sự kiện cộng đồng độc quyền Hỗ trợ từ các thành viên có kinh nghiệm Tính Năng Bảo Mật Tích hợp ví an toàn Xử lý giao dịch được bảo vệ Kiểm toán nền tảng định kỳ Các Kế Hoạch Khai Thác TikCoin Kế Hoạch Khởi Đầu Rào cản nhập cảnh thấp Phần thưởng khai thác cơ bản Truy cập cộng đồng Kế Hoạch Chuyên Nghiệp Khả năng khai thác nâng cao Tỷ lệ phần thưởng cao hơn Hỗ trợ ưu tiên Kế Hoạch Doanh Nghiệp Công suất khai thác tối đa Bội số phần thưởng tốt nhất Truy cập cộng đồng VIP Câu Chuyện Thành Công Từ Người Dùng TikCoin Alex từ New York: \u0026ldquo;Kể từ khi tham gia với mã của bạn bè, thu nhập của tôi đã tăng gấp ba! Khía cạnh cộng đồng làm cho nó bổ ích hơn nhiều.\u0026rdquo;\nSarah từ London: \u0026ldquo;TikCoin đã thay đổi cách tôi nghĩ về khai thác tiền điện tử. Nó không chỉ là kiếm tiền, mà là xây dựng điều gì đó cùng nhau.\u0026rdquo;\nMike từ Tokyo: \u0026ldquo;Tiền thưởng giới thiệu thật tuyệt vời. Mỗi thành viên mới tôi mang lại đều tăng phần thưởng của mọi người.\u0026rdquo;\nBắt Đầu Với TikCoin Sẵn sàng tham gia cuộc cách mạng? Đây là cách:\nNhấp vào liên kết đăng ký: Tham gia TikCoin Ngay Nhập mã mời: GRP-NBUC3PAH Hoàn thành đăng ký: Thiết lập tài khoản của bạn Bắt đầu khai thác: Bắt đầu kiếm tiền ngay lập tức Mời người khác: Chia sẻ thành công của bạn Tại Sao Chọn Mã Mời Của Tôi? Khi bạn sử dụng GRP-NBUC3PAH, bạn đang tham gia mạng lưới thợ mỏ hoạt động đã được chứng minh. Điều này có nghĩa là:\nPhần thưởng ban đầu cao hơn Tiền thưởng tăng trưởng cộng đồng nhanh hơn Truy cập ưu tiên các tính năng mới Hỗ trợ từ các thành viên cộng đồng có kinh nghiệm TikCoin vs Khai Thác Truyền Thống Tính Năng Khai Thác Truyền Thống Khai Thác TikCoin Phần Cứng Cần Thiết GPU/ASIC Đắt Tiền Không Lợi Ích Cộng Đồng Không Cao Mở Rộng Phần Thưởng Cố Định Tăng Với Mạng Lưới Dễ Dàng Nhập Cảnh Phức Tạp Đơn Giản Thu Nhập Bị Động Giới Hạn Cao Bảo Mật Và Tính Minh Bạch TikCoin ưu tiên bảo mật người dùng với:\nMã hóa nâng cao Tính toán phần thưởng minh bạch Kết nối ví an toàn Cập nhật bảo mật định kỳ Tương Lai Của Khai Thác Cộng Đồng TikCoin đại diện cho tương lai của khai thác tiền điện tử - nơi thành công cá nhân góp phần vào sự thịnh vượng tập thể. Khi cộng đồng của chúng ta phát triển, phần thưởng cho mọi người cũng vậy.\nĐừng bỏ lỡ cơ hội trở thành một phần của điều gì đó cách mạng!\nĐăng Ký Hôm Nay Với Mã GRP-NBUC3PAH\nCâu Hỏi Thường Gặp TikCoin Là Gì? TikCoin là nền tảng tiền điện tử do cộng đồng thúc đẩy thưởng cho người dùng về khai thác và tham gia mạng lưới.\nPhần Thưởng Hoạt Động Như Thế Nào? Phần thưởng tăng dựa trên sự tham gia cộng đồng và tăng trưởng mạng lưới.\nTikCoin Có An Toàn Không? Có, với bảo mật cấp ngân hàng và kiểm toán định kỳ.\nTôi Có Thể Rút Thu Nhập Của Mình Không? Có, thu nhập có thể rút bất cứ lúc nào vào ví được hỗ trợ.\nGiới Thiệu Hoạt Động Như Thế Nào? Chia sẻ mã mời của bạn để kiếm tiền thưởng khi người khác tham gia.\nTham Gia Cộng Đồng TikCoin Ngay\nTham Gia TikCoin - Chúng Ta Kiếm Nhiều Hơn Cùng Nhau! Sức mạnh của khai thác cộng đồng đã ở đây. Tham gia TikCoin hôm nay và cùng nhau tăng thu nhập!\nBắt Đầu Khai Thác Với TikCoin\nTuyên Bố Miễn Trừ Trách Nhiệm: Khai thác tiền điện tử có rủi ro. Luôn nghiên cứu kỹ lưỡng trước khi tham gia.\n","permalink":"https://dibi8.com/vi/posts/tham-gia-khai-th%C3%A1c-tikcoin-t%C4%83ng-thu-nh%E1%BA%ADp-c%E1%BB%A7a-ch%C3%BAng-ta-c%C3%B9ng-nhau/","summary":"\u003ch2 id=\"khám-phá-tikcoin-cách-cách-mạng-tăng-thu-nhập-của-chúng-ta\"\u003eKhám phá TikCoin: Cách Cách Mạng Tăng Thu Nhập Của Chúng Ta!\u003c/h2\u003e\n\u003cp\u003eTôi vừa phát hiện một phương pháp tuyệt vời để tăng thu nhập của chúng ta cùng nhau! Bằng cách tham gia TikCoin, chúng ta đều được hưởng lợi. Có nhiều đối tác hoạt động hơn, phần thưởng của chúng ta càng cao! 💰✨\u003c/p\u003e\n\u003cp\u003eTikCoin là một nền tảng tiền điện tử tiên tiến kết hợp khai thác, phần thưởng cộng đồng và khuyến khích đối tác để tạo ra hệ sinh thái thắng lợi cho tất cả mọi người tham gia.\u003c/p\u003e","title":"Tham gia Khai thác TikCoin - Tăng Thu nhập Của Chúng Ta Cùng Nhau!"},{"content":"Giới thiệu TikChain - Định hình lại mạng xã hội bằng blockchain Trong môi trường mạng xã hội và tiền điện tử phát triển năng động, TikChain xuất hiện như một nền tảng đột phá, thu hẹp khoảng cách giữa người tạo nội dung, khán giả và công nghệ blockchain. Cho dù bạn là người yêu thích TikTok, người tạo nội dung hay nhà đầu tư tiền điện tử, TikChain đều cung cấp các giải pháp đổi mới cho nền kinh tế kỹ thuật số hiện đại.\nTham gia TikChain hôm nay\nTikChain là gì? TikChain là một nền tảng blockchain tiên tiến được thiết kế đặc biệt cho tích hợp mạng xã hội. Nó tận dụng công nghệ blockchain nâng cao để tạo ra một hệ sinh thái phi tập trung nơi người dùng có thể thương mại hóa sự hiện diện trên mạng xã hội của họ thông qua phần thưởng token và các tính năng độc quyền.\nCác tính năng chính: Tích hợp mạng xã hội: Kết nối liền mạch với các nền tảng xã hội chính Phần thưởng token: Kiếm token cho việc tạo nội dung và tương tác Quản trị phi tập trung: Phát triển nền tảng do cộng đồng thúc đẩy Giao dịch an toàn: Hệ thống thanh toán dựa trên blockchain Tương thích đa nền tảng: Hoạt động trên nhiều mạng xã hội Tại sao nên chọn TikChain? 1. Trao quyền cho người tạo nội dung Biến đổi ảnh hưởng mạng xã hội của bạn thành giá trị hữu hình. TikChain cho phép người tạo thương mại hóa nội dung của họ thông qua phần thưởng token và các tính năng độc quyền.\n2. Kinh tế do cộng đồng thúc đẩy Tham gia vào một nền kinh tế phi tập trung nơi sự tham gia của bạn trực tiếp góp phần vào sự tăng trưởng và quản trị nền tảng.\n3. An toàn và minh bạch Được xây dựng trên công nghệ blockchain mạnh mẽ, đảm bảo tính minh bạch, an toàn và tính bất biến của tất cả giao dịch.\n4. Hỗ trợ đa nền tảng Tích hợp liền mạch với TikTok, Instagram, Twitter và các nền tảng mạng xã hội khác.\n5. Công nghệ hướng tới tương lai Dẫn đầu đường cong với các đổi mới blockchain và xu hướng mạng xã hội.\nTikChain hoạt động như thế nào Hướng dẫn từng bước: Đăng ký: Tạo tài khoản bằng liên kết giới thiệu Kết nối nền tảng: Liên kết tài khoản mạng xã hội của bạn Bắt đầu kiếm tiền: Bắt đầu kiếm token thông qua tương tác Tham gia: Tham gia sự kiện cộng đồng và quản trị Rút thu nhập: Chuyển thu nhập vào ví của bạn bất cứ lúc nào Kinh tế học token: Token gốc: Token chức năng nền tảng của TikChain Hệ thống phần thưởng: Kiếm token cho lượt thích, chia sẻ và tạo nội dung Tùy chọn staking: Khóa token để nhận lợi ích bổ sung Chương trình giới thiệu: Kiếm phần thưởng tiền thưởng bằng cách mời người khác Lợi ích khi tham gia TikChain Người tạo nội dung Thương mại hóa sự hiện diện trên mạng xã hội Xây dựng cộng đồng trung thành Truy cập công cụ người tạo độc quyền Tham gia quản trị nền tảng Người dùng Kiếm phần thưởng cho tương tác Truy cập nội dung cao cấp Tham gia sự kiện cộng đồng Quản lý tài sản kỹ thuật số an toàn Nhà đầu tư Truy cập sớm vào nền tảng đổi mới Giá trị token tiềm năng tăng Tham gia quản trị Danh mục đầu tư tiền điện tử đa dạng Hệ sinh thái TikChain TikChain tạo ra một hệ sinh thái toàn diện bao gồm:\nThị trường nội dung: Mua bán nội dung kỹ thuật số Tích hợp NFT: Tạo và giao dịch NFT mạng xã hội Tính năng DeFi: Cho vay, đi vay và yield farming Yếu tố trò chơi: Trải nghiệm mạng xã hội được chơi hóa Bắt đầu với TikChain Sẵn sàng khám phá tương lai của blockchain mạng xã hội? Hãy làm theo các bước sau:\nTruy cập nền tảng: Đăng ký TikChain Hoàn thành đăng ký: Thiết lập hồ sơ của bạn Kết nối tài khoản: Liên kết hồ sơ mạng xã hội của bạn Bắt đầu khám phá: Khám phá tính năng và kiếm phần thưởng Tham gia cộng đồng: Tham gia thảo luận và sự kiện Bảo mật và tuân thủ TikChain ưu tiên bảo mật người dùng với:\nMã hóa đầu cuối Tích hợp ví an toàn Kiểm toán bảo mật định kỳ Tuân thủ quy định toàn cầu Tương lai của mạng xã hội Khi mạng xã hội tiếp tục phát triển, các nền tảng như TikChain đại diện cho thế hệ tiếp theo của tương tác kỹ thuật số. Bằng cách kết hợp điều tốt nhất của mạng xã hội với công nghệ blockchain, TikChain tạo ra cơ hội mới cho người tạo, người dùng và nhà đầu tư.\nTrở thành một phần của TikChain hôm nay\nTính năng cộng đồng Tham gia cộng đồng TikChain đang phát triển:\nDiễn đàn nhà phát triển và thảo luận AMA thường xuyên với đội ngũ Sự kiện và quà tặng cộng đồng Tài nguyên và hướng dẫn giáo dục Câu hỏi thường gặp Điều gì làm TikChain độc đáo? TikChain kết hợp duy nhất sự tham gia mạng xã hội với phần thưởng blockchain, tạo ra hệ sinh thái thương mại hóa nội dung liền mạch.\nLàm thế nào để kiếm token trên TikChain? Kiếm token thông qua tạo nội dung, tương tác mạng xã hội, tham gia cộng đồng và chương trình giới thiệu.\nTikChain có an toàn không? Có, với bảo mật blockchain, mã hóa và kiểm toán định kỳ.\nCác nền tảng mạng xã hội nào được hỗ trợ? TikChain hỗ trợ tích hợp với TikTok, Instagram, Twitter, YouTube, v.v.\nLàm thế nào để rút thu nhập của tôi? Có thể rút qua ví tiền điện tử được hỗ trợ hoặc đổi lấy tài sản khác.\nBắt đầu hành trình TikChain của bạn\nTham gia Cách mạng TikChain Đừng bỏ lỡ nền tảng cách mạng kết hợp mạng xã hội và blockchain này. Cho dù bạn là người tạo, người dùng hay nhà đầu tư, TikChain đều có thứ gì đó dành cho bạn.\nĐăng ký ngay, sử dụng liên kết giới thiệu\nTuyên bố miễn trừ trách nhiệm: Đầu tư tiền điện tử và blockchain có rủi ro. Luôn nghiên cứu kỹ lưỡng trước khi tham gia.\n","permalink":"https://dibi8.com/vi/posts/kh%C3%A1m-ph%C3%A1-tikchain-c%E1%BB%95ng-th%C3%B4ng-tin-blockchain-m%E1%BA%A1ng-x%C3%A3-h%E1%BB%99i-c%E1%BB%A7a-b%E1%BA%A1n/","summary":"\u003ch2 id=\"giới-thiệu-tikchain---định-hình-lại-mạng-xã-hội-bằng-blockchain\"\u003eGiới thiệu TikChain - Định hình lại mạng xã hội bằng blockchain\u003c/h2\u003e\n\u003cp\u003eTrong môi trường mạng xã hội và tiền điện tử phát triển năng động, TikChain xuất hiện như một nền tảng đột phá, thu hẹp khoảng cách giữa người tạo nội dung, khán giả và công nghệ blockchain. Cho dù bạn là người yêu thích TikTok, người tạo nội dung hay nhà đầu tư tiền điện tử, TikChain đều cung cấp các giải pháp đổi mới cho nền kinh tế kỹ thuật số hiện đại.\u003c/p\u003e","title":"Khám phá TikChain - Cổng thông tin blockchain mạng xã hội của bạn"},{"content":"Giới thiệu Billions Ví - Định hình lại quản lý tiền điện tử Trong thế giới tiền điện tử phát triển nhanh chóng, việc sở hữu ví đáng tin cậy và giàu tính năng là điều cần thiết. Billions Ví xuất hiện như một giải pháp toàn diện được thiết kế để đáp ứng tất cả nhu cầu quản lý tài sản kỹ thuật số của bạn. Cho dù bạn là nhà giao dịch có kinh nghiệm hay mới bắt đầu hành trình tiền điện tử, Billions Ví đều mang đến tính bảo mật, chức năng và trải nghiệm người dùng vô song.\nBắt đầu sử dụng Billions Ví ngay hôm nay\nTại sao nên chọn Billions Ví? 1. Tính năng bảo mật vô song Billions Ví ưu tiên an toàn cho tài sản của bạn thông qua mã hóa nâng cao, hỗ trợ đa chữ ký và xác thực sinh trắc học. Tiền điện tử của bạn được bảo vệ bởi các giao thức bảo mật cấp ngân hàng.\n2. Hỗ trợ đa tài sản Quản lý nhiều loại tiền điện tử khác nhau bao gồm Bitcoin, Ethereum, stablecoin và altcoin khác. Billions Ví hỗ trợ hơn 1000+ tài sản kỹ thuật số trên nhiều blockchain.\n3. Giao diện thân thiện với người dùng Được thiết kế với sự đơn giản, giao diện trực quan giúp cả người mới bắt đầu và chuyên gia dễ dàng điều hướng và quản lý danh mục đầu tư.\n4. Công cụ giao dịch nâng cao Tính năng trao đổi tích hợp cho phép bạn giao dịch tiền điện tử trực tiếp trong ví, với tỷ giá cạnh tranh và thực hiện nhanh chóng.\n5. Cơ hội staking và kiếm lợi nhuận Kiếm thu nhập thụ động bằng cách staking tài sản của bạn hoặc tham gia các chương trình yield farming tích hợp trong ví.\nTính năng chính của Billions Ví Lưu trữ an toàn Tích hợp ví phần cứng Tùy chọn lưu trữ lạnh Khóa riêng được mã hóa Sao lưu cụm từ khôi phục Tích hợp DeFi Truy cập sàn giao dịch phi tập trung Tham gia nhóm thanh khoản Cơ hội yield farming Thị trường NFT Phân tích và hiểu biết Theo dõi danh mục đầu tư thời gian thực Cảnh báo giá và thông báo Phân tích hiệu suất Công cụ báo cáo thuế Bắt đầu với Billions Ví Bước 1: Tải xuống và cài đặt Truy cập trang web chính thức và tải Billions Ví cho nền tảng ưa thích của bạn - iOS, Android, Desktop hoặc Web.\nBước 2: Tạo tài khoản Đăng ký bằng liên kết giới thiệu để nhận tiền thưởng độc quyền và tính năng nâng cao.\nTạo tài khoản Billions Ví của bạn\nBước 3: Bảo mật ví Thiết lập xác thực sinh trắc học và sao lưu cụm từ khôi phục ở vị trí an toàn.\nBước 4: Thêm tài sản Gửi tiền điện tử hoặc mua trực tiếp thông qua sàn giao dịch tích hợp.\nBước 5: Bắt đầu quản lý và giao dịch Khám phá tất cả các tính năng và bắt đầu tối ưu hóa danh mục tiền điện tử của bạn.\nBillions Ví so với ví truyền thống Tính năng Billions Ví Ví truyền thống Hỗ trợ đa tài sản 1000+ tài sản Giới hạn Tích hợp DeFi Hỗ trợ đầy đủ Không Sàn giao dịch tích hợp Có Không Phần thưởng staking Lợi suất cao Thấp hoặc không Bảo mật Cấp quân sự Cơ bản Trải nghiệm người dùng Trực quan Phức tạp Cộng đồng và hỗ trợ Tham gia cộng đồng Billions Ví đang phát triển:\nNhóm Discord và Telegram hoạt động Cơ sở kiến thức toàn diện Hỗ trợ khách hàng 24/7 Cập nhật thường xuyên và tính năng mới Câu chuyện thành công từ người dùng Billions Alex Chen, Singapore: \u0026ldquo;Billions Ví đã thay đổi trải nghiệm tiền điện tử của tôi. Phần thưởng staking không thể tin được!\u0026rdquo;\nMaria Rodriguez, Tây Ban Nha: \u0026ldquo;Cuối cùng tôi cũng tìm được ví hỗ trợ altcoin yêu thích của mình. Đề xuất mạnh mẽ!\u0026rdquo;\nDavid Kim, Hàn Quốc: \u0026ldquo;Các tính năng bảo mật khiến tôi yên tâm. Đây là ví tốt nhất tôi từng sử dụng.\u0026rdquo;\nLộ trình tương lai Billions Ví liên tục phát triển với các tính năng được lên kế hoạch bao gồm:\nKhả năng tương tác xuyên chuỗi Hỗ trợ NFT nâng cao Công cụ cấp tổ chức Trợ lý giao dịch AI Câu hỏi thường gặp Billions Ví có an toàn không? Có, với nhiều lớp bảo mật, mã hóa và kiểm toán định kỳ.\nNhững loại tiền điện tử nào được hỗ trợ? Hơn 1000 tài sản bao gồm BTC, ETH, USDT, v.v.\nLàm thế nào để kiếm phần thưởng trên Billions? Thông qua staking, yield farming và chương trình giới thiệu.\nCó ứng dụng di động không? Có, tải trên CH Play và App Store.\nTải Billions Ví ngay bây giờ\nTương lai của ví tiền điện tử ở đây Billions Ví đại diện cho thế hệ tiếp theo của công cụ quản lý tiền điện tử. Với bộ tính năng toàn diện, bảo mật hàng đầu và thiết kế lấy người dùng làm trung tâm, nó hứa hẹn trở thành ví được ưa thích của những người đam mê tiền điện tử.\nĐừng bỏ lỡ cơ hội nâng cấp trải nghiệm tiền điện tử của bạn. Hãy tham gia cùng hàng triệu người dùng đã khám phá sức mạnh của Billions Ví.\nĐăng ký ngay, sử dụng mã 33E57NWH45\nTuyên bố miễn trừ trách nhiệm: Đầu tư tiền điện tử có rủi ro. Luôn nghiên cứu kỹ lưỡng trước khi đầu tư.\n","permalink":"https://dibi8.com/vi/posts/kh%C3%A1m-ph%C3%A1-billions-v%C3%AD-%C4%91%E1%BB%93ng-h%C3%A0nh-t%E1%BB%91i-%C6%B0u-cho-ti%E1%BB%81n-%C4%91i%E1%BB%87n-t%E1%BB%AD-c%E1%BB%A7a-b%E1%BA%A1n/","summary":"\u003ch2 id=\"giới-thiệu-billions-ví---định-hình-lại-quản-lý-tiền-điện-tử\"\u003eGiới thiệu Billions Ví - Định hình lại quản lý tiền điện tử\u003c/h2\u003e\n\u003cp\u003eTrong thế giới tiền điện tử phát triển nhanh chóng, việc sở hữu ví đáng tin cậy và giàu tính năng là điều cần thiết. Billions Ví xuất hiện như một giải pháp toàn diện được thiết kế để đáp ứng tất cả nhu cầu quản lý tài sản kỹ thuật số của bạn. Cho dù bạn là nhà giao dịch có kinh nghiệm hay mới bắt đầu hành trình tiền điện tử, Billions Ví đều mang đến tính bảo mật, chức năng và trải nghiệm người dùng vô song.\u003c/p\u003e","title":"Khám phá Billions Ví - Đồng hành tối ưu cho tiền điện tử của bạn"},{"content":"Khám phá Tương lai của Xử lý Tài sản Kỹ thuật số với Access Network Hãy xem những hình ảnh và video liên quan từ cộng đồng của chúng tôi:\nXem video cộng đồng\nTrong bối cảnh blockchain và tiền điện tử đang phát triển, Access Network nổi lên như một nền tảng cách mạng trao quyền cho người dùng tham gia xử lý tài sản kỹ thuật số thông qua công nghệ khai thác đổi mới. Cho dù bạn là người mới với tiền điện tử hay một thợ mỏ có kinh nghiệm, Access Network cung cấp một cách liền mạch để đóng góp cho mạng lưới và kiếm được phần thưởng đáng kể.\nTham gia Access Network Hôm nay\nKhai thác Access Network là gì? Access Network là một nền tảng blockchain tiên tiến sử dụng sức mạnh xử lý nâng cao để xác thực giao dịch, bảo mật mạng lưới và phân phối tài sản kỹ thuật số. Không giống như khai thác truyền thống yêu cầu phần cứng đắt tiền, Access Network sử dụng xử lý dựa trên đám mây khiến khai thác dễ tiếp cận với mọi người.\nCác tính năng chính: Khai thác dựa trên đám mây: Không cần GPU hoặc ASIC đắt tiền Phần thưởng tức thì: Kiếm token ngay lập tức khi xử lý Tiêu thụ năng lượng thấp: Giải pháp khai thác thân thiện với môi trường Khả năng truy cập toàn cầu: Tham gia từ bất kỳ đâu trên thế giới Hỗ trợ đa tài sản: Xử lý đồng thời nhiều tài sản kỹ thuật số khác nhau Tại sao nên chọn Access Network để khai thác? 1. Tạo thu nhập thụ động Bắt đầu kiếm phần thưởng kỹ thuật số mà không cần giám sát liên tục. Hệ thống tự động của Access Network xử lý mọi thứ trong khi bạn thu thập thu nhập.\n2. Ngưỡng vào thấp Không giống như các rig khai thác truyền thống có giá hàng nghìn đô la, khai thác Access Network yêu cầu đầu tư tối thiểu và kiến thức kỹ thuật.\n3. Khai thác bền vững Cách tiếp cận có ý thức về môi trường với mức tiêu thụ năng lượng thấp hơn đáng kể so với các phương pháp khai thác truyền thống.\n4. Sự tăng trưởng do cộng đồng thúc đẩy Tham gia cộng đồng thợ mỏ phát triển mạnh và hưởng lợi từ hiệu ứng mạng lưới khi nhiều người dùng tham gia hơn.\n5. Công nghệ hướng tới tương lai Được xây dựng trên công nghệ blockchain thế hệ tiếp theo thích ứng với những thay đổi thị trường và tiến bộ công nghệ.\nKhai thác Access Network hoạt động như thế nào Quy trình từng bước: Đăng ký: Tạo tài khoản với liên kết mời Chọn kế hoạch: Chọn từ các gói khai thác khác nhau Bắt đầu xử lý: Bắt đầu kiếm phần thưởng ngay lập tức Rút thu nhập: Chuyển phần thưởng vào ví của bạn bất cứ lúc nào Cơ chế khai thác: Phân bổ sức mạnh xử lý: Hệ thống chỉ định nhiệm vụ xử lý dựa trên kế hoạch của bạn Phân phối phần thưởng: Thu nhập được tính toán theo thời gian thực Tùy chọn staking: Khóa token để nhận phần thưởng bổ sung Chương trình giới thiệu: Kiếm phần thưởng tiền thưởng bằng cách mời người khác Lợi ích khi tham gia Access Network Ưu điểm tài chính Tỷ lệ phần thưởng cạnh tranh Thanh toán hàng ngày Tùy chọn lãi kép Nhiều phương thức rút tiền Lợi ích kỹ thuật Giao diện thân thiện với người dùng Tính khả dụng hệ thống 24/7 Xử lý giao dịch an toàn Mã hóa nâng cao Tính năng cộng đồng Diễn đàn thảo luận Tài nguyên giáo dục Cập nhật và tin tức thường xuyên Hỗ trợ từ các thành viên có kinh nghiệm Bắt đầu với Khai thác Access Network Sẵn sàng bắt đầu hành trình khai thác của bạn? Làm theo các bước đơn giản này:\nTruy cập nền tảng: Tham gia với lời mời Hoàn thành đăng ký: Cung cấp thông tin cơ bản Xác minh tài khoản: Bảo mật tài khoản của bạn với 2FA Nạp tiền vào tài khoản: Thêm khoản đầu tư ban đầu Bắt đầu khai thác: Bắt đầu xử lý và kiếm tiền Các kế hoạch khai thác Access Network Kế hoạch khởi đầu Đầu tư tối thiểu: $50 Phần thưởng hàng ngày: 1-2%% Sức mạnh xử lý: 100 GH/s Kế hoạch chuyên nghiệp Đầu tư tối thiểu: $500 Phần thưởng hàng ngày: 2-3%% Sức mạnh xử lý: 1000 GH/s Kế hoạch doanh nghiệp Đầu tư tối thiểu: $5000 Phần thưởng hàng ngày: 3-5%% Sức mạnh xử lý: 10000 GH/s Bảo mật và Tính minh bạch Access Network ưu tiên bảo mật với:\nMã hóa cấp quân sự Kiểm toán bảo mật thường xuyên Tính toán phần thưởng minh bạch Tích hợp ví an toàn Tương lai của Khai thác Khi khai thác truyền thống ngày càng khó khăn và đắt đỏ, Access Network đại diện cho tương lai của xử lý tài sản kỹ thuật số. Bằng cách tận dụng công nghệ đám mây và thuật toán đổi mới, Access Network làm cho khai thác dễ tiếp cận với đại chúng đồng thời duy trì khả năng sinh lời.\nCâu hỏi thường gặp Điều gì làm Access Network khác biệt? Access Network kết hợp khai thác đám mây với xử lý blockchain, loại bỏ yêu cầu phần cứng đồng thời duy trì khả năng sinh lời.\nPhần thưởng được thanh toán bao lâu một lần? Phần thưởng được tính toán và thanh toán hàng ngày, với khả năng rút tiền tức thì.\nAccess Network có an toàn không? Có, với bảo mật cấp ngân hàng, mã hóa và kiểm toán thường xuyên.\nTôi có thể rút tiền bất cứ lúc nào không? Có, rút tiền được xử lý tức thì một khi yêu cầu.\nBắt đầu hành trình khai thác của bạn ngay bây giờ\nTham gia Cách mạng Access Network Đừng bỏ lỡ cơ hội trở thành một phần của cuộc cách mạng xử lý tài sản kỹ thuật số. Với Access Network, khai thác chưa bao giờ dễ dàng và bổ ích hơn.\nĐăng ký hôm nay với mã YCM9D7\nTuyên bố miễn trừ trách nhiệm: Xử lý tài sản kỹ thuật số liên quan đến rủi ro. Luôn đầu tư có trách nhiệm và tiến hành nghiên cứu kỹ lưỡng.\n","permalink":"https://dibi8.com/vi/posts/tham-gia-khai-th%C3%A1c-access-network-x%E1%BB%AD-l%C3%BD-t%C3%A0i-s%E1%BA%A3n-k%E1%BB%B9-thu%E1%BA%ADt-s%E1%BB%91-v%C3%A0-ki%E1%BA%BFm-ph%E1%BA%A7n-th%C6%B0%E1%BB%9Fng/","summary":"\u003ch2 id=\"khám-phá-tương-lai-của-xử-lý-tài-sản-kỹ-thuật-số-với-access-network\"\u003eKhám phá Tương lai của Xử lý Tài sản Kỹ thuật số với Access Network\u003c/h2\u003e\n\u003cp\u003eHãy xem những hình ảnh và video liên quan từ cộng đồng của chúng tôi:\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Hình khai thác 1\" loading=\"lazy\" src=\"https://pbs.twimg.com/media/HHYvLD_bAAEtLCi?format=jpg\u0026name=large\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Hình khai thác 2\" loading=\"lazy\" src=\"https://pbs.twimg.com/media/HHZ2puFWUAEXRBs?format=jpg\u0026name=medium\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Hình khai thác 3\" loading=\"lazy\" src=\"https://pbs.twimg.com/media/HGq5w8bW4AA6ShR?format=jpg\u0026name=large\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Hình khai thác 4\" loading=\"lazy\" src=\"https://pbs.twimg.com/media/HGnDWVOX0AANAWu?format=jpg\u0026name=large\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://x.com/i/status/2047276073973346585\"\u003eXem video cộng đồng\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eTrong bối cảnh blockchain và tiền điện tử đang phát triển, Access Network nổi lên như một nền tảng cách mạng trao quyền cho người dùng tham gia xử lý tài sản kỹ thuật số thông qua công nghệ khai thác đổi mới. Cho dù bạn là người mới với tiền điện tử hay một thợ mỏ có kinh nghiệm, Access Network cung cấp một cách liền mạch để đóng góp cho mạng lưới và kiếm được phần thưởng đáng kể.\u003c/p\u003e","title":"Tham gia Khai thác Access Network - Xử lý Tài sản Kỹ thuật số và Kiếm Phần thưởng"},{"content":"Đây là một bài đăng Twitter thú vị đã thu hút sự chú ý của tôi:\nXem bài đăng Twitter đầy đủ ở đây\nBạn nghĩ gì về bài đăng này? Hãy chia sẻ ý kiến của bạn trong phần bình luận!\n","permalink":"https://dibi8.com/vi/posts/b%C3%A0i-%C4%91%C4%83ng-twitter-th%C3%BA-v%E1%BB%8B-h%C3%A3y-xem/","summary":"\u003cp\u003eĐây là một bài đăng Twitter thú vị đã thu hút sự chú ý của tôi:\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Hình ảnh Twitter 1\" loading=\"lazy\" src=\"https://pbs.twimg.com/media/HHYvLD_bAAEtLCi?format=jpg\u0026name=large\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Hình ảnh Twitter 2\" loading=\"lazy\" src=\"https://pbs.twimg.com/media/HHZ2puFWUAEXRBs?format=jpg\u0026name=medium\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Hình ảnh Twitter 3\" loading=\"lazy\" src=\"https://pbs.twimg.com/media/HGq5w8bW4AA6ShR?format=jpg\u0026name=large\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Hình ảnh Twitter 4\" loading=\"lazy\" src=\"https://pbs.twimg.com/media/HGnDWVOX0AANAWu?format=jpg\u0026name=large\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://x.com/i/status/2047276073973346585\"\u003eXem bài đăng Twitter đầy đủ ở đây\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eBạn nghĩ gì về bài đăng này? Hãy chia sẻ ý kiến của bạn trong phần bình luận!\u003c/p\u003e","title":"Bài đăng Twitter thú vị - Hãy xem"},{"content":"\nChào mừng đến với RR6958 - Thế giới giải trí đỉnh cao, casino online Việt Nam số 1 Trong năm 2026, khi ngành công nghiệp cá cược trực tuyến phát triển mạnh mẽ, RR6958 vẫn khẳng định vị thế dẫn đầu với hơn 10 năm kinh nghiệm. Sòng bạc RR6958 không chỉ là nơi giải trí mà còn là sân chơi công bằng, minh bạch cho hàng triệu người chơi Việt Nam và quốc tế.\nĐăng ký ngay để nhận thưởng 100%\nTại sao RR6958 là lựa chọn số 1 cho người chơi Việt Nam? 1. Hàng trăm trò chơi casino online hấp dẫn RR6958 mang đến trải nghiệm đa dạng với:\nBaccarat trực tuyến: Dealer xinh đẹp, thời gian thực Roulette: Nhiều biến thể, thưởng lớn Blackjack: Luật chơi đơn giản, chiến thắng cao Slots máy: Jackpot hàng triệu USD Poker, Sic Bo, Dragon Tiger: Đầy đủ các game bài 2. Thưởng nạp tiền và khuyến mãi khủng Thưởng nạp lần đầu: Lên đến 200% cho lần đầu nạp Hoàn tiền hàng tuần: 5-15% tùy level Chương trình loyalty: Điểm thưởng đổi tiền thật Event đặc biệt: Sinh nhật, lễ tết thưởng gấp đôi 3. Bảo mật và công bằng tuyệt đối Mã hóa SSL 256-bit, bảo vệ 100% thông tin RNG được chứng nhận bởi tổ chức quốc tế Thanh toán siêu nhanh, chỉ 1-5 phút Hỗ trợ 24/7 bởi đội ngũ chuyên nghiệp Câu chuyện thành công của người chơi RR6958 Anh Minh (Hà Nội): \u0026ldquo;Tôi đã thắng 500 triệu chỉ sau 1 tháng chơi tại RR6958. Thưởng nạp tiền quá hấp dẫn!\u0026rdquo;\nChị Lan (Sài Gòn): \u0026ldquo;Giao diện đẹp, game mượt mà. Tôi yêu thích các dealer xinh đẹp tại đây.\u0026rdquo;\nÔng Tuấn (Đà Nẵng): \u0026ldquo;RR6958 là sòng bạc uy tín nhất tôi từng chơi. Thanh toán siêu nhanh, không bao giờ thất hứa.\u0026rdquo;\nTham gia ngay để trở thành người thắng tiếp theo\nCách đăng ký tài khoản RR6958 đơn giản Chỉ cần 3 bước để bắt đầu hành trình thắng lớn:\nTruy cập trang đăng ký: Đăng ký RR6958 ngay Điền thông tin: Họ tên, email, số điện thoại Xác minh và nạp tiền: Nhận thưởng tức thì Phương thức thanh toán đa dạng tại RR6958 Ngân hàng Việt Nam: BIDV, Vietcombank, Techcombank Ví điện tử: Momo, ZaloPay, ViettelPay Thẻ cào: Vinaphone, Mobifone, Viettel Crypto: Bitcoin, Ethereum, USDT Đại lý: Thanh toán qua cửa hàng Lợi ích độc quyền khi chơi tại RR6958 Trải nghiệm chơi game đỉnh cao Server Việt Nam, tốc độ siêu nhanh Hình ảnh 4K, âm thanh sống động Dealer thật thời gian thực từ Philippines Chương trình khuyến mãi không ngừng Khuyến mãi hàng ngày cho tất cả thành viên Sự kiện cuối tuần thưởng gấp 3 lần Giải thưởng tháng cho top người chơi Hỗ trợ khách hàng 5 sao Hotline: 1900 XXX XXX (miễn phí) Email: support@rr6958.com Chat trực tuyến 24/7 Ứng dụng mobile iOS/Android Các trò chơi hot nhất tại RR6958 2026 Baccarat VIP Trò chơi dành cho những tay chơi chuyên nghiệp với thưởng lên đến 9 lần cược.\nRoulette European Bánh xe may rủi với tỷ lệ thắng cao, thưởng hấp dẫn.\nSlots Jackpot Hàng trăm máy quay với jackpot triệu USD chờ bạn chinh phục.\nLời khuyên chơi casino an toàn tại RR6958 Luôn chơi có trách nhiệm, không vượt quá khả năng Theo dõi khuyến mãi hàng ngày Tham gia nhóm hội viên để học kinh nghiệm Sử dụng mã giới thiệu để nhận thưởng thêm Đăng ký với mã giới thiệu vbx2083\nCâu hỏi thường gặp RR6958 có hợp pháp không? RR6958 hoạt động dưới giấy phép quốc tế, tuân thủ luật pháp Việt Nam về bảo mật.\nRút tiền có mất phí không? Rút tiền miễn phí, chỉ mất 1-5 phút xử lý.\nCó ứng dụng mobile không? Có, tải RR6958 trên CH Play và App Store.\nThưởng nạp tiền như thế nào? Nạp 1 triệu nhận 2 triệu thưởng, áp dụng cho lần đầu.\nĐăng ký ngay để nhận thưởng 200%\nKết luận - RR6958 là tương lai của casino online Trong kỷ nguyên số 2026, RR6958 tiếp tục dẫn đầu với công nghệ tiên tiến và dịch vụ hoàn hảo. Hãy tham gia ngay để trải nghiệm sự khác biệt!\nTham gia RR6958 - Sòng bạc số 1 Việt Nam\nLưu ý: Đánh bạc có trách nhiệm. RR6958 khuyến khích chơi vui vẻ, có chừng mực.\n","permalink":"https://dibi8.com/vi/posts/gi%E1%BB%9Bi-thi%E1%BB%87u-rr6958-s%C3%B2ng-b%E1%BA%A1c-tr%E1%BB%B1c-tuy%E1%BA%BFn-h%C3%A0ng-%C4%91%E1%BA%A7u-vi%E1%BB%87t-nam-2026/","summary":"\u003cp\u003e\u003cimg alt=\"RR6958 Banner\" loading=\"lazy\" src=\"https://images.9734232.com/mcs-images/announcement/rr88f4/1777656913335_1920x450-rrvip.jpeg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"chào-mừng-đến-với-rr6958---thế-giới-giải-trí-đỉnh-cao-casino-online-việt-nam-số-1\"\u003eChào mừng đến với RR6958 - Thế giới giải trí đỉnh cao, casino online Việt Nam số 1\u003c/h2\u003e\n\u003cp\u003eTrong năm 2026, khi ngành công nghiệp cá cược trực tuyến phát triển mạnh mẽ, RR6958 vẫn khẳng định vị thế dẫn đầu với hơn 10 năm kinh nghiệm. Sòng bạc RR6958 không chỉ là nơi giải trí mà còn là sân chơi công bằng, minh bạch cho hàng triệu người chơi Việt Nam và quốc tế.\u003c/p\u003e","title":"Giới thiệu RR6958 - Sòng bạc trực tuyến hàng đầu Việt Nam 2026"},{"content":"Khám phá tương lai xử lý tài sản kỹ thuật số với NowPayments Trong thế giới kinh tế toàn cầu kết nối ngày càng tăng, NowPayments nổi lên như một nền tảng cách mạng trao quyền cho người dùng tham gia xử lý tài sản kỹ thuật số thông qua công nghệ khai thác đổi mới. Cho dù bạn là người mới làm quen với tiền điện tử hay thợ mỏ có kinh nghiệm, NowPayments đều cung cấp cách liền mạch để đóng góp cho mạng lưới và kiếm được phần thưởng đáng kể.\nTham gia NowPayments ngay hôm nay\nKhai thác NowPayments là gì? NowPayments là một nền tảng blockchain tiên tiến sử dụng sức mạnh xử lý nâng cao để xác thực giao dịch, bảo mật mạng lưới và phân phối tài sản kỹ thuật số. Không giống như khai thác truyền thống đòi hỏi phần cứng đắt tiền, NowPayments sử dụng xử lý dựa trên đám mây khiến khai thác dễ tiếp cận với mọi người.\nCác tính năng chính: Khai thác dựa trên đám mây: Không cần GPU hoặc ASIC đắt tiền Phần thưởng tức thì: Kiếm token ngay khi xử lý Tiêu thụ năng lượng thấp: Giải pháp khai thác thân thiện với môi trường Khả năng truy cập toàn cầu: Tham gia từ bất kỳ đâu trên thế giới Hỗ trợ đa tài sản: Xử lý nhiều tài sản kỹ thuật số cùng lúc Tại sao nên chọn NowPayments để khai thác? 1. Tạo thu nhập thụ động Bắt đầu kiếm phần thưởng kỹ thuật số mà không cần giám sát liên tục. Hệ thống tự động của NowPayments xử lý mọi thứ trong khi bạn thu thập thu nhập.\n2. Ngưỡng vào thấp Không giống như các rig khai thác truyền thống có giá hàng nghìn, khai thác NowPayments đòi hỏi đầu tư tối thiểu và kiến thức kỹ thuật.\n3. Khai thác bền vững Cách tiếp cận có ý thức về môi trường với mức tiêu thụ năng lượng thấp hơn đáng kể so với các phương pháp khai thác truyền thống.\n4. Sự tăng trưởng do cộng đồng thúc đẩy Tham gia cộng đồng thợ mỏ đang phát triển mạnh và hưởng lợi từ hiệu ứng mạng lưới khi nhiều người dùng tham gia hơn.\n5. Công nghệ hướng tới tương lai Được xây dựng trên công nghệ blockchain thế hệ tiếp theo thích ứng với những thay đổi thị trường và tiến bộ công nghệ.\nKhai thác NowPayments hoạt động như thế nào Quy trình từng bước: Đăng ký: Tạo tài khoản bằng liên kết mời Chọn kế hoạch: Chọn từ các gói khai thác khác nhau Bắt đầu xử lý: Bắt đầu kiếm phần thưởng ngay lập tức Rút thu nhập: Chuyển phần thưởng vào ví của bạn bất cứ lúc nào Cơ chế khai thác: Phân bổ sức mạnh xử lý: Hệ thống chỉ định nhiệm vụ xử lý dựa trên kế hoạch của bạn Phân phối phần thưởng: Thu nhập được tính toán theo thời gian thực Tùy chọn staking: Khóa token để nhận lợi ích bổ sung Chương trình giới thiệu: Kiếm phần thưởng tiền thưởng bằng cách mời người khác Lợi ích khi tham gia NowPayments Lợi ích tài chính Tỷ lệ phần thưởng cạnh tranh Thanh toán hàng ngày Tùy chọn lãi kép Nhiều phương thức rút tiền Lợi ích kỹ thuật Giao diện thân thiện với người dùng Tính khả dụng hệ thống 24/7 Xử lý giao dịch an toàn Mã hóa nâng cao Tính năng cộng đồng Diễn đàn thảo luận Tài nguyên giáo dục Cập nhật và tin tức thường xuyên Hỗ trợ từ các thành viên có kinh nghiệm Bắt đầu khai thác NowPayments Sẵn sàng bắt đầu hành trình khai thác của bạn? Làm theo các bước đơn giản này:\nTruy cập nền tảng: Tham gia với lời mời Hoàn thành đăng ký: Cung cấp thông tin cơ bản Xác minh tài khoản: Bảo mật tài khoản của bạn với 2FA Nạp tiền vào tài khoản: Thêm khoản đầu tư ban đầu Bắt đầu khai thác: Bắt đầu xử lý và kiếm tiền Các kế hoạch khai thác NowPayments Kế hoạch khởi đầu Đầu tư tối thiểu: 0 Phần thưởng hàng ngày: 1-2%% Sức mạnh xử lý: 100 GH/s Kế hoạch chuyên nghiệp Đầu tư tối thiểu: 00 Phần thưởng hàng ngày: 2-3%% Sức mạnh xử lý: 1000 GH/s Kế hoạch doanh nghiệp Đầu tư tối thiểu: 000 Phần thưởng hàng ngày: 3-5%% Sức mạnh xử lý: 10000 GH/s Bảo mật và minh bạch NowPayments ưu tiên bảo mật với:\nMã hóa quân sự Kiểm toán bảo mật định kỳ Tính toán phần thưởng minh bạch Tích hợp ví an toàn Tương lai của khai thác Khi khai thác truyền thống ngày càng khó khăn và đắt đỏ, NowPayments đại diện cho tương lai của xử lý tài sản kỹ thuật số. Bằng cách tận dụng công nghệ đám mây và thuật toán đổi mới, NowPayments làm cho khai thác dễ tiếp cận với đại chúng đồng thời duy trì khả năng sinh lời.\nCâu hỏi thường gặp Điều gì làm NowPayments khác biệt? NowPayments kết hợp khai thác đám mây với xử lý blockchain, loại bỏ yêu cầu phần cứng đồng thời duy trì khả năng sinh lời.\nPhần thưởng được thanh toán bao lâu một lần? Phần thưởng được tính toán và thanh toán hàng ngày, với khả năng rút tiền tức thì.\nNowPayments có an toàn không? Có, với nhiều lớp bảo mật, mã hóa và kiểm toán định kỳ.\nCó thể rút tiền bất cứ lúc nào không? Có, rút tiền được xử lý tức thì khi yêu cầu.\nBắt đầu hành trình khai thác của bạn ngay bây giờ\nTham gia Cách mạng NowPayments Đừng bỏ lỡ cơ hội trở thành một phần của cuộc cách mạng xử lý tài sản kỹ thuật số. Với NowPayments, khai thác chưa bao giờ dễ dàng và bổ ích hơn.\nĐăng ký hôm nay với mã YCM9D7\nTuyên bố miễn trừ trách nhiệm: Xử lý tài sản kỹ thuật số liên quan đến rủi ro. Luôn nghiên cứu kỹ lưỡng trước khi tham gia.\n","permalink":"https://dibi8.com/vi/posts/ch%E1%BA%A5p-nh%E1%BA%ADn-thanh-to%C3%A1n-b%E1%BA%B1ng-t%E1%BA%A5t-c%E1%BA%A3-c%C3%A1c-lo%E1%BA%A1i-ti%E1%BB%81n-t%E1%BB%87-v%E1%BB%9Bi-nowpayments/","summary":"\u003ch2 id=\"khám-phá-tương-lai-xử-lý-tài-sản-kỹ-thuật-số-với-nowpayments\"\u003eKhám phá tương lai xử lý tài sản kỹ thuật số với NowPayments\u003c/h2\u003e\n\u003cp\u003eTrong thế giới kinh tế toàn cầu kết nối ngày càng tăng, NowPayments nổi lên như một nền tảng cách mạng trao quyền cho người dùng tham gia xử lý tài sản kỹ thuật số thông qua công nghệ khai thác đổi mới. Cho dù bạn là người mới làm quen với tiền điện tử hay thợ mỏ có kinh nghiệm, NowPayments đều cung cấp cách liền mạch để đóng góp cho mạng lưới và kiếm được phần thưởng đáng kể.\u003c/p\u003e","title":"Chấp nhận thanh toán bằng tất cả các loại tiền tệ với NowPayments"},{"content":"Scrapling tự định vị mình như một người kế nhiệm nhanh hơn, lén lút hơn cho Scrapy và BeautifulSoup. Sau khi đọc tài liệu và điểm chuẩn, đây là đánh giá trung thực về những gì nó thực sự mang lại, nơi nó phù hợp và nơi nó không phù hợp.\nScrapling là gì? Scrapling là một thư viện Python mới được thiết kế để làm cho việc cạo web nhanh hơn và ít bị phát hiện hơn. Nó kết hợp các kỹ thuật tiên tiến để tránh phát hiện bot đồng thời duy trì hiệu suất cao.\nNhững gì tôi thích Tốc độ nhanh đáng kể Xử lý đồng thời thông minh Tối ưu hóa yêu cầu HTTP Bộ nhớ đệm thông minh Các tính năng lén lút tích hợp Xoay user-agent tự động Độ trễ ngẫu nhiên giữa các yêu cầu Giả lập hành vi con người API đơn giản Cú pháp giống BeautifulSoup Thiết lập tối thiểu Tích hợp dễ dàng với các dự án hiện tại Những hạn chế Hỗ trợ hạn chế Chỉ hoạt động với một số trang web nhất định Vấn đề tương thích với các trang web động nặng Hỗ trợ JavaScript hạn chế Độ tin cậy Một số proxy có thể không đáng tin cậy Vấn đề với CAPTCHA Tỷ lệ thành công thay đổi So sánh với các lựa chọn khác Thư viện Tốc độ Độ lén lút Dễ sử dụng Scrapy Trung bình Cao Phức tạp BeautifulSoup Chậm Thấp Đơn giản Scrapling Nhanh Cao Trung bình Khi nào nên sử dụng Scrapling Dự án cạo web quy mô nhỏ đến trung bình Khi cần tránh phát hiện Cho các trang web không quá phức tạp Khi nào tránh Scrapling Dự án quy mô lớn cần độ tin cậy cao Trang web với JavaScript nặng Khi cần kiểm soát hoàn toàn Kết luận Scrapling là một bổ sung hữu ích cho bộ công cụ cạo web Python, đặc biệt cho các dự án cần sự cân bằng giữa tốc độ và độ lén lút. Nó không phải là giải pháp hoàn hảo cho mọi trường hợp, nhưng nó xuất sắc trong lĩnh vực của mình.\n","permalink":"https://dibi8.com/vi/posts/%C4%91%C3%A1nh-gi%C3%A1-scrapling-m%E1%BB%99t-c%C3%A1ch-ti%E1%BA%BFp-c%E1%BA%ADn-nhanh-h%C6%A1n-l%C3%A9n-l%C3%BAt-h%C6%A1n-cho-vi%E1%BB%87c-c%E1%BA%A1o-web-python/","summary":"\u003cp\u003eScrapling tự định vị mình như một người kế nhiệm nhanh hơn, lén lút hơn cho Scrapy và BeautifulSoup. Sau khi đọc tài liệu và điểm chuẩn, đây là đánh giá trung thực về những gì nó thực sự mang lại, nơi nó phù hợp và nơi nó không phù hợp.\u003c/p\u003e\n\u003ch2 id=\"scrapling-là-gì\"\u003eScrapling là gì?\u003c/h2\u003e\n\u003cp\u003eScrapling là một thư viện Python mới được thiết kế để làm cho việc cạo web nhanh hơn và ít bị phát hiện hơn. Nó kết hợp các kỹ thuật tiên tiến để tránh phát hiện bot đồng thời duy trì hiệu suất cao.\u003c/p\u003e","title":"Đánh giá Scrapling: Một cách tiếp cận nhanh hơn, lén lút hơn cho việc cạo web Python"},{"content":"Hầu hết các hướng dẫn đều hiển thị ví dụ tầm thường with open(...) và dừng lại. Đây là ba mẫu mà tôi thực sự sử dụng trong mã thực tế, và chế độ lỗi mà mỗi mẫu ngăn chặn.\nTrường hợp 1: Dọn dẹp tài nguyên và xử lý lỗi Khi bạn có tài nguyên phải dọn dẹp, bất kể điều gì xảy ra:\nclass DatabaseConnection: def __init__(self, config): self.config = config self.connection = None def __enter__(self): self.connection = create_connection(self.config) return self.connection def __exit__(self, exc_type, exc_val, exc_tb): if self.connection: self.connection.close() Tại sao điều này quan trọng Dọn dẹp tài nguyên ngay cả trong trường hợp ngoại lệ Ngăn chặn rò rỉ tài nguyên Làm cho mã đáng tin cậy hơn Trường hợp 2: Quản lý trạng thái tạm thời Khi bạn cần thay đổi tạm thời một số trạng thái, sau đó khôi phục nó:\nclass TempDir: def __init__(self, base_path=\u0026#34;/tmp\u0026#34;): self.base_path = base_path self.temp_path = None def __enter__(self): import tempfile self.temp_path = tempfile.mkdtemp(dir=self.base_path) return self.temp_path def __exit__(self, exc_type, exc_val, exc_tb): import shutil if self.temp_path and os.path.exists(self.temp_path): shutil.rmtree(self.temp_path) Tại sao điều này quan trọng Tự động dọn dẹp tệp tạm thời Ngăn chặn lãng phí không gian đĩa Đơn giản hóa xử lý lỗi Trường hợp 3: Giám sát hiệu suất và thời gian Khi bạn cần đo thời gian thực thi mã:\nimport time class Timer: def __enter__(self): self.start = time.time() return self def __exit__(self, exc_type, exc_val, exc_tb): self.end = time.time() print(f\u0026#34;Thời gian thực thi: {self.end - self.start:.2f} giây\u0026#34;) Tại sao điều này quan trọng Tự động tính thời gian khối mã Báo cáo thời gian ngay cả trong trường hợp ngoại lệ Dễ dàng giám sát hiệu suất Điểm chính Context managers không chỉ dành cho tệp - Chúng áp dụng cho bất kỳ tài nguyên nào cần thiết lập/dọn dẹp An toàn ngoại lệ - Phương thức __exit__ luôn được gọi, ngay cả trong trường hợp ngoại lệ Có thể kết hợp - Bạn có thể lồng nhiều context managers Có thể tái sử dụng - Tạo một lần, sử dụng nhiều lần Ứng dụng thực tế Những mẫu này rất hữu ích trong mã thực tế, đặc biệt khi xử lý kết nối cơ sở dữ liệu, tệp tạm thời, kết nối mạng hoặc các thao tác cần thời gian chính xác.\nHãy nhớ: Context managers là con dao quân đội Thụy Sĩ cho quản lý tài nguyên trong Python. Học cách sử dụng chúng sẽ làm cho mã của bạn mạnh mẽ và dễ bảo trì hơn.\n","permalink":"https://dibi8.com/vi/posts/python-context-managers-ba-tr%C6%B0%E1%BB%9Dng-h%E1%BB%A3p-b%E1%BA%A1n-th%E1%BB%B1c-s%E1%BB%B1-c%E1%BA%A7n/","summary":"\u003cp\u003eHầu hết các hướng dẫn đều hiển thị ví dụ tầm thường \u003ccode\u003ewith open(...)\u003c/code\u003e và dừng lại. Đây là ba mẫu mà tôi thực sự sử dụng trong mã thực tế, và chế độ lỗi mà mỗi mẫu ngăn chặn.\u003c/p\u003e\n\u003ch2 id=\"trường-hợp-1-dọn-dẹp-tài-nguyên-và-xử-lý-lỗi\"\u003eTrường hợp 1: Dọn dẹp tài nguyên và xử lý lỗi\u003c/h2\u003e\n\u003cp\u003eKhi bạn có tài nguyên phải dọn dẹp, bất kể điều gì xảy ra:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eDatabaseConnection\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"fm\"\u003e__init__\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003econfig\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econnection\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"fm\"\u003e__enter__\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econnection\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_connection\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfig\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econnection\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"fm\"\u003e__exit__\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eexc_type\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eexc_val\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eexc_tb\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econnection\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econnection\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"tại-sao-điều-này-quan-trọng\"\u003eTại sao điều này quan trọng\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eDọn dẹp tài nguyên ngay cả trong trường hợp ngoại lệ\u003c/li\u003e\n\u003cli\u003eNgăn chặn rò rỉ tài nguyên\u003c/li\u003e\n\u003cli\u003eLàm cho mã đáng tin cậy hơn\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"trường-hợp-2-quản-lý-trạng-thái-tạm-thời\"\u003eTrường hợp 2: Quản lý trạng thái tạm thời\u003c/h2\u003e\n\u003cp\u003eKhi bạn cần thay đổi tạm thời một số trạng thái, sau đó khôi phục nó:\u003c/p\u003e","title":"Python Context Managers: Ba trường hợp bạn thực sự cần"},{"content":"Đầu ra EXPLAIN ANALYZE trông đáng sợ cho đến khi bạn biết ba con số thực sự quan trọng. Đây là thứ tự tôi đọc chúng, và các mẫu chỉ ra các lỗi cụ thể.\nBa con số thực sự quan trọng 1. Tổng thời gian thực thi (Total Execution Time) Total runtime: 1234.567 ms Đây là chỉ số quan trọng nhất. Nếu truy vấn chậm, nó sẽ cho bạn biết ở đây.\n2. Số hàng thực tế vs số hàng ước tính (Actual vs Estimated Rows) Seq Scan on users (cost=0.00..123.45 rows=1000 width=32) (actual time=1.234..567.890 rows=50000 loops=1) Sự khác biệt lớn cho thấy trình lập kế hoạch đã đưa ra giả định sai.\n3. Tỷ lệ trúng bộ đệm (Buffer Hit Ratio) Buffers: shared hit=1000 read=50 Tỷ lệ trúng cao = sử dụng bộ đệm tốt, tỷ lệ trúng thấp = vấn đề I/O đĩa.\nThứ tự đọc Bắt đầu từ dưới - tổng thời gian thực thi Làm việc lên trên - tìm nút có chi phí cao nhất Kiểm tra ước tính hàng - thực tế vs ước tính Xem thống kê bộ đệm - mẫu I/O Mẫu vấn đề phổ biến Quét tuần tự khi nên quét chỉ mục Seq Scan on large_table (cost=1000.00..2000.00 rows=100000 width=32) Giải pháp: Thêm chỉ mục phù hợp\nVòng lồng khi nên kết nối băm Nested Loop (cost=1000.00..100000.00 rows=1000 width=64) -\u0026gt; Seq Scan on users -\u0026gt; Index Scan on orders Giải pháp: Tăng work_mem hoặc viết lại truy vấn\nQuá nhiều bộ đệm không trúng Buffers: shared hit=10 read=1000 Giải pháp: Tăng shared_buffers hoặc cải thiện truy vấn\nSắp xếp tràn ra đĩa Sort Method: external merge Disk: 16384kB Giải pháp: Tăng work_mem\nỨng dụng thực tế Những hiểu biết này giúp tôi xác định và sửa chữa:\nChỉ mục bị thiếu Thứ tự kết nối không hiệu quả Vấn đề thiếu bộ nhớ Bộ đệm không trúng Hãy nhớ: EXPLAIN ANALYZE là trình gỡ lỗi hiệu suất truy vấn của bạn. Học cách đọc nó sẽ tiết kiệm cho bạn vô số giờ đoán mò.\n","permalink":"https://dibi8.com/vi/posts/%C4%91%E1%BB%8Dc-%C4%91%E1%BA%A7u-ra-explain-analyze-trong-postgres-m%C3%A0-kh%C3%B4ng-b%E1%BB%8B-l%E1%BA%A1c/","summary":"\u003cp\u003eĐầu ra EXPLAIN ANALYZE trông đáng sợ cho đến khi bạn biết ba con số thực sự quan trọng. Đây là thứ tự tôi đọc chúng, và các mẫu chỉ ra các lỗi cụ thể.\u003c/p\u003e\n\u003ch2 id=\"ba-con-số-thực-sự-quan-trọng\"\u003eBa con số thực sự quan trọng\u003c/h2\u003e\n\u003ch3 id=\"1-tổng-thời-gian-thực-thi-total-execution-time\"\u003e1. \u003cstrong\u003eTổng thời gian thực thi (Total Execution Time)\u003c/strong\u003e\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTotal runtime: 1234.567 ms\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eĐây là chỉ số quan trọng nhất. Nếu truy vấn chậm, nó sẽ cho bạn biết ở đây.\u003c/p\u003e","title":"Đọc đầu ra EXPLAIN ANALYZE trong Postgres mà không bị lạc"}]