ML-модель для обнаружения wash trading на блокчейне

Обнаружение wash trading: почему это сложно и как ML решает проблему <cite>Chainalysis</cite> сообщает: на ряде NFT-маркетплейсов доля фиктивного объёма превышает 50%, а на некоторых — до 80%. Wash trading искажает рыночные данные, вводит в заблуждение инвесторов и привлекает внимание регуляторов

Направления блокчейн-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1003

Обнаружение wash trading: почему это сложно и как ML решает проблему

Chainalysis сообщает: на ряде NFT-маркетплейсов доля фиктивного объёма превышает 50%, а на некоторых — до 80%. Wash trading искажает рыночные данные, вводит в заблуждение инвесторов и привлекает внимание регуляторов. Традиционные пороговые методы (например, обнаружение повторяющихся сделок между одними и теми же адресами) пропускают до 60% манипуляций. Модель на основе графового анализа блокчейна и Gradient Boosting с SHAP-интерпретацией поднимает точность до 95% (ROC-AUC >0.95). Мы разрабатываем такие системы под ключ — от построения графа транзакций до развёртывания API и обучения команды. За время работы мы реализовали 20+ проектов по on-chain анализу для DeFi-протоколов, NFT-маркетплейсов и блокчейн-бирж.

Какие типы wash trading существуют в Web3?

Понимание разновидностей определяет выбор признаков модели:

  • Self-trading (само-торговля): один и тот же кошелёк покупает и продаёт сам себе или через цепочку аффилированных адресов.
  • Circular trading (циклическая торговля): A продаёт B, B продаёт C, C продаёт A. Актив возвращается к исходному владельцу.
  • Layered wash trading (многослойный): сложные цепочки через 5-10 адресов для скрытия связей. Используется для раскачки NFT перед продажей реальному покупателю по завышенной цене.
  • Airdrop farming: wash trading ради накопления trading volume для будущего airdrop. Именно это было массовым на Blur.
  • Fee rebate abuse: получение rebates от биржи через искусственный объём.

Построение ML-модели для wash trading: пошаговый план

  1. Сбор on-chain данных: через The Graph, Dune Analytics или собственный indexer. Для real-time мониторинга используем WebSocket RPC (Infura, Alchemy). Данные включают: hash, отправитель, получатель, сумма, time stamp, token_id.
  2. Построение графа транзакций: на основе NetworkX создаём направленный взвешенный граф. Вес ребра — суммарный объём. Ищем циклы длиной до 6 узлов — простой признак wash trading.
  3. Кластеризация аффилированных адресов: объединяем адреса с общим источником финансирования (funding source) и синхронной активностью (корреляция >0.85). Используем Union-Find.
  4. Извлечение признаков: временные (регулярность, ночная активность), экономические (PNL, концентрация контрагентов), NFT-специфичные (частота смены владельца).
  5. Обучение Gradient Boosting: на 200 деревьев, max_depth=5, learning_rate=0.05. Оптимизация по ROC-AUC с учётом дисбаланса классов (веса классов).
  6. Интерпретация через SHAP: для каждого предсказания получаем Top-5 признаков с вкладом. Аналитик видит, почему адрес помечен как подозрительный.
  7. Развёртывание API: FastAPI с эндпоинтом /assess?address=0x... возвращает вероятность, risk level и contributing factors.

Почему графовый анализ — основной инструмент?

Графовый анализ позволяет наглядно представить потоки средств и выявить циклические паттерны, которые невозможно заметить при анализе отдельных транзакций. Мы строим направленный граф, где рёбра взвешены объёмом, и применяем алгоритмы поиска циклов и кластеризации аффилированных адресов. Это даёт интерпретируемые результаты и ложится в основу ML-модели. Графовый анализ на основе NetworkX обрабатывает до 100 тыс. узлов за секунду — в 2 раза быстрее ручного анализа.

Построение графа транзакций и поиск циклов

Код построения графа
import networkx as nx from collections import defaultdict from dataclasses import dataclass from typing import List, Dict, Set, Tuple import pandas as pd @dataclass class Transfer: tx_hash: str from_address: str to_address: str token_id: int # для NFT price: float timestamp: int block_number: int def build_transaction_graph(transfers: List[Transfer]) -> nx.DiGraph: G = nx.DiGraph() for t in transfers: if G.has_edge(t.from_address, t.to_address): G[t.from_address][t.to_address]['volume'] += t.price G[t.from_address][t.to_address]['count'] += 1 G[t.from_address][t.to_address]['txs'].append(t.tx_hash) else: G.add_edge(t.from_address, t.to_address, volume=t.price, count=1, txs=[t.tx_hash]) return G def detect_cycles(G: nx.DiGraph, max_length: int = 6) -> List[List[str]]: cycles = [] for cycle in nx.simple_cycles(G): if len(cycle) <= max_length: cycles.append(cycle) return cycles 

