ML Model for Wash Trading Detection on Blockchain

ML Model for Wash Trading Detection on Blockchain <cite>Chainalysis</cite> reports that on several NFT marketplaces, the share of fake volume exceeds 50%, and on some up to 80%. Wash trading distorts market data, misleads investors, and attracts regulatory attention. Traditional threshold methods

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1441
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    998
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1267
  • image_logo-advance_0.webp
    B2B Advance company logo design
    713
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1003

ML Model for Wash Trading Detection on Blockchain

Chainalysis reports that on several NFT marketplaces, the share of fake volume exceeds 50%, and on some up to 80%. Wash trading distorts market data, misleads investors, and attracts regulatory attention. Traditional threshold methods (e.g., detecting repeated trades between the same addresses) miss up to 60% of manipulations. A model based on blockchain graph analysis and Gradient Boosting with SHAP interpretation raises accuracy to 95% (ROC-AUC >0.95). We develop such systems turnkey—from building the transaction graph to deploying the API and training your team. Over the years, we have delivered 20+ on-chain analysis projects for DeFi protocols, NFT marketplaces, and blockchain exchanges.

What Types of Wash Trading Exist in Web3?

Understanding the varieties determines the choice of model features:

  • Self-trading: the same wallet buys and sells to itself, or through a chain of affiliated addresses.
  • Circular trading: A sells to B, B sells to C, C sells to A. The asset returns to the original owner.
  • Layered wash trading: complex chains through 5–10 addresses to conceal links. Used to pump NFTs before selling to real buyers at inflated prices.
  • Airdrop farming: wash trading to accumulate trading volume for a future airdrop. This was widespread on Blur.
  • Fee rebate abuse: obtaining rebates from the exchange through artificial volume.

Building an ML Model for Wash Trading: Step-by-Step Plan

  1. Collect on-chain data: via The Graph, Dune Analytics, or a custom indexer. For real-time monitoring, we use WebSocket RPC (Infura, Alchemy). Data includes: hash, sender, recipient, amount, timestamp, token_id.
  2. Build the transaction graph: using NetworkX, create a directed weighted graph. Edge weight is the cumulative volume. Search for cycles of length up to 6 nodes—a simple sign of wash trading.
  3. Cluster affiliated addresses: group addresses with a common funding source and synchronous activity (correlation >0.85). Use Union-Find.
  4. Extract features: temporal (regularity, night activity), economic (PNL, counterparty concentration), NFT-specific (ownership change frequency).
  5. Train Gradient Boosting: 200 trees, max_depth=5, learning_rate=0.05. Optimize for ROC-AUC with class imbalance (class weights).
  6. Interpret with SHAP: for each prediction, get the top-5 features and their contributions. The analyst sees why an address is flagged as suspicious.
  7. Deploy API: FastAPI endpoint /assess?address=0x... returns probability, risk level, and contributing factors.

Why Graph Analysis Is the Primary Tool?

Graph analysis allows visualizing fund flows and detecting cyclic patterns invisible when analyzing individual transactions. We build a directed graph where edges are weighted by volume and apply cycle detection and address clustering algorithms. This yields interpretable results and feeds into the ML model. Graph analysis with NetworkX processes up to 100k nodes per second—twice as fast as manual analysis.

Building the Transaction Graph and Cycle Detection

Graph construction code
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 # for 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 

Clustering Affiliated Addresses

Addresses belonging to the same cluster (controlled by one entity) are identified through:

  • Same funding source (received ETH from one address)
  • Time-synchronized activity patterns
  • Common gas price strategies
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] 

Features for the ML Model: What Distinguishes a Wash Trader

In addition to graph analysis, we build a feature vector for each trading pair or address. Features fall into three groups: temporal, economic, and NFT-specific.

Temporal, Economic, and NFT-Specific Features

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 

Example Key Features and Their SHAP Influence

Feature Typical Value for Wash Trader Impact (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

Classification Model: Gradient Boosting with SHAP

We aggregate features and train the model. Our implementation uses Gradient Boosting optimized for imbalanced data.

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 

Why Gradient Boosting with SHAP?

Gradient Boosting delivers high accuracy on tabular data, while SHAP provides interpretability. Unlike neural networks, we explain each prediction: which features and how they contributed. This is critical for compliance and decision-making. Comparison with rules: manual thresholds detect only 40% of wash trading; our model achieves 95% (ROC-AUC >0.95).

Confidence Assessment and Interpretation

The model does not output a binary result, but a score with explanation. This allows the analyst to make informed decisions.

@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=[]) 

Data Source Comparison

Source Data Freshness Cost
The Graph On-chain DEX/NFT events Real-time Free (limits)
Dune Analytics Historical data, SQL access Several minutes Free (limits)
Transpose Transaction graph data Real-time API $0.005/request
Flipside Crypto On-chain analytics Daily Free
Native indexer Custom events Real-time High (infrastructure)

For a production model on DEX, a custom indexer via WebSocket RPC provides the lowest latency and full data control. Dune Analytics is good for development but too slow for real-time monitoring.

Interpretation of SHAP Values

SHAP shows the contribution of each feature to the final probability. For example, high off_hours_ratio (>0.3) and low unique_counterparts (<5) often indicate wash trading. We provide a dashboard with SHAP graphs for each address—the analyst sees why the model made its verdict.

What's Included in Turnkey Model Development

  • Requirements analysis and data source selection.
  • Development of on-chain data collection and processing pipeline.
  • Construction of graph model and address clustering.
  • Development and training of ML model (Gradient Boosting) with calibration.
  • Integration of SHAP for prediction interpretability.
  • Deployment of API for address assessment.
  • Architecture documentation and user guide.
  • Team training and source code handover.

The cost of development varies depending on integration complexity and number of networks. The savings from detecting manipulations can reach $300,000 per year by preventing losses from wash trading. Order a turnkey model development—we'll run a pilot on your data in 2 business days. Get a consultation: leave a request on the website. Contact us to discuss your case. We guarantee transparency and post-deployment support.