rmi-backend/app/exchange_flow_analyzer.py

335 lines
10 KiB
Python

"""
Exchange Flow Analyzer
======================
Liquidity pool monitoring and exchange flow analysis.
Tracks:
- Liquidity depth across DEXs
- Exchange deposit/withdrawal flows
- Whale movement tracking
- Risk scoring based on pool participation
"""
import logging
from dataclasses import dataclass
from datetime import datetime
from typing import Any
try:
from web3 import Web3
WEB3_AVAILABLE = True
except ImportError:
WEB3_AVAILABLE = False
logger = logging.getLogger(__name__)
logger.warning("web3.py not available, using stub mode")
logger = logging.getLogger(__name__)
# ─── LIQUIDITY POOL CONFIGURATIONS ────────────────────────────────
DEX_CONFIGS = {
"uniswap_v3": {
"factory": "0x1F98431c8aD98523631AE4a59f267346ea31F984",
"router": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Te7x",
"chain": "ethereum",
},
"uniswap_v2": {
"factory": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
"router": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
"chain": "ethereum",
},
"curve": {
"factory": "0xB9fEF8811f4367e123416528724181538c511156",
"chain": "ethereum",
},
"curve_v2": {
"factory": "0x0000000000000000000000000000000000000000", # Placeholder
"chain": "ethereum",
},
"balancer": {
"factory": "0xBA12222222228d8Ba445958a75a0704d566BF233",
"chain": "ethereum",
},
"sushiswap": {
"factory": "0xC0AEe478e3658e2610c5F7A4A2E1777c9Ef98307",
"router": "0x1b02dA8Cb0d8576294F01f02377Ce1978d58eF5C",
"chain": "ethereum",
},
"base_uniswap_v3": {
"factory": "0x33128a8fC13f53869D7171190F65e2B9B2E1F36F",
"router": "0x546C87632b88B584dE4106134983e4783719B8D4",
"chain": "base",
},
"base_sushiswap": {
"factory": "0xf551e3c70153905c553589509f14dea4945ca878",
"router": "0x66343d76f738ef4be3b99d3679a4389543a8393e",
"chain": "base",
},
"arbitrum_uniswap_v3": {
"factory": "0x1F98431c8aD98523631AE4a59f267346ea31F984",
"router": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Te7x",
"chain": "arbitrum",
},
"optimism_uniswap_v3": {
"factory": "0x1F98431c8aD98523631AE4a59f267346ea31F984",
"router": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Te7x",
"chain": "optimism",
},
}
# ─── POOL DATA MODELS ─────────────────────────────────────────────
@dataclass
class LiquidityPool:
"""Liquidity pool data."""
pool_address: str
token0: str
token1: str
token0_reserve: int # wei
token1_reserve: int # wei
total_liquidity_usd: float
volume_24h_usd: float
fees_24h_usd: float
tvl_usd: float
dune_analytics_id: str | None = None
dex: str = "unknown"
@dataclass
class ExchangeFlow:
"""Exchange deposit/withdrawal flow."""
address: str
exchange: str
entry_time: str
exit_time: str | None = None
entry_amount_usd: float = 0
exit_amount_usd: float = 0
profit_usd: float = 0
risk_score: int = 0
@dataclass
class WhaleMovement:
"""Whale movement tracking."""
address: str
amount: int # wei
token: str
from_exchange: str | None = None
to_exchange: str | None = None
timestamp: str = ""
tx_hash: str | None = None
# ─── EXCHANGE FLOW ANALYZER ───────────────────────────────────────
class ExchangeFlowAnalyzer:
"""Analyzer for exchange flows and liquidity pools."""
def __init__(self, web3_client=None):
self.web3 = web3_client
self.pools: dict[str, LiquidityPool] = {}
self.flows: list[ExchangeFlow] = []
self.whales: list[WhaleMovement] = []
self.exchange_ratelimits: dict[str, int] = {
"binance": 50000,
"coinbase": 100000,
"kraken": 25000,
"kucoin": 30000,
"ftx": 10000,
}
def get_liquidity_pool(self, pool_address: str, chain: str = "base") -> LiquidityPool | None:
"""
Get liquidity pool data.
Args:
pool_address: Pool contract address
chain: Blockchain network
Returns:
LiquidityPool data or None
"""
if not WEB3_AVAILABLE and not self.web3:
return self._stub_pool(pool_address)
try:
w3 = self.web3 or Web3(Web3.HTTPProvider(self._get_rpc(chain)))
# Get pool reserve data (Simplified - real implementation would use pool ABI)
reserve0 = w3.eth.get_balance(pool_address) if w3.eth.get_code(pool_address) else 0
reserve1 = 0 # Would fetch from other token
return LiquidityPool(
pool_address=pool_address,
token0="0x0000000000000000000000000000000000000000",
token1="0x0000000000000000000000000000000000000000",
token0_reserve=reserve0,
token1_reserve=reserve1,
total_liquidity_usd=0,
volume_24h_usd=0,
fees_24h_usd=0,
tvl_usd=0,
dex=self._detect_dex(pool_address, chain),
)
except Exception as e:
logger.error(f"Error fetching pool {pool_address}: {e}")
return self._stub_pool(pool_address)
def _get_rpc(self, chain: str) -> str:
"""Get RPC URL for chain."""
rpcs = {
"ethereum": "https://ethereum-rpc.publicnode.com",
"base": "https://base-rpc.publicnode.com",
"arbitrum": "https://arbitrum-rpc.publicnode.com",
"optimism": "https://optimism-rpc.publicnode.com",
}
return rpcs.get(chain, rpcs["ethereum"])
def _detect_dex(self, address: str, chain: str) -> str:
"""Detect which DEX a pool belongs to."""
address_lower = address.lower()
dex_configs = DEX_CONFIGS.get(chain, {})
for dex, config in dex_configs.items():
factory = config.get("factory", "").lower()
if factory and address_lower.startswith(factory[:10]):
return dex
# Fallback detection
if "uniswap" in address_lower or "1f984" in address_lower:
return "uniswap_v3"
if "curve" in address_lower:
return "curve"
if "balancer" in address_lower:
return "balancer"
if "sushiswap" in address_lower:
return "sushiswap"
return "unknown"
def _stub_pool(self, address: str) -> LiquidityPool:
"""Stub pool data when Web3 unavailable."""
return LiquidityPool(
pool_address=address,
token0="0x",
token1="0x",
token0_reserve=0,
token1_reserve=0,
total_liquidity_usd=0,
volume_24h_usd=0,
fees_24h_usd=0,
tvl_usd=0,
dex="stub",
)
def analyze_entity_flows(self, address: str, chain: str = "base") -> dict[str, Any]:
"""
Analyze exchange flows for an entity (wallet).
Args:
address: Entity address
chain: Blockchain network
Returns:
Flow analysis results
"""
result = {
"address": address,
"chain": chain,
"exchanges": [],
"total_inflow_usd": 0,
"total_outflow_usd": 0,
"risk_score": 0,
"last_updated": datetime.utcnow().isoformat(),
}
# Simplified analysis - real implementation would track actual flows
result["exchanges"].append(
{
"exchange": "binance",
"inflow_usd": 0,
"outflow_usd": 0,
"holdings_usd": 0,
"risk_score": 50, # Medium risk
}
)
return result
def compare_flows(self, addresses: list[str], chain: str = "base") -> dict[str, Any]:
"""Compare flows between multiple addresses."""
result = {
"chain": chain,
"addresses": {},
"correlations": [],
"last_updated": datetime.utcnow().isoformat(),
}
for addr in addresses:
flows = self.analyze_entity_flows(addr, chain)
result["addresses"][addr] = flows
# Detect correlations (simplified)
result["correlations"] = []
return result
def get_reserves(self, pool_address: str, chain: str = "base") -> dict[str, int]:
"""
Get current pool reserves.
Args:
pool_address: Pool address
chain: Blockchain network
Returns:
Reserves dict
"""
pool = self.get_liquidity_pool(pool_address, chain)
return {
"reserve0": pool.token0_reserve,
"reserve1": pool.token1_reserve,
"total_liquidity": pool.total_liquidity_usd,
}
def track_whale_movements(self, address: str, threshold_usd: float = 100000) -> list[WhaleMovement]:
"""
Track large movements for an address.
Args:
address: Wallet address
threshold_usd: Minimum transaction value in USD
Returns:
List of whale movements
"""
# Simplified - real implementation would scan blocks
return []
# ─── GLOBAL SINGLETON ─────────────────────────────────────────────
_analyzer = ExchangeFlowAnalyzer()
def analyze_entity_flows(address: str, chain: str = "base") -> dict[str, Any]:
"""Analyze entity exchange flows."""
return _analyzer.analyze_entity_flows(address, chain)
def compare_flows(addresses: list[str], chain: str = "base") -> dict[str, Any]:
"""Compare exchange flows between addresses."""
return _analyzer.compare_flows(addresses, chain)
def get_reserves(pool_address: str, chain: str = "base") -> dict[str, int]:
"""Get pool reserves."""
return _analyzer.get_reserves(pool_address, chain)