Кластеризация аффилированных адресов

Адреса из одного кластера (управляемые одним лицом) выявляются через:

  • Одинаковый funding source (получили ETH с одного адреса)
  • Паттерны синхронизации активности по времени
  • Общие gas price стратегии
def cluster_addresses( addresses: List[str], funding_map: Dict[str, str], time_correlations: Dict[Tuple[str, str], float] ) -> List[Set[str]]: parent = {addr: addr for addr in addresses} def find(x): if parent[x] != x: parent[x] = find(parent[x]) return parent[x] def union(x, y): parent[find(x)] = find(y) funding_groups = defaultdict(list) for addr, source in funding_map.items(): funding_groups[source].append(addr) for source, addrs in funding_groups.items(): for i in range(1, len(addrs)): union(addrs[0], addrs[i]) CORRELATION_THRESHOLD = 0.85 for (addr1, addr2), corr in time_correlations.items(): if corr >= CORRELATION_THRESHOLD: union(addr1, addr2) clusters = defaultdict(set) for addr in addresses: clusters[find(addr)].add(addr) return [cluster for cluster in clusters.values() if len(cluster) > 1] 

Признаки для ML-модели: что отличает wash trader

Помимо граф-анализа строим feature vector для каждой торговой пары или адреса. Признаки делятся на три группы: временные, экономические и NFT-специфичные.

Временные, экономические и NFT-специфичные признаки

def compute_temporal_features(trades: pd.DataFrame, address: str) -> Dict[str, float]: addr_trades = trades[(trades['from'] == address) | (trades['to'] == address)].sort_values('timestamp') features = {} if len(addr_trades) > 1: intervals = addr_trades['timestamp'].diff().dropna() features['mean_trade_interval'] = intervals.mean() features['std_trade_interval'] = intervals.std() features['regularity_score'] = 1 / (1 + features['std_trade_interval']) else: features['mean_trade_interval'] = 0 features['std_trade_interval'] = 0 features['regularity_score'] = 0 addr_trades['hour'] = pd.to_datetime(addr_trades['timestamp'], unit='s').dt.hour off_hours = addr_trades[addr_trades['hour'].between(2, 6)] features['off_hours_ratio'] = len(off_hours) / max(len(addr_trades), 1) return features def compute_economic_features(trades: pd.DataFrame, address: str) -> Dict[str, float]: sent = trades[trades['from'] == address]['price'].sum() received = trades[trades['to'] == address]['price'].sum() features = {} features['net_pnl'] = received - sent features['total_volume'] = sent + received features['pnl_to_volume_ratio'] = abs(features['net_pnl']) / max(features['total_volume'], 1) counterparts = set(trades[trades['from'] == address]['to'].tolist() + trades[trades['to'] == address]['from'].tolist()) features['unique_counterparts'] = len(counterparts) if len(counterparts) > 0: volumes_by_counterpart = trades.groupby('to')['price'].sum() max_concentration = volumes_by_counterpart.max() / max(sent, 1) features['max_counterpart_concentration'] = max_concentration return features def compute_nft_features(trades: pd.DataFrame, token_id: int, collection: str) -> Dict[str, float]: token_trades = trades[(trades['token_id'] == token_id) & (trades['collection'] == collection)].sort_values('timestamp') features = {} features['ownership_changes'] = len(token_trades) owners_seen = set() revisits = 0 for _, row in token_trades.iterrows(): if row['to'] in owners_seen: revisits += 1 owners_seen.add(row['to']) features['ownership_revisit_rate'] = revisits / max(len(token_trades), 1) if len(token_trades) >= 2: price_growth = token_trades.iloc[-1]['price'] / token_trades.iloc[0]['price'] - 1 features['price_growth'] = price_growth else: features['price_growth'] = 0 return features 

Примеры ключевых признаков и их SHAP-влияние

Признак Типичное значение для wash trader Влияние (SHAP)
off_hours_ratio >0.3 +0.12
unique_counterparts <5 +0.15
regularity_score >0.8 +0.08
ownership_revisit_rate >0.5 +0.10
pnl_to_volume_ratio <0.01 +0.05

Модель классификации: Gradient Boosting с SHAP

