1
This commit is contained in:
parent
195e3adb0e
commit
1a450e59d1
@ -48,6 +48,91 @@ ERC20_SYMBOL_SELECTOR = "0x95d89b41"
|
||||
ERC20_NAME_SELECTOR = "0x06fdde03"
|
||||
ERC20_DECIMALS_SELECTOR = "0x313ce567"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Known CEX hot/deposit wallet addresses (lowercase).
|
||||
# Sources: Etherscan/BscScan labeled addresses, Arkham, Nansen public tags.
|
||||
# Used to classify transfer direction: inflow (to CEX) vs outflow (from CEX).
|
||||
# ---------------------------------------------------------------------------
|
||||
_CEX_ADDRESSES: set[str] = {
|
||||
# Binance
|
||||
"0x28c6c06298d514db089934071355e5743bf21d60",
|
||||
"0x21a31ee1afc51d94c2efccaa2092ad1028285549",
|
||||
"0xdfd5293d8e347dfe59e90efd55b2956a1343963d",
|
||||
"0x56eddb7aa87536c09ccc2793473599fd21a8b17f",
|
||||
"0x9696f59e4d72e237be84ffd425dcad154bf96976",
|
||||
"0xf977814e90da44bfa03b6295a0616a897441acec",
|
||||
"0x8894e0a0c962cb723c1ef8a1b67f07aa277d42ad",
|
||||
"0xe2fc31f816a9b94326492132018c3aecc4a93ae1",
|
||||
"0x3c783c21a0383057d128bae431894a5c19f9cf06",
|
||||
"0xb38e8c17e38363af6ebdcb3dae12e0243582891d",
|
||||
"0x5a52e96bacdabb82fd05763e25335261b270efcb",
|
||||
"0x835678a611b28684005a5e2233695fb6cbbb00a4",
|
||||
# OKX
|
||||
"0x6cc5f688a315f3dc28a7781717a9a798a59fda7b",
|
||||
"0x236f9f97e0e62388479bf9e5ba4889e46b0273c3",
|
||||
"0xa7efae728d2936e78bda97dc267687568dd593f3",
|
||||
"0x98ec059dc3adfbdd63429454aeb0c990fba4a128",
|
||||
"0x6fb624b48d9299674022a23d92515e76ba880113",
|
||||
# Bybit
|
||||
"0xf89d7b9c864f589bbf53a82105107622b35eaa40",
|
||||
"0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4",
|
||||
# Coinbase
|
||||
"0x71660c4005ba85c37ccec55d0c4493e66fe775d3",
|
||||
"0x503828976d22510aad0201ac7ec88293211d23da",
|
||||
"0xddfabcdc4d8ffc6d5beaf154f18b778f892a0740",
|
||||
"0x3cd751e6b0078be393132286c442345e68ff0aaa",
|
||||
"0xb5d85cbf7cb3ee0d56b3bb207d5fc4b82f43f511",
|
||||
"0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43",
|
||||
# Kraken
|
||||
"0x2910543af39aba0cd09dbb2d50200b3e800a63d2",
|
||||
"0x267be1c1d684f78cb4f6a176c4911b741e4ffdc0",
|
||||
# KuCoin
|
||||
"0xd6216fc19db775df9774a6e33526131da7d19a2c",
|
||||
"0xf16e9b0d03470827a95cdfd0cb8a8a3b46969b91",
|
||||
"0x738cf6903e6c4e699d1c2dd9ab8b67fcdb3121ea",
|
||||
# Gate.io
|
||||
"0x0d0707963952f2fba59dd06f2b425ace40b492fe",
|
||||
"0x1c4b70a3968436b9a0a9cf5205c787eb81bb558c",
|
||||
# Huobi / HTX
|
||||
"0xab5c66752a9e8167967685f1450532fb96d5d24f",
|
||||
"0x6748f50f686bfbca6fe8ad62b22228b87f31ff2b",
|
||||
"0xfdb16996831753d5331ff813c29a93c76834a0ad",
|
||||
"0x46340b20830761efd32832a74d7169b29feb9758",
|
||||
# Bitfinex
|
||||
"0x876eabf441b2ee5b5b0554fd502a8e0600950cfa",
|
||||
"0x742d35cc6634c0532925a3b844bc9e7595f2bd3e",
|
||||
# Crypto.com
|
||||
"0x6262998ced04146fa42253a5c0af90ca02dfd2a3",
|
||||
"0x46340b20830761efd32832a74d7169b29feb9758",
|
||||
# MEXC
|
||||
"0x3cc936b795a188f0e246cbb2d74c5bd190aecf18",
|
||||
# Upbit
|
||||
"0x5e032243d507c743b061ef27c9169ae92ed40ec0",
|
||||
}
|
||||
|
||||
|
||||
def is_cex_address(address: str) -> bool:
|
||||
"""Check if an address belongs to a known centralized exchange."""
|
||||
return str(address or "").lower().strip() in _CEX_ADDRESSES
|
||||
|
||||
|
||||
def classify_transfer_signal(from_addr: str, to_addr: str) -> tuple[str, str]:
|
||||
"""Classify a transfer's signal code and direction based on CEX address labels.
|
||||
|
||||
Returns (signal_code, direction):
|
||||
- to CEX → ("exchange_inflow_risk", "risk") — likely selling
|
||||
- from CEX → ("exchange_outflow", "positive") — likely accumulating
|
||||
- neither → ("whale_accumulation", "positive") — large wallet-to-wallet move
|
||||
"""
|
||||
to_is_cex = is_cex_address(to_addr)
|
||||
from_is_cex = is_cex_address(from_addr)
|
||||
if to_is_cex and not from_is_cex:
|
||||
return "exchange_inflow_risk", "risk"
|
||||
if from_is_cex and not to_is_cex:
|
||||
return "exchange_outflow", "positive"
|
||||
# Both CEX (internal transfer) or neither (wallet-to-wallet whale move)
|
||||
return "whale_accumulation", "positive"
|
||||
|
||||
|
||||
def _env_bool(name, default=False):
|
||||
value = os.getenv(name)
|
||||
@ -454,22 +539,37 @@ def _event_from_evm_transfer(log, mapping, cfg=None, source="nodereal"):
|
||||
return None
|
||||
chain = str(mapping.get("chain") or "").lower()
|
||||
tx_hash = str(log.get("transactionHash") or "").strip()
|
||||
from_addr = _topic_to_address(topics[1])
|
||||
to_addr = _topic_to_address(topics[2])
|
||||
sig_code, direction = classify_transfer_signal(from_addr, to_addr)
|
||||
severity = "RISK" if direction == "risk" else "A"
|
||||
confidence = 80 if direction == "risk" else 76
|
||||
# Descriptive labels based on classification
|
||||
if sig_code == "exchange_inflow_risk":
|
||||
wallet_label = "CEX 充值地址"
|
||||
counterparty_label = "发送方 " + _short_addr(from_addr)
|
||||
elif sig_code == "exchange_outflow":
|
||||
wallet_label = "接收钱包 " + _short_addr(to_addr)
|
||||
counterparty_label = "CEX 提币地址"
|
||||
else:
|
||||
wallet_label = "EVM 接收地址"
|
||||
counterparty_label = "EVM 发送地址 " + _short_addr(from_addr)
|
||||
return {
|
||||
"chain": chain,
|
||||
"symbol": mapping.get("symbol"),
|
||||
"contract_address": mapping.get("contract_address") or "",
|
||||
"event_type": "token_transfer",
|
||||
"signal_code": "whale_accumulation",
|
||||
"signal_label": signal_label("whale_accumulation"),
|
||||
"direction": "positive",
|
||||
"signal_code": sig_code,
|
||||
"signal_label": signal_label(sig_code),
|
||||
"direction": direction,
|
||||
"value_usd": value_usd,
|
||||
"amount": amount,
|
||||
"tx_hash": tx_hash,
|
||||
"wallet_address": _topic_to_address(topics[2]),
|
||||
"wallet_label": "EVM 接收地址",
|
||||
"counterparty_label": "EVM 发送地址 " + _short_addr(_topic_to_address(topics[1])),
|
||||
"confidence": 76,
|
||||
"severity": "A",
|
||||
"wallet_address": to_addr,
|
||||
"wallet_label": wallet_label,
|
||||
"counterparty_label": counterparty_label,
|
||||
"confidence": confidence,
|
||||
"severity": severity,
|
||||
"detected_at": _now().isoformat(timespec="seconds"),
|
||||
"source": source,
|
||||
"url": _chain_explorer_tx_url(chain, tx_hash),
|
||||
|
||||
@ -624,14 +624,18 @@ def _apply_daily_factor_weight_governance():
|
||||
new_weight = old_weight
|
||||
action = ""
|
||||
|
||||
if total >= kill_min_samples and hit_rate < kill_hit_rate:
|
||||
# Expectancy-first governance: avg_pnl is the per-trade expectancy and the
|
||||
# source of truth for profitability. hit_rate alone must not kill or demote
|
||||
# a low-hit-rate but high-payoff signal (typical of breakout/trend setups),
|
||||
# nor promote a high-hit-rate but net-losing one.
|
||||
if total >= kill_min_samples and avg_pnl <= 0 and hit_rate < kill_hit_rate:
|
||||
new_weight = 0.0
|
||||
action = "淘汰"
|
||||
elif hit_rate < warn_hit_rate or avg_pnl <= -3:
|
||||
elif avg_pnl <= -3 or (avg_pnl <= 0 and hit_rate < warn_hit_rate):
|
||||
new_weight = round(max(0.0, old_weight * 0.5), 3)
|
||||
action = "降权"
|
||||
elif hit_rate >= 55 and avg_pnl > 0:
|
||||
target = round(min(4.0, max(old_weight, hit_rate / 50 * base)), 3)
|
||||
elif avg_pnl > 0 and (hit_rate >= 55 or avg_pnl >= 2):
|
||||
target = round(min(4.0, max(old_weight, (hit_rate / 50) * base, (1 + avg_pnl / 5) * base)), 3)
|
||||
if target > old_weight:
|
||||
new_weight = target
|
||||
action = "升权"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user