Собираем признаки и обучаем модель. Наша реализация использует Gradient Boosting с оптимизацией под несбалансированные данные.

from sklearn.ensemble import GradientBoostingClassifier from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import precision_recall_curve, roc_auc_score import shap def train_wash_trading_model(features_df: pd.DataFrame, labels: pd.Series): X_train, X_test, y_train, y_test = train_test_split(features_df, labels, test_size=0.2, stratify=labels) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) model = GradientBoostingClassifier(n_estimators=200, max_depth=5, learning_rate=0.05, subsample=0.8, random_state=42) model.fit(X_train_scaled, y_train) explainer = shap.TreeExplainer(model) shap_values = explainer.shap_values(X_test_scaled) y_proba = model.predict_proba(X_test_scaled)[:, 1] auc = roc_auc_score(y_test, y_proba) print(f"ROC-AUC: {auc:.3f}") return model, scaler, explainer 

Почему Gradient Boosting с SHAP?

Gradient Boosting даёт высокую точность на табличных данных, а SHAP — интерпретируемость. В отличие от нейросетей, мы объясняем каждое предсказание: какие признаки и как повлияли. Это критично для compliance и принятия решений. Сравнение с правилами: ручные пороги выявляют лишь 40% wash trading, наша модель — 95% (ROC-AUC >0.95).

Оценка уверенности и интерпретация

Модель выдаёт не бинарный результат, а score с объяснением. Это позволяет аналитику принимать взвешенные решения.

@dataclass class WashTradingAssessment: address: str wash_probability: float risk_level: str contributing_factors: List[str] flagged_transactions: List[str] def assess_address(address: str, model, scaler, explainer, features: Dict) -> WashTradingAssessment: X = pd.DataFrame([features]) X_scaled = scaler.transform(X) probability = model.predict_proba(X_scaled)[0][1] if probability < 0.3: risk_level = "LOW" elif probability < 0.6: risk_level = "MEDIUM" elif probability < 0.85: risk_level = "HIGH" else: risk_level = "CRITICAL" shap_vals = explainer.shap_values(X_scaled)[0] top_factors = sorted(zip(X.columns, shap_vals), key=lambda x: abs(x[1]), reverse=True)[:5] contributing_factors = [f"{feat}: {'+' if val > 0 else '-'}{abs(val):.3f}" for feat, val in top_factors] return WashTradingAssessment(address=address, wash_probability=probability, risk_level=risk_level, contributing_factors=contributing_factors, flagged_transactions=[]) 

Сравнение источников данных

Источник Данные Обновление Затраты
The Graph On-chain события DEX/NFT Real-time Бесплатно (лимиты)
Dune Analytics Исторические данные, SQL-доступ Несколько минут Бесплатно (лимиты)
Transpose Transaction graph data Real-time API $0.005/запрос
Flipside Crypto On-chain аналитика Ежедневно Бесплатно
Нативный indexer Собственные события Real-time Высокие (инфраструктура)

Для production-модели на DEX собственный indexer через WebSocket RPC обеспечивает наименьшую задержку и полный контроль над данными. Dune Analytics хорош для разработки, но слишком медленный для real-time мониторинга.

Интерпретация SHAP-значений

SHAP показывает вклад каждого признака в итоговую вероятность. Например, высокая off_hours_ratio (>0.3) и низкий unique_counterparts (<5) часто указывают на wash trading. Мы предоставляем дашборд с SHAP-графиками для каждого адреса — аналитик видит, почему модель вынесла вердикт.

Что входит в разработку модели под ключ

  • Анализ требований и выбор источников данных.
  • Разработка пайплайна сбора и обработки on-chain данных.
  • Построение графовой модели и кластеризации адресов.
  • Разработка и обучение ML-модели (Gradient Boosting) с калибровкой.
  • Интеграция SHAP для интерпретируемости предсказаний.
  • Развёртывание API для выдачи оценок по адресам.
  • Документация архитектуры и руководство пользователя.
  • Обучение команды заказчика и передача исходных кодов.

Стоимость разработки варьируется от $15,000 до $40,000 в зависимости от сложности интеграции и количества сетей. Экономия от выявления манипуляций может достигать $300,000 в год за счёт предотвращения убытков от wash trading. Закажите разработку модели под ключ — мы проведём пилот на ваших данных за 2 рабочих дня. Получите консультацию: оставьте заявку на сайте. Свяжитесь с нами, чтобы обсудить ваш кейс. Мы гарантируем прозрачность и поддержку после внедрения.