
Finding Arbitrage Opportunities
- 87 installs
- 2.6k repo stars
- Updated August 5, 2026
- jeremylongshore/claude-code-plugins-plus-skills
Scan CEX, DEX, and cross-chain crypto markets for price spreads and calculate net arbitrage profit after fees.
About
Aggregates crypto prices across exchanges and chains to detect direct, triangular, and cross-chain arbitrage opportunities with post-fee profit estimates. A developer uses it to scan for and monitor trading spreads.
- Scans CEX, DEX, and cross-chain markets via a Python script
- Computes net profit after fees with risk and confidence levels
Finding Arbitrage Opportunities by the numbers
- 87 all-time installs (skills.sh)
- Ranked #534 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/jeremylongshore/claude-code-plugins-plus-skills --skill finding-arbitrage-opportunitiesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 87 |
|---|---|
| repo stars | ★ 2.6k |
| Last updated | August 5, 2026 |
| Repository | jeremylongshore/claude-code-plugins-plus-skills ↗ |
What it does
Scan CEX, DEX, and cross-chain crypto markets for price spreads and calculate net arbitrage profit after fees.
Files
Finding Arbitrage Opportunities
Overview
Detect and analyze arbitrage opportunities across cryptocurrency exchanges and DeFi protocols. Aggregates prices from CEX and DEX sources, calculates net profit after fees, and identifies direct, triangular, and cross-chain arbitrage paths.
Prerequisites
- Python 3.9+ with
httpx,rich, andnetworkxpackages - Internet access for API calls (no API keys required for basic use)
- Optional: Exchange API keys for real-time order book access
- Understanding of arbitrage concepts and trading fees
Instructions
1. Quick spread scan on a specific pair:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py scan ETH USDCShows current prices per exchange, spread %, estimated profit after fees, and recommended action.
2. Multi-exchange comparison across specific exchanges:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py scan ETH USDC \
--exchanges binance,coinbase,kraken,kucoin,okx3. DEX price comparison across decentralized exchanges:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py scan ETH USDC --dex-onlyCompares Uniswap V3, SushiSwap, Curve, Balancer with gas cost estimates.
4. Triangular arbitrage discovery within a single exchange:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py triangular binance --min-profit 0.55. Cross-chain opportunities across different blockchains:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py cross-chain USDC \
--chains ethereum,polygon,arbitrum6. Real-time monitoring with threshold alerts:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py monitor ETH USDC \
--threshold 0.5 --interval 57. Export opportunities for bot integration:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py scan ETH USDC --output json > opportunities.jsonOutput
- Quick mode (default): Best opportunity with profit estimate, buy/sell recommendation, risk level
- Detailed mode (
--detailed): All exchange prices, fee breakdown, slippage estimates, historical spread context - Monitor mode: Real-time updates with threshold alerts and trend indicators
See ${CLAUDE_SKILL_DIR}/references/implementation.md for exchange fee tables and output format examples.
Error Handling
| Error | Cause | Fix |
|---|---|---|
| Rate limited | Too many API requests | Reduce polling frequency or add API key |
| Stale prices | Data older than 10s | Flagged with warning; retry |
| No spread | Efficient market pricing | Normal condition; try different pairs |
| Insufficient liquidity | Trade exceeds order book depth | Reduce trade size |
Examples
Quick ETH/USDC spread scan - Find best buy/sell across all CEX exchanges:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py scan ETH USDCSample detection output:
ARB OPPORTUNITY: ETH/USDC
Buy: Binance @ $3,198.50 | Sell: Coinbase @ $3,214.20
Spread: 0.49% | Net Profit (after fees): 0.29% ($9.27 per ETH)
Risk: LOW | Confidence: HIGH | Window: ~30sTriangular arb on Binance - Discover circular paths with minimum 0.5% net profit:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py triangular binance --min-profit 0.5Cross-chain USDC opportunities - Compare stablecoin prices across L1/L2 chains:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py cross-chain USDC --chains ethereum,polygon,arbitrumCalculate exact profit - Detailed fee breakdown for a specific trade:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py calc \
--buy-exchange binance --sell-exchange coinbase --pair ETH/USDC --amount 10 # 10 = trade size in ETHResources
- CoinGecko API - Free price data
- CCXT Library - Unified exchange API
- Uniswap Subgraph - DEX data
${CLAUDE_SKILL_DIR}/references/implementation.md- Exchange fee tables, configuration, advanced arbitrage types, disclaimer
ARD: Finding Arbitrage Opportunities
Part of Tons of Skills by Intent Solutions | jeremylongshore.com
Architecture Pattern
Pattern: Multi-Source Aggregation + Graph Analysis
This skill aggregates price data from multiple sources (CEX APIs, DEX subgraphs, on-chain oracles) and applies graph algorithms to detect arbitrage paths. The architecture separates data fetching, opportunity detection, and profit calculation into independent components.
Workflow
┌─────────────────────────────────────────────────────────────────────┐
│ ARBITRAGE OPPORTUNITY FINDER │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ CEX APIs │ │ DEX Subgraph │ │ On-Chain │ │
│ │ (CCXT) │ │ (GraphQL) │ │ (RPC) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PRICE AGGREGATOR │ │
│ │ • Normalize formats (bid/ask/mid) │ │
│ │ • Timestamp validation │ │
│ │ • Source reliability scoring │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Direct │ │ Triangular │ │ Cross- │ │
│ │ Scanner │ │ Finder │ │ Chain │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PROFIT CALCULATOR │ │
│ │ • Trading fees (maker/taker) │ │
│ │ • Swap fees (DEX-specific) │ │
│ │ • Gas costs (network-specific) │ │
│ │ • Slippage estimates (size-based) │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ OPPORTUNITY RANKER │ │
│ │ • Net profit calculation │ │
│ │ • Risk scoring │ │
│ │ • Execution complexity │ │
│ │ • Time sensitivity │ │
│ └──────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ OUTPUT │ │
│ │ • Console tables with color-coded profits │ │
│ │ • JSON for bot integration │ │
│ │ • Alerts for threshold breaches │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘Progressive Disclosure Strategy
Level 1: Quick Scan (Default)
- Single pair, top exchanges
- Basic spread display
- Simple profit estimate
Level 2: Detailed Analysis (--detailed)
- All available exchanges
- Fee breakdown
- Slippage estimates
- Risk indicators
Level 3: Full Discovery (--full)
- Triangular path discovery
- Cross-chain comparison
- Historical spread context
- Execution recommendations
Tool Permission Strategy
allowed-tools: Read, Write, Edit, Grep, Glob, Bash(crypto:arbitrage-*)
# Scoped bash for:
# - arbitrage-scan: Run opportunity scanner
# - arbitrage-monitor: Real-time monitoring
# - arbitrage-calc: Profit calculationsDirectory Structure
plugins/crypto/arbitrage-opportunity-finder/
├── skills/
│ └── finding-arbitrage-opportunities/
│ ├── SKILL.md # Core instructions
│ ├── PRD.md # Product requirements
│ ├── ARD.md # Architecture (this file)
│ ├── scripts/
│ │ ├── arb_finder.py # Main CLI entry point
│ │ ├── price_fetcher.py # Multi-source price aggregation
│ │ ├── opportunity_scanner.py # Direct spread detection
│ │ ├── triangular_finder.py # Graph-based path finder
│ │ ├── profit_calculator.py # Cost-aware profit calculation
│ │ └── formatters.py # Output formatting
│ ├── config/
│ │ └── settings.yaml # Exchange and API config
│ └── references/
│ ├── errors.md # Error handling
│ └── examples.md # Usage examplesAPI Integration Architecture
Price Data Sources
┌─────────────────────────────────────────────────────────────────┐
│ DATA SOURCE HIERARCHY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PRIMARY: Direct Exchange APIs (via CCXT) │
│ ├── Binance, Coinbase, Kraken, KuCoin, OKX │
│ ├── Real-time order book data │
│ └── Lowest latency, highest accuracy │
│ │
│ SECONDARY: DEX Subgraphs (GraphQL) │
│ ├── Uniswap, SushiSwap, Curve, Balancer │
│ ├── Pool reserve data for price calculation │
│ └── ~1-3 second latency │
│ │
│ TERTIARY: Aggregators (REST) │
│ ├── CoinGecko, CoinMarketCap │
│ ├── Pre-aggregated prices (may be delayed) │
│ └── Good for overview, not precision trading │
│ │
│ FALLBACK: On-Chain Oracles │
│ ├── Chainlink price feeds │
│ └── Most reliable but least granular │
│ │
└─────────────────────────────────────────────────────────────────┘Fee Structure Database
| Venue Type | Fee Model | Typical Range |
|---|---|---|
| CEX (Spot) | Maker/Taker | 0.02% - 0.10% |
| Uniswap V3 | Pool Fee | 0.01% - 1.00% |
| SushiSwap | Fixed | 0.30% |
| Curve | Dynamic | 0.01% - 0.04% |
| Balancer | Pool-specific | 0.01% - 10% |
Data Flow Architecture
Input Processing
User Request
│
▼
┌─────────────────────┐
│ Parse Arguments │
│ • token_pair │
│ • exchanges │
│ • threshold │
│ • mode │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Validate Inputs │
│ • Token exists │
│ • Exchange valid │
│ • Threshold range │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Load Config │
│ • API endpoints │
│ • Fee schedules │
│ • Gas prices │
└─────────────────────┘Opportunity Detection
Price Data
│
▼
┌─────────────────────┐
│ Build Price Matrix │
│ exchange × pair │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Calculate Spreads │
│ buy_price[A] vs │
│ sell_price[B] │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Apply Costs │
│ - Trading fees │
│ - Withdrawal fees │
│ - Gas (if DEX) │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Filter & Rank │
│ profit > threshold │
└─────────────────────┘Error Handling Strategy
| Error Type | Detection | Response |
|---|---|---|
| API Rate Limit | HTTP 429 | Exponential backoff, switch source |
| Stale Data | Timestamp > 10s | Warning, mark unreliable |
| Exchange Offline | Connection timeout | Skip, use alternatives |
| Invalid Pair | No price data | Log, suggest alternatives |
| Slippage Exceeded | Size > liquidity | Reduce size or flag |
Composability & Stacking
Standalone Use
python arb_finder.py scan ETH USDC --exchanges binance,coinbase,krakenPipeline Integration
python arb_finder.py scan ETH USDC --output json | jq '.opportunities[0]'With Flash Loan Simulator
# Find opportunity, then simulate flash loan execution
python arb_finder.py scan ETH USDC --output json | \
python ../flash-loan-simulator/scripts/flash_simulator.py arbitrage --stdinContinuous Monitoring
python arb_finder.py monitor ETH USDC --threshold 0.5 --interval 5Performance & Scalability
Latency Targets
| Operation | Target | Notes |
|---|---|---|
| Single exchange fetch | < 500ms | Direct API |
| Multi-exchange scan (5) | < 2s | Parallel requests |
| Triangular path (100 pairs) | < 3s | Graph algorithm |
| Full scan (all exchanges) | < 10s | Rate-limited |
Optimization Strategies
1. Parallel Fetching: Concurrent API calls to multiple exchanges 2. Caching: Short-lived cache (5s) for repeated queries 3. Incremental Updates: Only re-fetch changed prices in monitor mode 4. Graph Pruning: Exclude low-liquidity pairs from triangular search
Security & Compliance
API Key Handling
- Keys loaded from environment variables or encrypted config
- Never logged or displayed in output
- Rate limits respected to avoid bans
Data Privacy
- No user data collected or stored
- Price data is public market information
- No personally identifiable information
Risk Warnings
- All opportunities marked as estimates
- Execution risk disclaimers
- Educational purpose disclaimer
Testing Strategy
Unit Tests
- Price normalization
- Fee calculations
- Spread computations
- Path finding algorithms
Integration Tests
- API connectivity (with mocks)
- Multi-source aggregation
- End-to-end opportunity detection
Manual Validation
- Compare detected spreads with exchange UIs
- Verify profit calculations with manual trades
- Test rate limit handling
# Arbitrage Opportunity Finder Configuration
# ============================================
# Configure data sources, exchanges, and scanning parameters.
# ─────────────────────────────────────────────────────────────────
# Data Sources
# ─────────────────────────────────────────────────────────────────
# Primary aggregators and APIs for price data.
data_sources:
coingecko:
enabled: true
base_url: "https://api.coingecko.com/api/v3"
rate_limit: 10 # calls per minute (free tier)
api_key: null # Optional: for higher rate limits
coinmarketcap:
enabled: false
base_url: "https://pro-api.coinmarketcap.com/v1"
api_key: null # Required for access
# ─────────────────────────────────────────────────────────────────
# Centralized Exchanges (CEX)
# ─────────────────────────────────────────────────────────────────
# Fee structures and API configurations for CEX.
cex:
binance:
enabled: true
name: "Binance"
maker_fee: 0.0010 # 0.10%
taker_fee: 0.0010 # 0.10%
withdrawal_fee: 0.0005
api_base: "https://api.binance.com"
api_key: null # Optional for public data
api_secret: null
coinbase:
enabled: true
name: "Coinbase"
maker_fee: 0.0040 # 0.40%
taker_fee: 0.0060 # 0.60%
withdrawal_fee: 0.0000 # Free for most assets
api_base: "https://api.coinbase.com"
api_key: null
api_secret: null
kraken:
enabled: true
name: "Kraken"
maker_fee: 0.0016 # 0.16%
taker_fee: 0.0026 # 0.26%
withdrawal_fee: 0.0010
api_base: "https://api.kraken.com"
api_key: null
api_secret: null
kucoin:
enabled: true
name: "KuCoin"
maker_fee: 0.0010
taker_fee: 0.0010
withdrawal_fee: 0.0003
api_base: "https://api.kucoin.com"
api_key: null
api_secret: null
okx:
enabled: true
name: "OKX"
maker_fee: 0.0008 # 0.08%
taker_fee: 0.0010 # 0.10%
withdrawal_fee: 0.0004
api_base: "https://www.okx.com"
api_key: null
api_secret: null
# ─────────────────────────────────────────────────────────────────
# Decentralized Exchanges (DEX)
# ─────────────────────────────────────────────────────────────────
# DEX configurations with subgraph endpoints.
dex:
uniswap:
enabled: true
name: "Uniswap V3"
fee_tiers: [0.0001, 0.0005, 0.003, 0.01] # 0.01%, 0.05%, 0.3%, 1%
default_fee: 0.003
gas_estimate: 150000
subgraph:
ethereum: "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
polygon: "https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-polygon"
arbitrum: "https://api.thegraph.com/subgraphs/name/ianlapham/arbitrum-dev"
chains: [ethereum, polygon, arbitrum, optimism]
sushiswap:
enabled: true
name: "SushiSwap"
fee: 0.003 # 0.30% fixed
gas_estimate: 150000
subgraph:
ethereum: "https://api.thegraph.com/subgraphs/name/sushiswap/exchange"
chains: [ethereum, polygon, arbitrum]
curve:
enabled: true
name: "Curve"
fee: 0.0004 # 0.04% for stable pools
gas_estimate: 200000
chains: [ethereum, polygon, arbitrum]
balancer:
enabled: true
name: "Balancer V2"
fee_range: [0.0001, 0.10] # Pool-dependent
default_fee: 0.001
gas_estimate: 180000
chains: [ethereum, polygon, arbitrum]
# ─────────────────────────────────────────────────────────────────
# Scanning Parameters
# ─────────────────────────────────────────────────────────────────
# Default parameters for opportunity scanning.
scanning:
# Minimum profit threshold to report
min_profit_pct: 0.1 # 0.1%
# Maximum data staleness (seconds)
max_staleness: 30
# Default monitoring interval
monitor_interval: 5 # seconds
# Alert threshold
alert_threshold: 0.3 # 0.3%
# Rate limit handling
request_delay_ms: 100
backoff_multiplier: 2.0
max_retries: 3
# ─────────────────────────────────────────────────────────────────
# Market Parameters
# ─────────────────────────────────────────────────────────────────
# Default market assumptions for calculations.
market:
eth_price_usd: 2500.0
btc_price_usd: 68000.0
gas_price_gwei: 30.0
default_slippage_pct: 0.1
# ─────────────────────────────────────────────────────────────────
# Triangular Arbitrage
# ─────────────────────────────────────────────────────────────────
# Settings for triangular path finding.
triangular:
# Maximum path length
max_hops: 3
# Minimum liquidity per pair (USD)
min_liquidity: 10000
# Minimum profit to report
min_profit_pct: 0.1
# Maximum paths to return
max_results: 100
# ─────────────────────────────────────────────────────────────────
# Output Settings
# ─────────────────────────────────────────────────────────────────
output:
default_format: "console" # console, json
console_width: 70
show_all_quotes: true
show_warnings: true
# ─────────────────────────────────────────────────────────────────
# Common Trading Pairs
# ─────────────────────────────────────────────────────────────────
# Pre-configured pairs for quick scanning.
popular_pairs:
- ["ETH", "USDC"]
- ["ETH", "USDT"]
- ["BTC", "USDC"]
- ["BTC", "USDT"]
- ["ETH", "BTC"]
- ["USDC", "USDT"]
# ─────────────────────────────────────────────────────────────────
# Educational Disclaimer
# ─────────────────────────────────────────────────────────────────
# FOR EDUCATIONAL PURPOSES ONLY
#
# Arbitrage trading involves significant risks:
# - Opportunities may disappear before execution
# - Price data may be delayed or inaccurate
# - Fees can exceed profits on small trades
# - Market conditions change rapidly
#
# This tool provides analysis only. Always verify before trading.
PRD: Finding Arbitrage Opportunities
Summary
One-liner: Real-time detection and analysis of arbitrage opportunities across CEX, DEX, and cross-chain markets.
Domain: Cryptocurrency / Trading / DeFi
Users: Arbitrage Traders, Quantitative Researchers, DeFi Developers
Problem Statement
Cryptocurrency markets are fragmented across hundreds of centralized exchanges (CEX) and decentralized exchanges (DEX), creating price discrepancies that represent arbitrage opportunities. However, manually monitoring these spreads across multiple venues is impractical due to:
- Volume: Thousands of trading pairs across hundreds of exchanges
- Speed: Opportunities exist for seconds to minutes
- Complexity: Multi-hop paths (triangular arbitrage) require graph analysis
- Costs: Fees, slippage, and gas must be accurately calculated
Traders need automated tools to scan markets, identify profitable opportunities, and calculate net profit after all costs.
Target Users
Persona 1: Arbitrage Trader (Alex)
- Role: Professional crypto trader specializing in arbitrage strategies
- Goals: Find consistent low-risk profits from market inefficiencies
- Pain Points: Missing opportunities due to manual monitoring; executing unprofitable trades due to hidden costs
- Technical Level: High (understands order books, APIs, gas costs)
Persona 2: Quantitative Researcher (Quinn)
- Role: Researcher analyzing market microstructure and efficiency
- Goals: Study arbitrage dynamics, measure market integration, backtest strategies
- Pain Points: Lack of historical spread data; difficulty aggregating prices across venues
- Technical Level: Very High (builds custom models and systems)
Persona 3: DeFi Bot Developer (Dana)
- Role: Developer building automated trading bots and MEV strategies
- Goals: Integrate opportunity detection into automated execution systems
- Pain Points: Need reliable APIs and data formats; require accurate profit calculations
- Technical Level: Expert (writes smart contracts, understands MEV)
User Stories
US-1: CEX Spread Scanning (Critical)
As an arbitrage trader, I want to scan price spreads across centralized exchanges for a token pair, So that I can identify exchange-to-exchange arbitrage opportunities.
Acceptance Criteria:
- Fetch bid/ask prices from at least 5 major CEXs
- Calculate spread after trading fees (maker/taker)
- Display opportunities above a configurable profit threshold
- Show estimated profit in USD and percentage
US-2: DEX Price Comparison (Critical)
As a DeFi developer, I want to compare token prices across decentralized exchanges, So that I can find DEX-to-DEX arbitrage without CEX dependency.
Acceptance Criteria:
- Query prices from Uniswap, SushiSwap, Curve, Balancer
- Account for DEX swap fees (0.01% - 1%)
- Estimate gas costs for swap transactions
- Support multiple chains (Ethereum, Polygon, Arbitrum)
US-3: Triangular Arbitrage Detection (High)
As a quantitative researcher, I want to discover triangular arbitrage paths within a single exchange, So that I can exploit multi-hop inefficiencies.
Acceptance Criteria:
- Build price graph from available trading pairs
- Find profitable circular paths (A→B→C→A)
- Calculate net profit after all hop fees
- Rank paths by profit and execution complexity
US-4: Cross-Chain Opportunities (Medium)
As a DeFi bot developer, I want to identify cross-chain price differences, So that I can build bridge arbitrage strategies.
Acceptance Criteria:
- Compare same-token prices across L1/L2 chains
- Factor in bridge fees and transfer times
- Estimate total gas costs (both chains)
- Flag opportunities with acceptable delay risk
US-5: Real-Time Monitoring (High)
As an arbitrage trader, I want to continuously monitor spreads with alerts, So that I don't miss time-sensitive opportunities.
Acceptance Criteria:
- Configurable polling interval (default: 5 seconds)
- Alert when spread exceeds threshold
- Rate-limit handling for API calls
- Graceful degradation if exchange is unavailable
US-6: Profit Calculator (Critical)
As any user, I want accurate profit calculations after all costs, So that I only pursue genuinely profitable opportunities.
Acceptance Criteria:
- Include exchange trading fees (maker/taker)
- Include DEX swap fees
- Include network gas costs
- Include slippage estimates based on trade size
- Show breakeven prices
Functional Requirements
- REQ-1: Multi-exchange price aggregation (CEX and DEX)
- REQ-2: Spread calculation with fee deduction
- REQ-3: Triangular arbitrage path finding
- REQ-4: Cross-chain price comparison
- REQ-5: Configurable profit threshold filtering
- REQ-6: Real-time monitoring mode with alerts
- REQ-7: JSON export for bot integration
- REQ-8: Historical spread tracking (optional)
API Integrations
| API | Purpose | Auth Required |
|---|---|---|
| CoinGecko | Price aggregation, exchange list | No (free tier) |
| CCXT | Unified CEX API access | Per-exchange |
| Uniswap Subgraph | DEX pool prices | No |
| SushiSwap Subgraph | DEX pool prices | No |
| Etherscan | Gas price oracle | Optional |
| Chainlink | Price feeds (on-chain) | No |
Non-Goals
- Trade Execution: This skill finds opportunities; it does not execute trades
- Wallet Management: No private key handling or transaction signing
- MEV Protection: Flashbots integration is out of scope
- Historical Backtesting: Focus is real-time detection, not historical analysis
- Portfolio Tracking: Separate from position management
Success Metrics
- Opportunities identified match real market spreads (±0.1%)
- Profit calculations accurate within 5% of actual execution
- Latency from price update to opportunity detection < 2 seconds
- False positive rate (unprofitable opportunities shown as profitable) < 5%
Constraints & Assumptions
Constraints:
- Free API tiers have rate limits (e.g., CoinGecko: 10-30 calls/min)
- DEX prices require blockchain queries (latency ~1-3s)
- Cross-chain bridge times vary (minutes to hours)
Assumptions:
- Users have basic understanding of arbitrage concepts
- Users can obtain exchange API keys if needed
- Network connectivity is stable
Risk Assessment
| Risk | Impact | Mitigation |
|---|---|---|
| Stale prices | Execute at wrong price | Show data freshness; require < 5s staleness |
| Rate limiting | Miss opportunities | Implement backoff; use multiple API sources |
| Slippage miscalculation | Negative profit | Conservative slippage estimates; size warnings |
| Exchange downtime | Incomplete data | Mark unavailable exchanges; use alternatives |
Educational Disclaimer
FOR EDUCATIONAL PURPOSES ONLY
Arbitrage trading involves significant risks:
- Opportunities may disappear before execution
- Price data may be delayed or inaccurate
- Transaction fees can exceed expected profits
- Market manipulation can create false opportunities
- Automated trading requires careful risk management
This tool provides analysis only. Users are responsible for their own trading decisions.
Error Handling Reference
Comprehensive error codes and solutions for arbitrage opportunity detection.
---
Price Data Errors
ERR_PRICE_STALE
Error: Price data exceeds maximum staleness threshold Cause: Exchange API returned outdated data or connection issues Solution:
- Check
max_stalenesssetting in config (default: 30 seconds) - Verify exchange API connectivity
- Switch to alternative data source
- Exclude stale quotes from analysis
ERR_PRICE_UNAVAILABLE
Error: Could not fetch price for trading pair Cause: Exchange doesn't list the pair or API timeout Solution:
- Verify trading pair exists on target exchange
- Check exchange API status page
- Use alternative exchange for that pair
- Implement fallback data sources
ERR_BID_ASK_INVERTED
Error: Bid price exceeds ask price Cause: Data corruption, API error, or extreme volatility Solution:
- Skip the quote and log warning
- Fetch fresh data
- This indicates unreliable data source
---
Rate Limiting Errors
ERR_RATE_LIMIT_CEX
Error: Exchange API rate limit exceeded Cause: Too many requests in rate limit window Solution:
- Implement exponential backoff (config:
backoff_multiplier) - Add delay between requests (config:
request_delay_ms) - Use API key for higher limits
- Batch requests where possible
ERR_RATE_LIMIT_DEX
Error: DEX subgraph rate limit exceeded Cause: Too many GraphQL queries Solution:
- Reduce query frequency
- Cache pool data locally
- Use batch queries
- Consider self-hosted subgraph
ERR_API_QUOTA_EXHAUSTED
Error: Daily/monthly API quota reached Cause: Free tier limits exceeded Solution:
- Upgrade API subscription tier
- Switch to alternative provider
- Implement data caching
- Reduce polling frequency
---
Triangular Arbitrage Errors
ERR_NO_PATH_FOUND
Error: No triangular path exists between tokens Cause: Missing trading pairs for complete cycle Solution:
- Verify all three pairs exist on exchange
- Check pair liquidity (may be delisted)
- Try different token combinations
- Use alternative exchange
ERR_INSUFFICIENT_LIQUIDITY
Error: Pool/order book liquidity below minimum threshold Cause: Trading pair has low volume Solution:
- Adjust
min_liquiditythreshold - Skip low-liquidity pairs
- Use larger, more liquid markets
- Factor in higher slippage estimate
ERR_PATH_NEGATIVE_PROFIT
Error: All triangular paths result in losses after fees Cause: Markets are efficient, no arbitrage exists Solution:
- This is normal - markets are usually efficient
- Continue monitoring for opportunities
- Adjust
min_profit_pctthreshold - Check fee calculations are accurate
---
Exchange Connectivity Errors
ERR_EXCHANGE_UNAVAILABLE
Error: Cannot connect to exchange API Cause: Exchange maintenance, network issues, or API changes Solution:
- Check exchange status page
- Retry with exponential backoff
- Use alternative exchange
- Verify API base URL is correct
ERR_AUTH_FAILED
Error: API key authentication failed Cause: Invalid/expired API key or incorrect signature Solution:
- Regenerate API keys
- Check key permissions (read access required)
- Verify system clock sync (for signature)
- Ensure API key hasn't expired
ERR_EXCHANGE_NOT_SUPPORTED
Error: Exchange not configured in system Cause: Attempting to use unconfigured exchange Solution:
- Add exchange to
config/settings.yaml - Verify exchange name spelling
- Check supported exchanges list
- Configure fee structure
---
DEX-Specific Errors
ERR_GAS_ESTIMATE_FAILED
Error: Could not estimate gas for DEX swap Cause: RPC node issues or invalid transaction Solution:
- Use default gas estimates from config
- Switch to alternative RPC endpoint
- Verify pool contract address
- Check for contract upgrades
ERR_SUBGRAPH_SYNC
Error: DEX subgraph is behind blockchain Cause: Subgraph indexing delay Solution:
- Use on-chain RPC calls instead
- Wait for subgraph to catch up
- Factor in data delay in analysis
- Use alternative subgraph endpoint
ERR_POOL_DEPRECATED
Error: Liquidity pool no longer active Cause: V2→V3 migration or pool closure Solution:
- Update pool addresses in config
- Switch to newer protocol version
- Remove deprecated pool from scan
---
Calculation Errors
ERR_INVALID_AMOUNT
Error: Trade amount is invalid Cause: Non-numeric input or negative value Solution:
- Validate amount before calculation
- Ensure positive numeric value
- Use Decimal for precision
ERR_FEE_EXCEEDS_PROFIT
Error: Total fees exceed gross profit Cause: Spread too small for trade size Solution:
- Increase trade amount (larger size = better fee ratio)
- Use exchanges with lower fees
- Wait for larger spread opportunities
- Skip unprofitable opportunities
ERR_SLIPPAGE_OVERFLOW
Error: Estimated slippage exceeds safe threshold Cause: Trade size too large for liquidity Solution:
- Reduce trade amount
- Split into smaller trades
- Use limit orders
- Target higher liquidity pools
---
Risk Assessment Errors
WARN_HIGH_RISK_OPPORTUNITY
Warning: Opportunity flagged as high/extreme risk Cause: Multiple risk factors present Solution:
- Review risk factors in output
- Verify data freshness
- Consider smaller position size
- Skip if outside risk tolerance
WARN_DATA_STALENESS
Warning: Some quotes exceed staleness threshold Cause: Delayed data from one or more sources Solution:
- Refresh stale data before execution
- Factor staleness into risk assessment
- Use only fresh data for decisions
---
Recovery Procedures
General Recovery
1. Check exchange/API status pages 2. Verify network connectivity 3. Review config settings 4. Check API key validity 5. Restart with fresh data
Data Recovery
1. Clear cached price data 2. Refetch from all sources 3. Validate data consistency 4. Resume normal operation
Emergency Stop
If encountering repeated errors: 1. Stop monitoring loop 2. Review error logs 3. Fix configuration issues 4. Test with single request 5. Resume monitoring gradually
--- [Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)
Examples
Comprehensive usage examples for arbitrage opportunity detection.
---
Example 1: Basic Spread Scan
Scan for ETH/USDC arbitrage across all enabled exchanges.
Command
python arb_finder.py scan ETH USDCOutput
======================== ARBITRAGE SCAN RESULTS ========================
Pair: ETH/USDC
Exchanges scanned: 9
Opportunities found: 3
----------------------------------------------------------------------
CURRENT PRICES
----------------------------------------------------------------------
Exchange Bid Ask Spread
----------------------------------------------------------------------
Coinbase $2,543.80 $2,544.10 0.012%
Uniswap V3 $2,543.50 $2,544.00 0.020%
Kraken $2,542.30 $2,542.80 0.020%
Binance $2,541.20 $2,541.50 0.012%
KuCoin $2,541.00 $2,541.40 0.016%
OKX $2,540.80 $2,541.20 0.016%
SushiSwap $2,540.50 $2,541.00 0.020%
Curve $2,540.20 $2,540.60 0.016%
Balancer $2,539.80 $2,540.30 0.020%
----------------------------------------------------------------------
OPPORTUNITIES
----------------------------------------------------------------------
Buy On Sell On Gross Net Risk
----------------------------------------------------------------------
Balancer Coinbase +0.157% +0.037% 🟢 LOW
Curve Uniswap V3 +0.130% +0.026% 🟡 MEDIUM
SushiSwap Kraken +0.071% -0.029% 🟡 MEDIUM
----------------------------------------------------------------------
BEST OPPORTUNITY
----------------------------------------------------------------------
Buy on Balancer at $2,540.30
Sell on Coinbase at $2,543.80
Gross spread: +0.1378%
Buy fee: -0.10%
Sell fee: -0.60%
Gas cost: ~$11.25
----------------------------------------------------------------------
Net spread: +0.0368%
Risk: 🟢 LOW
Notes:
• DEX-to-CEX trade requires bridging
• Consider gas costs for DEX execution
✓ PROFITABLE - Consider execution---
Example 2: CEX-Only Scan
Scan only centralized exchanges (faster, no gas costs).
Command
python arb_finder.py scan BTC USDT --cex-only --min-profit 0.05Output
======================== ARBITRAGE SCAN RESULTS ========================
Pair: BTC/USDT
Exchanges scanned: 5
Opportunities found: 1
----------------------------------------------------------------------
CURRENT PRICES
----------------------------------------------------------------------
Exchange Bid Ask Spread
----------------------------------------------------------------------
Coinbase $67,920.00 $67,950.00 0.044%
Binance $67,850.00 $67,865.00 0.022%
Kraken $67,830.00 $67,860.00 0.044%
KuCoin $67,820.00 $67,850.00 0.044%
OKX $67,810.00 $67,840.00 0.044%
----------------------------------------------------------------------
OPPORTUNITIES
----------------------------------------------------------------------
Buy On Sell On Gross Net Risk
----------------------------------------------------------------------
OKX Coinbase +0.118% +0.052% 🟢 LOW
----------------------------------------------------------------------
BEST OPPORTUNITY
----------------------------------------------------------------------
Buy on OKX at $67,840.00
Sell on Coinbase at $67,920.00
Gross spread: +0.1179%
Buy fee: -0.10%
Sell fee: -0.60%
----------------------------------------------------------------------
Net spread: +0.0519%
Risk: 🟢 LOW
✓ PROFITABLE - Consider execution---
Example 3: Specific Exchange Comparison
Compare prices between specific exchanges only.
Command
python arb_finder.py scan ETH USDC --exchanges binance,coinbase,krakenOutput
======================== ARBITRAGE SCAN RESULTS ========================
Pair: ETH/USDC
Exchanges scanned: 3
Opportunities found: 1
----------------------------------------------------------------------
CURRENT PRICES
----------------------------------------------------------------------
Exchange Bid Ask Spread
----------------------------------------------------------------------
Coinbase $2,543.80 $2,544.10 0.012%
Kraken $2,542.30 $2,542.80 0.020%
Binance $2,541.20 $2,541.50 0.012%
----------------------------------------------------------------------
OPPORTUNITIES
----------------------------------------------------------------------
Buy On Sell On Gross Net Risk
----------------------------------------------------------------------
Binance Coinbase +0.091% +0.021% 🟢 LOW
----------------------------------------------------------------------
BEST OPPORTUNITY
----------------------------------------------------------------------
Buy on Binance at $2,541.50
Sell on Coinbase at $2,543.80
Gross spread: +0.0905%
Buy fee: -0.10%
Sell fee: -0.60%
----------------------------------------------------------------------
Net spread: +0.0205%
Risk: 🟢 LOW
✓ PROFITABLE - Consider execution---
Example 4: Triangular Arbitrage Scan
Find triangular arbitrage paths on a single exchange.
Command
python arb_finder.py triangular binance --min-profit 0.0Output
======================== TRIANGULAR ARBITRAGE ========================
Found 6 paths
Path Gross Fees Net
----------------------------------------------------------------------
ETH → BTC → USDT → ETH +0.3520% -0.3000% +0.0520%
BNB → ETH → USDT → BNB +0.2850% -0.3000% -0.0150%
BNB → BTC → USDT → BNB +0.2410% -0.3000% -0.0590%
ETH → USDC → USDT → ETH +0.1800% -0.3000% -0.1200%
BTC → USDC → USDT → BTC +0.1500% -0.3000% -0.1500%
BNB → BTC → ETH → BNB +0.1200% -0.3000% -0.1800%
----------------------------------------------------------------------
BEST PATH
----------------------------------------------------------------------
Path: ETH → BTC → USDT → ETH
Exchange: binance
Execution Steps:
1. Sell ETH for BTC at 0.03745
2. Sell BTC for USDT at 67850.00
3. Buy ETH with USDT at 2541.50
Gross Profit: +0.3520%
Total Fees: -0.3000%
Net Profit: +0.0520%
✓ PROFITABLE - Consider execution---
Example 5: Real-Time Monitoring
Monitor for opportunities with alerts.
Command
python arb_finder.py monitor ETH USDC --threshold 0.3 --interval 5Output
Monitoring ETH/USDC for spreads > 0.3%
Interval: 5s | Press Ctrl+C to stop
[1] Best: Binance → Coinbase (+0.021%) - below threshold
[2] Best: Binance → Coinbase (+0.019%) - below threshold
[3] Best: Binance → Coinbase (+0.025%) - below threshold
============================================================
🚨 ARBITRAGE ALERT
============================================================
ETH/USDC spread +0.342%
Buy on OKX at $2,535.20
Sell on Coinbase at $2,543.88
Risk: 🟢 LOW
============================================================
[5] Best: OKX → Coinbase (+0.342%) - ALERT TRIGGERED
[6] Best: OKX → Coinbase (+0.285%) - below threshold
^C
Monitoring stopped---
Example 6: Profit Calculation
Calculate exact profit for a specific trade.
Command
python arb_finder.py calc \
--buy-exchange binance \
--sell-exchange coinbase \
--pair ETH/USDC \
--amount 10Output
======================== PROFIT BREAKDOWN ========================
Trade: 10 ETH
Buy on binance at $2,541.50
Sell on coinbase at $2,543.80
Gross Profit: $23.00 (+0.090%)
Costs:
Buy fee: -$2.54
Sell fee: -$15.26
Withdrawal: -$1.27
Gas: -$0.00
Slippage: -$2.54
------------------------------
Total: -$21.62
Net Profit: $1.38 (+0.005%)
Breakeven spread: 0.085%
✓ PROFITABLE---
Example 7: Manual Price Entry
Calculate with specific buy/sell prices.
Command
python arb_finder.py calc \
--buy-exchange binance \
--sell-exchange coinbase \
--pair ETH/USDC \
--amount 50 \
--buy-price 2500.00 \
--sell-price 2510.00Output
======================== PROFIT BREAKDOWN ========================
Trade: 50 ETH
Buy on binance at $2,500.00
Sell on coinbase at $2,510.00
Gross Profit: $500.00 (+0.400%)
Costs:
Buy fee: -$12.50
Sell fee: -$75.30
Withdrawal: -$6.25
Gas: -$0.00
Slippage: -$12.50
------------------------------
Total: -$106.55
Net Profit: $393.45 (+0.315%)
Breakeven spread: 0.085%
✓ PROFITABLE---
Example 8: JSON Output
Export results in JSON format for programmatic use.
Command
python arb_finder.py scan ETH USDC --output jsonOutput
{
"pair": "ETH/USDC",
"timestamp": 1705968000,
"quotes_count": 9,
"opportunities_count": 3,
"quotes": [
{
"exchange": "coinbase",
"exchange_type": "cex",
"bid": 2543.80,
"ask": 2544.10,
"spread_pct": 0.012,
"volume_24h": 125000000.0
}
],
"opportunities": [
{
"buy_exchange": "balancer",
"sell_exchange": "coinbase",
"buy_price": 2540.30,
"sell_price": 2543.80,
"gross_spread_pct": 0.1378,
"net_spread_pct": 0.0368,
"risk_level": "low",
"is_profitable": true
}
],
"best_opportunity": {
"buy_exchange": "balancer",
"sell_exchange": "coinbase",
"buy_price": 2540.30,
"sell_price": 2543.80,
"net_spread_pct": 0.0368,
"risk_level": "low"
}
}---
Example 9: DEX-Only Scan
Scan only decentralized exchanges.
Command
python arb_finder.py scan ETH USDC --dex-onlyOutput
======================== ARBITRAGE SCAN RESULTS ========================
Pair: ETH/USDC
Exchanges scanned: 4
Opportunities found: 2
----------------------------------------------------------------------
CURRENT PRICES
----------------------------------------------------------------------
Exchange Bid Ask Spread
----------------------------------------------------------------------
Uniswap V3 $2,543.50 $2,544.00 0.020%
SushiSwap $2,540.50 $2,541.00 0.020%
Curve $2,540.20 $2,540.60 0.016%
Balancer $2,539.80 $2,540.30 0.020%
----------------------------------------------------------------------
OPPORTUNITIES
----------------------------------------------------------------------
Buy On Sell On Gross Net Risk
----------------------------------------------------------------------
Balancer Uniswap V3 +0.126% +0.062% 🟢 LOW
Curve Uniswap V3 +0.114% +0.050% 🟢 LOW
----------------------------------------------------------------------
BEST OPPORTUNITY
----------------------------------------------------------------------
Buy on Balancer at $2,540.30
Sell on Uniswap V3 at $2,543.50
Gross spread: +0.1260%
Buy fee: -0.10%
Sell fee: -0.30%
Gas cost: ~$22.50 (2 swaps)
----------------------------------------------------------------------
Net spread: +0.0620%
Risk: 🟢 LOW
Notes:
• Both DEX - can execute atomically via flash loan
• Gas costs for 2 swap operations included
✓ PROFITABLE - Consider execution---
Example 10: Custom Gas Price
Adjust calculations for different gas conditions.
Command
python arb_finder.py scan ETH USDC --dex-only --gas-price 100 --eth-price 3000Output
======================== ARBITRAGE SCAN RESULTS ========================
Pair: ETH/USDC
Exchanges scanned: 4
Opportunities found: 0
----------------------------------------------------------------------
CURRENT PRICES
----------------------------------------------------------------------
Exchange Bid Ask Spread
----------------------------------------------------------------------
Uniswap V3 $2,543.50 $2,544.00 0.020%
SushiSwap $2,540.50 $2,541.00 0.020%
Curve $2,540.20 $2,540.60 0.016%
Balancer $2,539.80 $2,540.30 0.020%
No profitable opportunities found (market is efficient)
Note: High gas price ($45.00 per swap) makes small spreads unprofitable.
Consider waiting for lower gas or targeting larger spreads.---
Common Patterns
Morning Scan Routine
# Quick overview of major pairs
python arb_finder.py scan BTC USDT --cex-only
python arb_finder.py scan ETH USDC --cex-only
python arb_finder.py scan ETH BTC --cex-onlyContinuous Monitoring
# Run in tmux/screen for persistent monitoring
python arb_finder.py monitor ETH USDC --threshold 0.2 --interval 10Export for Analysis
# Save to file for later analysis
python arb_finder.py scan ETH USDC --output json > arb_$(date +%Y%m%d).jsonTriangular Sweep
# Check all major exchanges for triangular opportunities
for exchange in binance coinbase kraken; do
echo "=== $exchange ==="
python arb_finder.py triangular $exchange --min-profit 0.0
done--- [Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)
Finding Arbitrage Opportunities - Implementation Reference
Supported Exchanges
Centralized Exchanges (CEX)
| Exchange | Maker Fee | Taker Fee | Withdrawal |
|---|---|---|---|
| Binance | 0.10% | 0.10% | Variable |
| Coinbase | 0.40% | 0.60% | Variable |
| Kraken | 0.16% | 0.26% | Variable |
| KuCoin | 0.10% | 0.10% | Variable |
| OKX | 0.08% | 0.10% | Variable |
Decentralized Exchanges (DEX)
| DEX | Fee Range | Gas (ETH) | Chains |
|---|---|---|---|
| Uniswap V3 | 0.01-1% | ~150k | ETH, Polygon, Arbitrum |
| SushiSwap | 0.30% | ~150k | Multi-chain |
| Curve | 0.04% | ~200k | ETH, Polygon, Arbitrum |
| Balancer | 0.01-10% | ~180k | ETH, Polygon, Arbitrum |
Configuration
Configure price sources in ${CLAUDE_SKILL_DIR}/config/settings.yaml:
# Primary data sources
data_sources:
coingecko:
enabled: true
base_url: "https://api.coingecko.com/api/v3"
rate_limit: 10 # calls per minute (free tier)
exchanges:
- binance
- coinbase
- kraken
- kucoin
- okxEnvironment variables for API keys:
export BINANCE_API_KEY="your-key"
export COINBASE_API_KEY="your-key"Advanced Arbitrage Types
Triangular Arbitrage
Find profitable circular paths within a single exchange:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py triangular binance --min-profit 0.5Example output:
Path: ETH -> BTC -> USDT -> ETH
Gross: +0.82%
Fees: -0.30% (3 x 0.10%)
-----------------------
Net: +0.52%Cross-Chain Opportunities
Compare prices across different blockchains:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py cross-chain USDC \
--chains ethereum,polygon,arbitrumShows:
- Price on each chain
- Bridge fees and times
- Net profit after bridging
Real-Time Monitoring
Continuously monitor for opportunities:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py monitor ETH USDC \
--threshold 0.5 \
--interval 5Alert format:
[ALERT] ETH/USDC spread 0.62% (Binance -> Coinbase)
Buy: $2,541.20 | Sell: $2,556.98
Net Profit: +$12.34 (after fees)Profit Calculator
Calculate exact profit for a trade:
python ${CLAUDE_SKILL_DIR}/scripts/arb_finder.py calc \
--buy-exchange binance \
--sell-exchange coinbase \
--pair ETH/USDC \
--amount 10Shows detailed breakdown:
- Gross profit
- Trading fees (both exchanges)
- Withdrawal fees
- Net profit
- Breakeven spread
Educational Disclaimer
FOR EDUCATIONAL PURPOSES ONLY
Arbitrage trading involves significant risks:
- Opportunities may disappear before execution
- Price data may be delayed or inaccurate
- Fees can exceed profits on small trades
- Market conditions change rapidly
This tool provides analysis only. Do not trade without understanding the risks.
Data Sources
- CoinGecko API - Free price data
- CCXT Library - Unified exchange API
- Uniswap Subgraph - DEX data
- Gas Tracker - Ethereum gas prices
--- [Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)
#!/usr/bin/env python3
"""
Arbitrage Opportunity Finder - Main CLI Entry Point.
Scans for arbitrage opportunities across exchanges with:
- Direct spread detection (CEX-to-CEX, DEX-to-DEX)
- Triangular arbitrage path finding
- Profit calculation after all fees
- Real-time monitoring
Usage:
python arb_finder.py scan ETH USDC
python arb_finder.py scan ETH USDC --exchanges binance,coinbase,kraken
python arb_finder.py triangular binance --min-profit 0.5
python arb_finder.py monitor ETH USDC --threshold 0.5 --interval 5
python arb_finder.py calc --buy-exchange binance --sell-exchange coinbase --pair ETH/USDC --amount 10
"""
import argparse
import sys
import time
from decimal import Decimal, InvalidOperation
from typing import Optional
from price_fetcher import PriceFetcher, ExchangeType
from opportunity_scanner import OpportunityScanner
from triangular_finder import TriangularFinder
from profit_calculator import ProfitCalculator
from formatters import ConsoleFormatter, JSONFormatter
def parse_args() -> argparse.Namespace:
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="Arbitrage Opportunity Finder",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Scan for ETH/USDC arbitrage across all exchanges
%(prog)s scan ETH USDC
# Scan specific exchanges
%(prog)s scan ETH USDC --exchanges binance,coinbase,kraken
# Scan DEX only
%(prog)s scan ETH USDC --dex-only
# Find triangular arbitrage on Binance
%(prog)s triangular binance --min-profit 0.1
# Monitor for opportunities with alerts
%(prog)s monitor ETH USDC --threshold 0.3 --interval 5
# Calculate profit for specific trade
%(prog)s calc --buy-exchange binance --sell-exchange coinbase --pair ETH/USDC --amount 10
Supported Exchanges:
CEX: binance, coinbase, kraken, kucoin, okx
DEX: uniswap, sushiswap, curve, balancer
EDUCATIONAL DISCLAIMER:
This tool is for analysis and learning only.
Arbitrage involves risks. Always verify data before trading.
""",
)
subparsers = parser.add_subparsers(dest="command", help="Command")
# Scan subcommand
scan_parser = subparsers.add_parser(
"scan", help="Scan for direct arbitrage opportunities"
)
scan_parser.add_argument("base", help="Base token (e.g., ETH)")
scan_parser.add_argument("quote", help="Quote token (e.g., USDC)")
scan_parser.add_argument(
"--exchanges",
type=str,
help="Comma-separated list of exchanges",
)
scan_parser.add_argument(
"--cex-only",
action="store_true",
help="Only scan centralized exchanges",
)
scan_parser.add_argument(
"--dex-only",
action="store_true",
help="Only scan decentralized exchanges",
)
scan_parser.add_argument(
"--min-profit",
type=float,
default=0.0,
help="Minimum profit percentage to show (default: 0.0)",
)
# Triangular subcommand
tri_parser = subparsers.add_parser(
"triangular", help="Find triangular arbitrage paths"
)
tri_parser.add_argument("exchange", help="Exchange to analyze")
tri_parser.add_argument(
"--min-profit",
type=float,
default=0.1,
help="Minimum profit percentage (default: 0.1)",
)
# Monitor subcommand
mon_parser = subparsers.add_parser(
"monitor", help="Real-time opportunity monitoring"
)
mon_parser.add_argument("base", help="Base token (e.g., ETH)")
mon_parser.add_argument("quote", help="Quote token (e.g., USDC)")
mon_parser.add_argument(
"--threshold",
type=float,
default=0.3,
help="Alert threshold percentage (default: 0.3)",
)
mon_parser.add_argument(
"--interval",
type=int,
default=5,
help="Polling interval in seconds (default: 5)",
)
mon_parser.add_argument(
"--exchanges",
type=str,
help="Comma-separated list of exchanges",
)
# Calc subcommand
calc_parser = subparsers.add_parser(
"calc", help="Calculate profit for specific trade"
)
calc_parser.add_argument(
"--buy-exchange",
required=True,
help="Exchange to buy on",
)
calc_parser.add_argument(
"--sell-exchange",
required=True,
help="Exchange to sell on",
)
calc_parser.add_argument(
"--pair",
required=True,
help="Trading pair (e.g., ETH/USDC)",
)
calc_parser.add_argument(
"--amount",
type=str,
required=True,
help="Trade amount in base currency",
)
calc_parser.add_argument(
"--buy-price",
type=str,
help="Buy price (fetched if not provided)",
)
calc_parser.add_argument(
"--sell-price",
type=str,
help="Sell price (fetched if not provided)",
)
# Global options for all subcommands
for sub in [scan_parser, tri_parser, mon_parser, calc_parser]:
sub.add_argument(
"--output",
choices=["console", "json"],
default="console",
help="Output format (default: console)",
)
sub.add_argument(
"--eth-price",
type=float,
default=2500.0,
help="ETH price in USD (default: 2500)",
)
sub.add_argument(
"--gas-price",
type=float,
default=30.0,
help="Gas price in gwei (default: 30)",
)
return parser.parse_args()
def run_scan(args: argparse.Namespace) -> int:
"""Run direct arbitrage scan."""
# Parse exchanges
exchanges = None
if args.exchanges:
exchanges = [e.strip() for e in args.exchanges.split(",")]
exchange_type = None
if args.cex_only:
exchange_type = ExchangeType.CEX
elif args.dex_only:
exchange_type = ExchangeType.DEX
# Create scanner
scanner = OpportunityScanner(
min_profit_pct=args.min_profit,
gas_price_gwei=args.gas_price,
eth_price_usd=args.eth_price,
)
# Run scan
result = scanner.scan(
args.base.upper(),
args.quote.upper(),
exchanges,
exchange_type,
)
# Format output
if args.output == "json":
fmt = JSONFormatter()
print(fmt.format_scan_result(result))
else:
fmt = ConsoleFormatter()
print(fmt.format_scan_result(result))
# Return code based on findings
return 0 if result.opportunities else 1
def run_triangular(args: argparse.Namespace) -> int:
"""Run triangular arbitrage finder."""
finder = TriangularFinder(min_profit_pct=args.min_profit)
# Find opportunities
paths = finder.find_opportunities(args.exchange)
# Format output
if args.output == "json":
fmt = JSONFormatter()
print(fmt.format_triangular_results(paths))
else:
fmt = ConsoleFormatter()
print(fmt.format_triangular_results(paths))
return 0 if paths else 1
def run_monitor(args: argparse.Namespace) -> int:
"""Run real-time monitoring."""
# Parse exchanges
exchanges = None
if args.exchanges:
exchanges = [e.strip() for e in args.exchanges.split(",")]
scanner = OpportunityScanner(
min_profit_pct=args.threshold,
gas_price_gwei=args.gas_price,
eth_price_usd=args.eth_price,
)
console = ConsoleFormatter()
print(f"Monitoring {args.base}/{args.quote} for spreads > {args.threshold}%")
print(f"Interval: {args.interval}s | Press Ctrl+C to stop\n")
iteration = 0
try:
while True:
iteration += 1
result = scanner.scan(
args.base.upper(),
args.quote.upper(),
exchanges,
)
if result.best_opportunity:
best = result.best_opportunity
if best.net_spread_pct >= args.threshold:
print(console.format_alert(best))
print()
else:
print(
f"[{iteration}] Best: {best.buy_exchange} → {best.sell_exchange} "
f"({best.net_spread_pct:+.3f}%) - below threshold"
)
else:
print(f"[{iteration}] No opportunities found")
time.sleep(args.interval)
except KeyboardInterrupt:
print("\nMonitoring stopped")
return 0
def run_calc(args: argparse.Namespace) -> int:
"""Run profit calculation."""
try:
amount = Decimal(args.amount)
except InvalidOperation:
print(f"Error: Invalid amount '{args.amount}'", file=sys.stderr)
return 1
# Parse pair
if "/" in args.pair:
base, quote = args.pair.split("/")
else:
print(f"Error: Invalid pair format '{args.pair}' (use BASE/QUOTE)", file=sys.stderr)
return 1
# Get prices if not provided
fetcher = PriceFetcher(use_mock=True)
if args.buy_price:
buy_price = Decimal(args.buy_price)
else:
quote_obj = fetcher.fetch_all_prices_sync(base, quote, [args.buy_exchange])
if quote_obj:
buy_price = quote_obj[0].ask
else:
print(f"Error: Could not fetch price from {args.buy_exchange}", file=sys.stderr)
return 1
if args.sell_price:
sell_price = Decimal(args.sell_price)
else:
quote_obj = fetcher.fetch_all_prices_sync(base, quote, [args.sell_exchange])
if quote_obj:
sell_price = quote_obj[0].bid
else:
print(f"Error: Could not fetch price from {args.sell_exchange}", file=sys.stderr)
return 1
# Calculate
calc = ProfitCalculator(
gas_price_gwei=args.gas_price,
eth_price_usd=args.eth_price,
)
breakdown = calc.calculate(
pair=args.pair,
buy_exchange=args.buy_exchange,
sell_exchange=args.sell_exchange,
buy_price=buy_price,
sell_price=sell_price,
amount=amount,
)
# Format output
if args.output == "json":
fmt = JSONFormatter()
print(fmt.format_profit_breakdown(breakdown))
else:
fmt = ConsoleFormatter()
print(fmt.format_profit_breakdown(breakdown))
return 0 if breakdown.is_profitable else 1
def print_banner():
"""Print startup banner."""
banner = """
╔══════════════════════════════════════════════════════════════════╗
║ ARBITRAGE OPPORTUNITY FINDER v1.0.0 ║
║ ║
║ Find profitable arbitrage across CEX, DEX, and cross-chain ║
║ Exchanges: Binance | Coinbase | Kraken | Uniswap | SushiSwap ║
║ ║
║ ⚠️ EDUCATIONAL PURPOSES ONLY - Verify before trading ║
╚══════════════════════════════════════════════════════════════════╝
"""
print(banner, file=sys.stderr)
def main() -> int:
"""Main entry point."""
args = parse_args()
if not args.command:
print_banner()
print("Use --help for usage information", file=sys.stderr)
print("\nQuick start:")
print(" arb_finder.py scan ETH USDC")
print(" arb_finder.py triangular binance")
print(" arb_finder.py monitor ETH USDC --threshold 0.3")
return 1
# Run appropriate command
if args.command == "scan":
return run_scan(args)
elif args.command == "triangular":
return run_triangular(args)
elif args.command == "monitor":
return run_monitor(args)
elif args.command == "calc":
return run_calc(args)
else:
print(f"Unknown command: {args.command}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python3
"""
Arbitrage opportunity output formatters.
Handles all output formatting:
- Console tables and reports
- JSON export
- Alert messages
"""
import json
from dataclasses import asdict
from decimal import Decimal
from typing import Any, Dict, List, Optional
from opportunity_scanner import ArbitrageOpportunity, ScanResult, RiskLevel
from triangular_finder import ArbitragePath
from profit_calculator import ProfitBreakdown
class DecimalEncoder(json.JSONEncoder):
"""JSON encoder that handles Decimal types."""
def default(self, obj):
if isinstance(obj, Decimal):
return float(obj)
if hasattr(obj, "value"): # Enum
return obj.value
return super().default(obj)
class ConsoleFormatter:
"""Format output for console display."""
# Risk level indicators
RISK_INDICATORS = {
RiskLevel.LOW: ("🟢", "LOW"),
RiskLevel.MEDIUM: ("🟡", "MEDIUM"),
RiskLevel.HIGH: ("🟠", "HIGH"),
RiskLevel.EXTREME: ("🔴", "EXTREME"),
}
def __init__(self, width: int = 70):
"""Initialize formatter with display width."""
self.width = width
def _header(self, title: str) -> str:
"""Create a section header."""
padding = (self.width - len(title) - 2) // 2
return f"\n{'=' * padding} {title} {'=' * padding}\n"
def _subheader(self, title: str) -> str:
"""Create a subsection header."""
return f"\n{'-' * self.width}\n{title}\n{'-' * self.width}\n"
def format_scan_result(self, result: ScanResult) -> str:
"""Format a scan result with all opportunities."""
lines = []
lines.append(self._header("ARBITRAGE SCAN RESULTS"))
# Summary
lines.append(f"Pair: {result.pair}")
lines.append(f"Exchanges scanned: {len(result.quotes)}")
lines.append(f"Opportunities found: {len(result.opportunities)}")
if not result.opportunities:
lines.append("\nNo profitable opportunities found (market is efficient)")
return "\n".join(lines)
# Price table
lines.append(self._subheader("CURRENT PRICES"))
lines.append(f"{'Exchange':<15} {'Bid':>12} {'Ask':>12} {'Spread':>8}")
lines.append("-" * 50)
for quote in sorted(result.quotes, key=lambda x: x.bid, reverse=True):
lines.append(
f"{quote.exchange:<15} "
f"${quote.bid:>11,.2f} "
f"${quote.ask:>11,.2f} "
f"{quote.spread_pct:>7.3f}%"
)
# Opportunities table
lines.append(self._subheader("OPPORTUNITIES"))
lines.append(
f"{'Buy On':<15} {'Sell On':<15} {'Gross':>8} {'Net':>8} {'Risk':<8}"
)
lines.append("-" * 60)
for opp in result.opportunities[:10]:
indicator = "+" if opp.is_profitable else "-"
risk_icon, _ = self.RISK_INDICATORS.get(opp.risk_level, ("⚪", "?"))
lines.append(
f"{opp.buy_exchange:<15} "
f"{opp.sell_exchange:<15} "
f"{indicator}{opp.gross_spread_pct:>6.3f}% "
f"{indicator}{opp.net_spread_pct:>6.3f}% "
f"{risk_icon} {opp.risk_level.value:<6}"
)
# Best opportunity details
if result.best_opportunity:
lines.append(self._subheader("BEST OPPORTUNITY"))
lines.append(self.format_opportunity_details(result.best_opportunity))
return "\n".join(lines)
def format_opportunity_details(self, opp: ArbitrageOpportunity) -> str:
"""Format detailed view of a single opportunity."""
lines = []
risk_icon, risk_label = self.RISK_INDICATORS.get(
opp.risk_level, ("⚪", "UNKNOWN")
)
lines.append(f"Buy on {opp.buy_exchange} at ${opp.buy_price:,.2f}")
lines.append(f"Sell on {opp.sell_exchange} at ${opp.sell_price:,.2f}")
lines.append("")
lines.append(f"Gross spread: {opp.gross_spread_pct:+.4f}%")
lines.append(f"Buy fee: -{opp.buy_fee_pct:.2f}%")
lines.append(f"Sell fee: -{opp.sell_fee_pct:.2f}%")
if opp.estimated_gas_usd > 0:
lines.append(f"Gas cost: ~${opp.estimated_gas_usd:.2f}")
lines.append("-" * 40)
lines.append(f"Net spread: {opp.net_spread_pct:+.4f}%")
lines.append(f"Risk: {risk_icon} {risk_label}")
if opp.notes:
lines.append("")
lines.append("Notes:")
for note in opp.notes:
lines.append(f" • {note}")
if opp.is_profitable:
lines.append("")
lines.append("✓ PROFITABLE - Consider execution")
else:
lines.append("")
lines.append("✗ NOT PROFITABLE after fees")
return "\n".join(lines)
def format_triangular_results(self, paths: List[ArbitragePath]) -> str:
"""Format triangular arbitrage results."""
lines = []
lines.append(self._header("TRIANGULAR ARBITRAGE"))
if not paths:
lines.append("No profitable triangular paths found")
return "\n".join(lines)
lines.append(f"Found {len(paths)} paths\n")
lines.append(f"{'Path':<30} {'Gross':>10} {'Fees':>10} {'Net':>10}")
lines.append("-" * 65)
for path in paths[:10]:
path_str = " → ".join(path.tokens)
indicator = "+" if path.is_profitable else "-"
lines.append(
f"{path_str:<30} "
f"{indicator}{abs(path.gross_profit_pct):>8.4f}% "
f"-{path.total_fees_pct:>8.4f}% "
f"{indicator}{abs(path.net_profit_pct):>8.4f}%"
)
# Best path details
if paths:
best = paths[0]
lines.append(self._subheader("BEST PATH"))
lines.append(f"Path: {' → '.join(best.tokens)}")
lines.append(f"Exchange: {best.exchange}")
lines.append("")
lines.append("Execution Steps:")
for i, step in enumerate(best.execution_steps, 1):
lines.append(f" {i}. {step}")
lines.append("")
lines.append(f"Gross Profit: {best.gross_profit_pct:+.4f}%")
lines.append(f"Total Fees: -{best.total_fees_pct:.4f}%")
lines.append(f"Net Profit: {best.net_profit_pct:+.4f}%")
return "\n".join(lines)
def format_profit_breakdown(self, breakdown: ProfitBreakdown) -> str:
"""Format profit breakdown."""
lines = []
lines.append(self._header("PROFIT BREAKDOWN"))
lines.append(f"Trade: {breakdown.trade_amount} {breakdown.pair.split('/')[0]}")
lines.append(f"Buy on {breakdown.buy_exchange} at ${breakdown.buy_price:,.2f}")
lines.append(f"Sell on {breakdown.sell_exchange} at ${breakdown.sell_price:,.2f}")
lines.append("")
lines.append(f"Gross Profit: ${breakdown.gross_profit:,.2f} ({breakdown.gross_profit_pct:+.3f}%)")
lines.append("")
lines.append("Costs:")
lines.append(f" Buy fee: -${breakdown.buy_fee:,.2f}")
lines.append(f" Sell fee: -${breakdown.sell_fee:,.2f}")
lines.append(f" Withdrawal: -${breakdown.withdrawal_fee:,.2f}")
lines.append(f" Gas: -${breakdown.gas_cost_usd:,.2f}")
lines.append(f" Slippage: -${breakdown.slippage_cost:,.2f}")
lines.append(f" {'-' * 30}")
lines.append(f" Total: -${breakdown.total_costs:,.2f}")
lines.append("")
lines.append(f"Net Profit: ${breakdown.net_profit:,.2f} ({breakdown.net_profit_pct:+.3f}%)")
lines.append(f"Breakeven spread: {breakdown.breakeven_spread_pct:.3f}%")
if breakdown.is_profitable:
lines.append("\n✓ PROFITABLE")
else:
lines.append("\n✗ NOT PROFITABLE")
return "\n".join(lines)
def format_alert(
self,
opp: ArbitrageOpportunity,
trade_amount: Optional[Decimal] = None,
) -> str:
"""Format an alert message for a detected opportunity."""
lines = []
lines.append("=" * 60)
lines.append("🚨 ARBITRAGE ALERT")
lines.append("=" * 60)
lines.append("")
lines.append(f"{opp.pair} spread {opp.net_spread_pct:+.3f}%")
lines.append(f"Buy on {opp.buy_exchange} at ${opp.buy_price:,.2f}")
lines.append(f"Sell on {opp.sell_exchange} at ${opp.sell_price:,.2f}")
if trade_amount:
profit = opp.profit_for_amount(trade_amount)
lines.append(f"")
lines.append(f"For {trade_amount} units:")
lines.append(f"Estimated profit: ${profit:,.2f}")
risk_icon, risk_label = self.RISK_INDICATORS.get(
opp.risk_level, ("⚪", "?")
)
lines.append("")
lines.append(f"Risk: {risk_icon} {risk_label}")
return "\n".join(lines)
class JSONFormatter:
"""Format output as JSON."""
def format_scan_result(self, result: ScanResult) -> str:
"""Format scan result as JSON."""
data = {
"pair": result.pair,
"timestamp": result.timestamp,
"quotes_count": len(result.quotes),
"opportunities_count": len(result.opportunities),
"quotes": [
{
"exchange": q.exchange,
"exchange_type": q.exchange_type.value,
"bid": float(q.bid),
"ask": float(q.ask),
"spread_pct": q.spread_pct,
"volume_24h": float(q.volume_24h),
}
for q in result.quotes
],
"opportunities": [
{
"buy_exchange": o.buy_exchange,
"sell_exchange": o.sell_exchange,
"buy_price": float(o.buy_price),
"sell_price": float(o.sell_price),
"gross_spread_pct": o.gross_spread_pct,
"net_spread_pct": o.net_spread_pct,
"risk_level": o.risk_level.value,
"is_profitable": o.is_profitable,
}
for o in result.opportunities
],
"best_opportunity": None,
}
if result.best_opportunity:
o = result.best_opportunity
data["best_opportunity"] = {
"buy_exchange": o.buy_exchange,
"sell_exchange": o.sell_exchange,
"buy_price": float(o.buy_price),
"sell_price": float(o.sell_price),
"net_spread_pct": o.net_spread_pct,
"risk_level": o.risk_level.value,
}
return json.dumps(data, indent=2, cls=DecimalEncoder)
def format_triangular_results(self, paths: List[ArbitragePath]) -> str:
"""Format triangular results as JSON."""
data = {
"paths_count": len(paths),
"paths": [
{
"tokens": p.tokens,
"exchange": p.exchange,
"gross_profit_pct": p.gross_profit_pct,
"total_fees_pct": p.total_fees_pct,
"net_profit_pct": p.net_profit_pct,
"is_profitable": p.is_profitable,
"steps": p.execution_steps,
}
for p in paths
],
}
return json.dumps(data, indent=2, cls=DecimalEncoder)
def format_profit_breakdown(self, breakdown: ProfitBreakdown) -> str:
"""Format profit breakdown as JSON."""
data = {
"pair": breakdown.pair,
"buy_exchange": breakdown.buy_exchange,
"sell_exchange": breakdown.sell_exchange,
"trade_amount": float(breakdown.trade_amount),
"buy_price": float(breakdown.buy_price),
"sell_price": float(breakdown.sell_price),
"gross_profit": float(breakdown.gross_profit),
"gross_profit_pct": breakdown.gross_profit_pct,
"costs": {
"buy_fee": float(breakdown.buy_fee),
"sell_fee": float(breakdown.sell_fee),
"withdrawal_fee": float(breakdown.withdrawal_fee),
"gas_cost_usd": float(breakdown.gas_cost_usd),
"slippage_cost": float(breakdown.slippage_cost),
"total": float(breakdown.total_costs),
},
"net_profit": float(breakdown.net_profit),
"net_profit_pct": breakdown.net_profit_pct,
"net_profit_usd": float(breakdown.net_profit_usd),
"breakeven_spread_pct": breakdown.breakeven_spread_pct,
"is_profitable": breakdown.is_profitable,
}
return json.dumps(data, indent=2, cls=DecimalEncoder)
def demo():
"""Demonstrate formatters."""
from opportunity_scanner import OpportunityScanner
# Run a scan
scanner = OpportunityScanner(min_profit_pct=0.0)
result = scanner.scan("ETH", "USDC")
# Console format
console = ConsoleFormatter()
print(console.format_scan_result(result))
# JSON format
print("\n" + "=" * 70)
print("JSON OUTPUT")
print("=" * 70)
json_fmt = JSONFormatter()
print(json_fmt.format_scan_result(result))
if __name__ == "__main__":
demo()
#!/usr/bin/env python3
"""
Arbitrage opportunity scanner.
Detects direct arbitrage opportunities (buy low, sell high)
across multiple exchanges with profit calculation.
"""
from dataclasses import dataclass
from decimal import Decimal
from enum import Enum
from typing import List, Optional, Tuple
from price_fetcher import PriceFetcher, PriceQuote, ExchangeType, ExchangeConfig
class OpportunityType(Enum):
"""Type of arbitrage opportunity."""
DIRECT = "DIRECT" # Buy on A, sell on B
TRIANGULAR = "TRIANGULAR" # A→B→C→A circular
CROSS_CHAIN = "CROSS_CHAIN" # Same asset across chains
class RiskLevel(Enum):
"""Risk level classification."""
LOW = "LOW"
MEDIUM = "MEDIUM"
HIGH = "HIGH"
EXTREME = "EXTREME"
@dataclass
class ArbitrageOpportunity:
"""Represents a single arbitrage opportunity."""
opportunity_type: OpportunityType
pair: str
buy_exchange: str
sell_exchange: str
buy_price: Decimal
sell_price: Decimal
gross_spread_pct: float
net_spread_pct: float
gross_profit_per_unit: Decimal
net_profit_per_unit: Decimal
buy_fee_pct: float
sell_fee_pct: float
estimated_gas_usd: float # For DEX
risk_level: RiskLevel
notes: List[str]
buy_exchange_type: ExchangeType
sell_exchange_type: ExchangeType
@property
def is_profitable(self) -> bool:
"""Check if opportunity is profitable after fees."""
return self.net_profit_per_unit > 0
def profit_for_amount(self, amount: Decimal) -> Decimal:
"""Calculate profit for a specific trade amount."""
return amount * self.net_profit_per_unit / self.buy_price
@dataclass
class ScanResult:
"""Result of an arbitrage scan."""
pair: str
quotes: List[PriceQuote]
opportunities: List[ArbitrageOpportunity]
best_opportunity: Optional[ArbitrageOpportunity]
timestamp: float
class OpportunityScanner:
"""
Scans for arbitrage opportunities across exchanges.
Features:
- Direct spread detection (CEX-to-CEX, DEX-to-DEX, CEX-to-DEX)
- Fee-aware profit calculation
- Risk assessment
"""
def __init__(
self,
fetcher: Optional[PriceFetcher] = None,
min_profit_pct: float = 0.1, # Minimum 0.1% profit
gas_price_gwei: float = 30.0,
eth_price_usd: float = 2500.0,
):
"""
Initialize scanner.
Args:
fetcher: Price fetcher instance
min_profit_pct: Minimum net profit percentage to report
gas_price_gwei: Current gas price in gwei
eth_price_usd: Current ETH price
"""
self.fetcher = fetcher or PriceFetcher(use_mock=True)
self.min_profit_pct = min_profit_pct
self.gas_price_gwei = gas_price_gwei
self.eth_price_usd = eth_price_usd
def scan(
self,
base: str,
quote: str,
exchanges: Optional[List[str]] = None,
exchange_type: Optional[ExchangeType] = None,
) -> ScanResult:
"""
Scan for arbitrage opportunities on a pair.
Args:
base: Base token (e.g., ETH)
quote: Quote token (e.g., USDC)
exchanges: Specific exchanges to scan
exchange_type: Filter by CEX or DEX
Returns:
ScanResult with all opportunities
"""
import time
# Fetch all prices
quotes = self.fetcher.fetch_all_prices_sync(
base, quote, exchanges, exchange_type
)
if len(quotes) < 2:
return ScanResult(
pair=f"{base}/{quote}",
quotes=quotes,
opportunities=[],
best_opportunity=None,
timestamp=time.time(),
)
# Find all opportunities
opportunities = []
for buy_quote in quotes:
for sell_quote in quotes:
if buy_quote.exchange == sell_quote.exchange:
continue
opp = self._evaluate_opportunity(buy_quote, sell_quote)
if opp and opp.net_spread_pct >= self.min_profit_pct:
opportunities.append(opp)
# Sort by net profit
opportunities.sort(key=lambda x: x.net_spread_pct, reverse=True)
return ScanResult(
pair=f"{base}/{quote}",
quotes=quotes,
opportunities=opportunities,
best_opportunity=opportunities[0] if opportunities else None,
timestamp=time.time(),
)
def _evaluate_opportunity(
self,
buy_quote: PriceQuote,
sell_quote: PriceQuote,
) -> Optional[ArbitrageOpportunity]:
"""
Evaluate a potential arbitrage opportunity.
Args:
buy_quote: Quote from buy exchange (use ask price)
sell_quote: Quote from sell exchange (use bid price)
Returns:
ArbitrageOpportunity or None if not profitable
"""
# Check basic profitability (sell price > buy price)
if sell_quote.bid <= buy_quote.ask:
return None
# Get exchange configs
buy_config = self.fetcher.get_exchange_config(
buy_quote.exchange.lower().replace(" ", "").replace("v3", "")
)
sell_config = self.fetcher.get_exchange_config(
sell_quote.exchange.lower().replace(" ", "").replace("v3", "")
)
# Default fees if config not found
buy_fee = buy_config.taker_fee if buy_config else Decimal("0.001")
sell_fee = sell_config.taker_fee if sell_config else Decimal("0.001")
# Calculate gross spread
gross_profit = sell_quote.bid - buy_quote.ask
gross_spread_pct = float(gross_profit / buy_quote.ask * 100)
# Calculate fees
buy_fee_amount = buy_quote.ask * buy_fee
sell_fee_amount = sell_quote.bid * sell_fee
total_fees = buy_fee_amount + sell_fee_amount
# Calculate gas costs (for DEX)
gas_cost_usd = 0.0
if buy_quote.exchange_type == ExchangeType.DEX:
gas_units = buy_config.gas_overhead if buy_config else 150000
gas_cost_usd += gas_units * self.gas_price_gwei * 1e-9 * self.eth_price_usd
if sell_quote.exchange_type == ExchangeType.DEX:
gas_units = sell_config.gas_overhead if sell_config else 150000
gas_cost_usd += gas_units * self.gas_price_gwei * 1e-9 * self.eth_price_usd
# Calculate net profit
net_profit = gross_profit - total_fees
net_spread_pct = float(net_profit / buy_quote.ask * 100)
# Assess risk
risk_level = self._assess_risk(buy_quote, sell_quote, net_spread_pct)
# Generate notes
notes = []
if not buy_quote.is_fresh or not sell_quote.is_fresh:
notes.append("Warning: Price data may be stale")
if buy_quote.exchange_type == ExchangeType.DEX:
notes.append(f"Buy requires on-chain tx (~${gas_cost_usd:.2f} gas)")
if sell_quote.exchange_type == ExchangeType.DEX:
notes.append(f"Sell requires on-chain tx")
if gross_spread_pct > 2.0:
notes.append("Large spread may indicate low liquidity or stale data")
return ArbitrageOpportunity(
opportunity_type=OpportunityType.DIRECT,
pair=buy_quote.pair,
buy_exchange=buy_quote.exchange,
sell_exchange=sell_quote.exchange,
buy_price=buy_quote.ask,
sell_price=sell_quote.bid,
gross_spread_pct=gross_spread_pct,
net_spread_pct=net_spread_pct,
gross_profit_per_unit=gross_profit,
net_profit_per_unit=net_profit,
buy_fee_pct=float(buy_fee) * 100,
sell_fee_pct=float(sell_fee) * 100,
estimated_gas_usd=gas_cost_usd,
risk_level=risk_level,
notes=notes,
buy_exchange_type=buy_quote.exchange_type,
sell_exchange_type=sell_quote.exchange_type,
)
def _assess_risk(
self,
buy_quote: PriceQuote,
sell_quote: PriceQuote,
net_spread_pct: float,
) -> RiskLevel:
"""Assess risk level of an opportunity."""
risk_score = 0
# Staleness
if buy_quote.staleness_seconds > 10:
risk_score += 2
if sell_quote.staleness_seconds > 10:
risk_score += 2
# Spread size (very large spreads are suspicious)
if net_spread_pct > 5.0:
risk_score += 3
elif net_spread_pct > 2.0:
risk_score += 1
# DEX involvement (execution uncertainty)
if buy_quote.exchange_type == ExchangeType.DEX:
risk_score += 1
if sell_quote.exchange_type == ExchangeType.DEX:
risk_score += 1
# Volume (low volume = slippage risk)
min_volume = min(buy_quote.volume_24h, sell_quote.volume_24h)
if min_volume < Decimal("1000"):
risk_score += 2
elif min_volume < Decimal("10000"):
risk_score += 1
# Map to risk level
if risk_score <= 1:
return RiskLevel.LOW
elif risk_score <= 3:
return RiskLevel.MEDIUM
elif risk_score <= 5:
return RiskLevel.HIGH
else:
return RiskLevel.EXTREME
def scan_multiple_pairs(
self,
pairs: List[Tuple[str, str]],
exchanges: Optional[List[str]] = None,
) -> List[ScanResult]:
"""Scan multiple pairs for opportunities."""
results = []
for base, quote in pairs:
result = self.scan(base, quote, exchanges)
results.append(result)
return results
def demo():
"""Demonstrate opportunity scanner."""
scanner = OpportunityScanner(min_profit_pct=0.0) # Show all
print("=" * 70)
print("ARBITRAGE OPPORTUNITY SCANNER")
print("=" * 70)
# Scan ETH/USDC
result = scanner.scan("ETH", "USDC")
print(f"\nScanned {len(result.quotes)} exchanges for ETH/USDC")
print(f"Found {len(result.opportunities)} opportunities\n")
if result.opportunities:
print(f"{'Buy On':<15} {'Sell On':<15} {'Gross':>8} {'Net':>8} {'Risk':<8}")
print("-" * 70)
for opp in result.opportunities[:10]:
profit_color = "+" if opp.is_profitable else "-"
print(
f"{opp.buy_exchange:<15} "
f"{opp.sell_exchange:<15} "
f"{profit_color}{opp.gross_spread_pct:>6.3f}% "
f"{profit_color}{opp.net_spread_pct:>6.3f}% "
f"{opp.risk_level.value:<8}"
)
if result.best_opportunity:
best = result.best_opportunity
print(f"\nBest Opportunity:")
print(f" Buy on {best.buy_exchange} at ${best.buy_price:,.2f}")
print(f" Sell on {best.sell_exchange} at ${best.sell_price:,.2f}")
print(f" Gross spread: {best.gross_spread_pct:.3f}%")
print(f" Net spread: {best.net_spread_pct:.3f}%")
print(f" Buy fee: {best.buy_fee_pct:.2f}%")
print(f" Sell fee: {best.sell_fee_pct:.2f}%")
if best.notes:
print(f" Notes: {'; '.join(best.notes)}")
else:
print("No profitable opportunities found (market is efficient)")
if __name__ == "__main__":
demo()
#!/usr/bin/env python3
"""
Multi-source price aggregation for arbitrage detection.
Fetches prices from:
- CoinGecko (free, aggregated)
- DEX subgraphs (Uniswap, SushiSwap)
- Mock exchange data (for simulation)
"""
import asyncio
import time
from dataclasses import dataclass, field
from decimal import Decimal
from enum import Enum
from typing import Dict, List, Optional, Tuple
import httpx
class ExchangeType(Enum):
"""Exchange type classification."""
CEX = "CEX"
DEX = "DEX"
@dataclass
class PriceQuote:
"""Price quote from a single source."""
exchange: str
exchange_type: ExchangeType
pair: str
bid: Decimal # Best buy price (you sell at this)
ask: Decimal # Best sell price (you buy at this)
mid: Decimal # Mid-market price
spread_pct: float # Bid-ask spread percentage
volume_24h: Decimal # 24h volume in base currency
timestamp: float # Unix timestamp
chain: str = "ethereum" # For DEX
@property
def is_fresh(self) -> bool:
"""Check if quote is fresh (< 30 seconds old)."""
return (time.time() - self.timestamp) < 30
@property
def staleness_seconds(self) -> float:
"""Get quote age in seconds."""
return time.time() - self.timestamp
@dataclass
class ExchangeConfig:
"""Configuration for an exchange."""
name: str
exchange_type: ExchangeType
maker_fee: Decimal
taker_fee: Decimal
withdrawal_fee: Decimal = Decimal("0") # Per withdrawal
gas_overhead: int = 0 # For DEX
chains: List[str] = field(default_factory=lambda: ["ethereum"])
class PriceFetcher:
"""
Multi-source price fetcher.
Aggregates prices from CEX and DEX sources with:
- Rate limiting
- Timeout handling
- Source reliability tracking
"""
# CEX fee structures (as of 2024)
CEX_CONFIGS = {
"binance": ExchangeConfig(
name="Binance",
exchange_type=ExchangeType.CEX,
maker_fee=Decimal("0.0010"), # 0.10%
taker_fee=Decimal("0.0010"),
),
"coinbase": ExchangeConfig(
name="Coinbase",
exchange_type=ExchangeType.CEX,
maker_fee=Decimal("0.0040"), # 0.40%
taker_fee=Decimal("0.0060"), # 0.60%
),
"kraken": ExchangeConfig(
name="Kraken",
exchange_type=ExchangeType.CEX,
maker_fee=Decimal("0.0016"), # 0.16%
taker_fee=Decimal("0.0026"), # 0.26%
),
"kucoin": ExchangeConfig(
name="KuCoin",
exchange_type=ExchangeType.CEX,
maker_fee=Decimal("0.0010"),
taker_fee=Decimal("0.0010"),
),
"okx": ExchangeConfig(
name="OKX",
exchange_type=ExchangeType.CEX,
maker_fee=Decimal("0.0008"), # 0.08%
taker_fee=Decimal("0.0010"),
),
}
# DEX fee structures
DEX_CONFIGS = {
"uniswap": ExchangeConfig(
name="Uniswap V3",
exchange_type=ExchangeType.DEX,
maker_fee=Decimal("0.0030"), # 0.30% (most common tier)
taker_fee=Decimal("0.0030"),
gas_overhead=150000,
chains=["ethereum", "polygon", "arbitrum", "optimism"],
),
"sushiswap": ExchangeConfig(
name="SushiSwap",
exchange_type=ExchangeType.DEX,
maker_fee=Decimal("0.0030"),
taker_fee=Decimal("0.0030"),
gas_overhead=150000,
chains=["ethereum", "polygon", "arbitrum"],
),
"curve": ExchangeConfig(
name="Curve",
exchange_type=ExchangeType.DEX,
maker_fee=Decimal("0.0004"), # 0.04%
taker_fee=Decimal("0.0004"),
gas_overhead=200000,
chains=["ethereum", "polygon", "arbitrum"],
),
"balancer": ExchangeConfig(
name="Balancer",
exchange_type=ExchangeType.DEX,
maker_fee=Decimal("0.0010"), # Varies by pool
taker_fee=Decimal("0.0010"),
gas_overhead=180000,
chains=["ethereum", "polygon", "arbitrum"],
),
}
# Mock prices for simulation (simulate real market spreads)
MOCK_PRICES = {
("ETH", "USDC"): {
"binance": {"bid": 2541.20, "ask": 2541.50, "volume": 125000},
"coinbase": {"bid": 2543.80, "ask": 2544.10, "volume": 45000},
"kraken": {"bid": 2542.50, "ask": 2543.00, "volume": 35000},
"kucoin": {"bid": 2540.90, "ask": 2541.40, "volume": 28000},
"okx": {"bid": 2541.00, "ask": 2541.60, "volume": 52000},
"uniswap": {"bid": 2542.10, "ask": 2542.80, "volume": 85000},
"sushiswap": {"bid": 2540.50, "ask": 2541.30, "volume": 22000},
},
("BTC", "USDC"): {
"binance": {"bid": 67850.00, "ask": 67865.00, "volume": 4500},
"coinbase": {"bid": 67920.00, "ask": 67950.00, "volume": 2100},
"kraken": {"bid": 67880.00, "ask": 67910.00, "volume": 1800},
"kucoin": {"bid": 67840.00, "ask": 67870.00, "volume": 1200},
"okx": {"bid": 67855.00, "ask": 67880.00, "volume": 3200},
"uniswap": {"bid": 67870.00, "ask": 67920.00, "volume": 950},
},
("ETH", "BTC"): {
"binance": {"bid": 0.03745, "ask": 0.03748, "volume": 8500},
"coinbase": {"bid": 0.03752, "ask": 0.03756, "volume": 3200},
"kraken": {"bid": 0.03748, "ask": 0.03752, "volume": 2100},
"kucoin": {"bid": 0.03744, "ask": 0.03749, "volume": 1800},
"okx": {"bid": 0.03746, "ask": 0.03750, "volume": 4100},
},
("USDC", "USDT"): {
"binance": {"bid": 0.9998, "ask": 1.0002, "volume": 500000},
"coinbase": {"bid": 0.9997, "ask": 1.0003, "volume": 120000},
"curve": {"bid": 0.99995, "ask": 1.00005, "volume": 2500000},
},
}
def __init__(self, use_mock: bool = True, timeout: float = 10.0):
"""
Initialize price fetcher.
Args:
use_mock: Use mock data instead of live APIs
timeout: Request timeout in seconds
"""
self.use_mock = use_mock
self.timeout = timeout
self.client = httpx.AsyncClient(timeout=timeout)
def get_exchange_config(self, exchange: str) -> Optional[ExchangeConfig]:
"""Get exchange configuration."""
exchange_lower = exchange.lower()
if exchange_lower in self.CEX_CONFIGS:
return self.CEX_CONFIGS[exchange_lower]
if exchange_lower in self.DEX_CONFIGS:
return self.DEX_CONFIGS[exchange_lower]
return None
def list_exchanges(self, exchange_type: Optional[ExchangeType] = None) -> List[str]:
"""List available exchanges."""
result = []
if exchange_type is None or exchange_type == ExchangeType.CEX:
result.extend(self.CEX_CONFIGS.keys())
if exchange_type is None or exchange_type == ExchangeType.DEX:
result.extend(self.DEX_CONFIGS.keys())
return result
async def fetch_price(
self,
base: str,
quote: str,
exchange: str,
) -> Optional[PriceQuote]:
"""
Fetch price from a single exchange.
Args:
base: Base token (e.g., ETH)
quote: Quote token (e.g., USDC)
exchange: Exchange name
Returns:
PriceQuote or None if unavailable
"""
if self.use_mock:
return self._get_mock_price(base, quote, exchange)
# Real API integration would go here
# For now, fall back to mock
return self._get_mock_price(base, quote, exchange)
def _get_mock_price(
self,
base: str,
quote: str,
exchange: str,
) -> Optional[PriceQuote]:
"""Get mock price data."""
pair_key = (base.upper(), quote.upper())
exchange_lower = exchange.lower()
# Try direct pair
if pair_key in self.MOCK_PRICES:
prices = self.MOCK_PRICES[pair_key]
if exchange_lower in prices:
data = prices[exchange_lower]
return self._build_quote(base, quote, exchange_lower, data)
# Try inverse pair
inverse_key = (quote.upper(), base.upper())
if inverse_key in self.MOCK_PRICES:
prices = self.MOCK_PRICES[inverse_key]
if exchange_lower in prices:
data = prices[exchange_lower]
# Invert prices
inverted = {
"bid": 1.0 / data["ask"],
"ask": 1.0 / data["bid"],
"volume": data["volume"],
}
return self._build_quote(base, quote, exchange_lower, inverted)
return None
def _build_quote(
self,
base: str,
quote: str,
exchange: str,
data: dict,
) -> PriceQuote:
"""Build PriceQuote from raw data."""
config = self.get_exchange_config(exchange)
bid = Decimal(str(data["bid"]))
ask = Decimal(str(data["ask"]))
mid = (bid + ask) / 2
spread_pct = float((ask - bid) / mid * 100)
return PriceQuote(
exchange=config.name if config else exchange,
exchange_type=config.exchange_type if config else ExchangeType.CEX,
pair=f"{base}/{quote}",
bid=bid,
ask=ask,
mid=mid,
spread_pct=spread_pct,
volume_24h=Decimal(str(data["volume"])),
timestamp=time.time(),
chain="ethereum",
)
async def fetch_all_prices(
self,
base: str,
quote: str,
exchanges: Optional[List[str]] = None,
exchange_type: Optional[ExchangeType] = None,
) -> List[PriceQuote]:
"""
Fetch prices from multiple exchanges.
Args:
base: Base token
quote: Quote token
exchanges: Specific exchanges (default: all)
exchange_type: Filter by CEX or DEX
Returns:
List of PriceQuote objects
"""
if exchanges is None:
exchanges = self.list_exchanges(exchange_type)
# Fetch concurrently
tasks = [
self.fetch_price(base, quote, ex)
for ex in exchanges
]
results = await asyncio.gather(*tasks)
# Filter None results
return [r for r in results if r is not None]
def fetch_all_prices_sync(
self,
base: str,
quote: str,
exchanges: Optional[List[str]] = None,
exchange_type: Optional[ExchangeType] = None,
) -> List[PriceQuote]:
"""Synchronous wrapper for fetch_all_prices."""
return asyncio.run(
self.fetch_all_prices(base, quote, exchanges, exchange_type)
)
async def close(self):
"""Close HTTP client."""
await self.client.aclose()
def demo():
"""Demonstrate price fetcher."""
fetcher = PriceFetcher(use_mock=True)
print("=" * 60)
print("PRICE FETCHER DEMO")
print("=" * 60)
# Fetch ETH/USDC prices
prices = fetcher.fetch_all_prices_sync("ETH", "USDC")
print(f"\nETH/USDC prices across {len(prices)} exchanges:\n")
print(f"{'Exchange':<15} {'Bid':>12} {'Ask':>12} {'Spread':>8} {'Volume':>12}")
print("-" * 60)
for quote in sorted(prices, key=lambda x: x.bid, reverse=True):
print(
f"{quote.exchange:<15} "
f"${quote.bid:>11,.2f} "
f"${quote.ask:>11,.2f} "
f"{quote.spread_pct:>7.3f}% "
f"{quote.volume_24h:>11,.0f}"
)
# Show best opportunity
if len(prices) >= 2:
sorted_by_bid = sorted(prices, key=lambda x: x.bid, reverse=True)
sorted_by_ask = sorted(prices, key=lambda x: x.ask)
best_sell = sorted_by_bid[0]
best_buy = sorted_by_ask[0]
if best_sell.bid > best_buy.ask:
spread = float((best_sell.bid - best_buy.ask) / best_buy.ask * 100)
print(f"\nBest opportunity:")
print(f" Buy on {best_buy.exchange} at ${best_buy.ask:,.2f}")
print(f" Sell on {best_sell.exchange} at ${best_sell.bid:,.2f}")
print(f" Gross spread: {spread:.3f}%")
if __name__ == "__main__":
demo()
#!/usr/bin/env python3
"""
Arbitrage profit calculator.
Calculates exact profit after all costs including:
- Trading fees (maker/taker)
- Withdrawal fees
- Gas costs (for DEX)
- Slippage estimates
"""
from dataclasses import dataclass
from decimal import Decimal
from typing import Optional
from price_fetcher import ExchangeType
@dataclass
class ProfitBreakdown:
"""Detailed profit breakdown for an arbitrage trade."""
# Trade details
pair: str
buy_exchange: str
sell_exchange: str
trade_amount: Decimal
buy_price: Decimal
sell_price: Decimal
# Gross profit
gross_profit: Decimal
gross_profit_pct: float
# Costs
buy_fee: Decimal
sell_fee: Decimal
withdrawal_fee: Decimal
gas_cost_usd: Decimal
slippage_cost: Decimal
total_costs: Decimal
# Net profit
net_profit: Decimal
net_profit_pct: float
net_profit_usd: Decimal
# Analysis
breakeven_spread_pct: float
profit_per_dollar: Decimal
is_profitable: bool
@dataclass
class SlippageEstimate:
"""Slippage estimate based on trade size and liquidity."""
base_slippage_pct: float
size_factor: float
liquidity_factor: float
total_slippage_pct: float
slippage_cost: Decimal
class ProfitCalculator:
"""
Calculates detailed profit for arbitrage opportunities.
Accounts for all costs and provides breakeven analysis.
"""
# Fee structures by exchange
EXCHANGE_FEES = {
"binance": {"maker": 0.0010, "taker": 0.0010, "withdrawal": 0.0005},
"coinbase": {"maker": 0.0040, "taker": 0.0060, "withdrawal": 0.0000},
"kraken": {"maker": 0.0016, "taker": 0.0026, "withdrawal": 0.0010},
"kucoin": {"maker": 0.0010, "taker": 0.0010, "withdrawal": 0.0003},
"okx": {"maker": 0.0008, "taker": 0.0010, "withdrawal": 0.0004},
"uniswap": {"maker": 0.0030, "taker": 0.0030, "withdrawal": 0.0000},
"sushiswap": {"maker": 0.0030, "taker": 0.0030, "withdrawal": 0.0000},
"curve": {"maker": 0.0004, "taker": 0.0004, "withdrawal": 0.0000},
"balancer": {"maker": 0.0010, "taker": 0.0010, "withdrawal": 0.0000},
}
# Gas costs by operation
GAS_COSTS = {
"uniswap_swap": 150000,
"sushiswap_swap": 150000,
"curve_swap": 200000,
"balancer_swap": 180000,
"erc20_transfer": 65000,
}
def __init__(
self,
gas_price_gwei: float = 30.0,
eth_price_usd: float = 2500.0,
default_slippage_pct: float = 0.1,
):
"""
Initialize calculator.
Args:
gas_price_gwei: Current gas price in gwei
eth_price_usd: Current ETH price
default_slippage_pct: Default slippage assumption
"""
self.gas_price_gwei = gas_price_gwei
self.eth_price_usd = eth_price_usd
self.default_slippage_pct = default_slippage_pct
def calculate(
self,
pair: str,
buy_exchange: str,
sell_exchange: str,
buy_price: Decimal,
sell_price: Decimal,
amount: Decimal,
include_withdrawal: bool = True,
buy_exchange_type: ExchangeType = ExchangeType.CEX,
sell_exchange_type: ExchangeType = ExchangeType.CEX,
) -> ProfitBreakdown:
"""
Calculate detailed profit breakdown.
Args:
pair: Trading pair (e.g., "ETH/USDC")
buy_exchange: Exchange to buy on
sell_exchange: Exchange to sell on
buy_price: Price to buy at
sell_price: Price to sell at
amount: Amount to trade (in base currency)
include_withdrawal: Include withdrawal fee
buy_exchange_type: CEX or DEX
sell_exchange_type: CEX or DEX
Returns:
ProfitBreakdown with all details
"""
# Get fee rates
buy_fees = self._get_fees(buy_exchange)
sell_fees = self._get_fees(sell_exchange)
# Calculate gross profit
buy_cost = amount * buy_price
sell_revenue = amount * sell_price
gross_profit = sell_revenue - buy_cost
gross_profit_pct = float(gross_profit / buy_cost * 100)
# Calculate trading fees
buy_fee = buy_cost * Decimal(str(buy_fees["taker"]))
sell_fee = sell_revenue * Decimal(str(sell_fees["taker"]))
# Withdrawal fee (if moving between exchanges)
withdrawal_fee = Decimal("0")
if include_withdrawal:
withdrawal_fee = amount * Decimal(str(buy_fees["withdrawal"]))
# Gas costs (for DEX)
gas_cost_usd = Decimal("0")
if buy_exchange_type == ExchangeType.DEX:
gas_units = self.GAS_COSTS.get(f"{buy_exchange.lower()}_swap", 150000)
gas_cost_usd += Decimal(str(
gas_units * self.gas_price_gwei * 1e-9 * self.eth_price_usd
))
if sell_exchange_type == ExchangeType.DEX:
gas_units = self.GAS_COSTS.get(f"{sell_exchange.lower()}_swap", 150000)
gas_cost_usd += Decimal(str(
gas_units * self.gas_price_gwei * 1e-9 * self.eth_price_usd
))
# Slippage estimate
slippage = self.estimate_slippage(amount, buy_price)
slippage_cost = slippage.slippage_cost
# Total costs
total_costs = buy_fee + sell_fee + withdrawal_fee + slippage_cost
# Add gas cost (convert to base currency)
if gas_cost_usd > 0:
total_costs += gas_cost_usd / buy_price
# Net profit
net_profit = gross_profit - total_costs
net_profit_pct = float(net_profit / buy_cost * 100) if buy_cost > 0 else 0
net_profit_usd = net_profit * buy_price # Convert to USD
# Breakeven analysis
total_fee_pct = float((buy_fee + sell_fee) / buy_cost * 100) if buy_cost > 0 else 0
breakeven_spread_pct = total_fee_pct + float(slippage.total_slippage_pct)
# Profit per dollar invested
profit_per_dollar = net_profit_usd / buy_cost if buy_cost > 0 else Decimal("0")
return ProfitBreakdown(
pair=pair,
buy_exchange=buy_exchange,
sell_exchange=sell_exchange,
trade_amount=amount,
buy_price=buy_price,
sell_price=sell_price,
gross_profit=gross_profit,
gross_profit_pct=gross_profit_pct,
buy_fee=buy_fee,
sell_fee=sell_fee,
withdrawal_fee=withdrawal_fee,
gas_cost_usd=gas_cost_usd,
slippage_cost=slippage_cost,
total_costs=total_costs,
net_profit=net_profit,
net_profit_pct=net_profit_pct,
net_profit_usd=net_profit_usd,
breakeven_spread_pct=breakeven_spread_pct,
profit_per_dollar=profit_per_dollar,
is_profitable=net_profit > 0,
)
def _get_fees(self, exchange: str) -> dict:
"""Get fee structure for an exchange."""
exchange_lower = exchange.lower().replace(" ", "").replace("v3", "")
return self.EXCHANGE_FEES.get(
exchange_lower,
{"maker": 0.001, "taker": 0.001, "withdrawal": 0.0005}
)
def estimate_slippage(
self,
amount: Decimal,
price: Decimal,
liquidity_usd: Decimal = Decimal("1000000"),
) -> SlippageEstimate:
"""
Estimate slippage based on trade size.
Simplified model:
- Base slippage for small trades
- Size factor increases with trade size
- Liquidity factor based on pool depth
"""
trade_value = amount * price
# Base slippage
base_slippage = self.default_slippage_pct
# Size factor (larger trades = more slippage)
size_pct = float(trade_value / liquidity_usd * 100)
if size_pct < 0.1:
size_factor = 1.0
elif size_pct < 1.0:
size_factor = 1.5
elif size_pct < 5.0:
size_factor = 2.5
else:
size_factor = 5.0
# Liquidity factor
if liquidity_usd < Decimal("100000"):
liquidity_factor = 2.0
elif liquidity_usd < Decimal("1000000"):
liquidity_factor = 1.5
else:
liquidity_factor = 1.0
# Total slippage
total_slippage_pct = base_slippage * size_factor * liquidity_factor
slippage_cost = trade_value * Decimal(str(total_slippage_pct / 100))
return SlippageEstimate(
base_slippage_pct=base_slippage,
size_factor=size_factor,
liquidity_factor=liquidity_factor,
total_slippage_pct=total_slippage_pct,
slippage_cost=slippage_cost,
)
def calculate_minimum_amount(
self,
buy_exchange: str,
sell_exchange: str,
spread_pct: float,
target_profit_usd: Decimal = Decimal("10"),
) -> Decimal:
"""
Calculate minimum trade amount to achieve target profit.
Args:
buy_exchange: Exchange to buy on
sell_exchange: Exchange to sell on
spread_pct: Current spread percentage
target_profit_usd: Target profit in USD
Returns:
Minimum trade amount in USD
"""
buy_fees = self._get_fees(buy_exchange)
sell_fees = self._get_fees(sell_exchange)
# Total fee percentage
total_fee_pct = (
buy_fees["taker"] + sell_fees["taker"] +
buy_fees["withdrawal"] + self.default_slippage_pct / 100
)
# Net spread after fees
net_spread_pct = spread_pct / 100 - total_fee_pct
if net_spread_pct <= 0:
return Decimal("-1") # Not profitable at any size
# Amount needed for target profit
# profit = amount * net_spread_pct
# amount = profit / net_spread_pct
return target_profit_usd / Decimal(str(net_spread_pct))
def demo():
"""Demonstrate profit calculator."""
calc = ProfitCalculator(gas_price_gwei=30.0, eth_price_usd=2500.0)
print("=" * 70)
print("ARBITRAGE PROFIT CALCULATOR")
print("=" * 70)
# Calculate profit for ETH/USDC arbitrage
breakdown = calc.calculate(
pair="ETH/USDC",
buy_exchange="binance",
sell_exchange="coinbase",
buy_price=Decimal("2541.50"),
sell_price=Decimal("2543.80"),
amount=Decimal("10"), # 10 ETH
buy_exchange_type=ExchangeType.CEX,
sell_exchange_type=ExchangeType.CEX,
)
print(f"\nTrade: {breakdown.trade_amount} ETH/USDC")
print(f"Buy on {breakdown.buy_exchange} at ${breakdown.buy_price:,.2f}")
print(f"Sell on {breakdown.sell_exchange} at ${breakdown.sell_price:,.2f}")
print(f"\n{'─' * 50}")
print("PROFIT BREAKDOWN")
print(f"{'─' * 50}")
print(f"\nGross Profit: ${breakdown.gross_profit:,.2f} ({breakdown.gross_profit_pct:+.3f}%)")
print(f"\nCosts:")
print(f" Buy fee ({breakdown.buy_exchange}): ${breakdown.buy_fee:,.2f}")
print(f" Sell fee ({breakdown.sell_exchange}): ${breakdown.sell_fee:,.2f}")
print(f" Withdrawal fee: ${breakdown.withdrawal_fee:,.2f}")
print(f" Gas cost: ${breakdown.gas_cost_usd:,.2f}")
print(f" Est. slippage: ${breakdown.slippage_cost:,.2f}")
print(f" {'─' * 30}")
print(f" Total costs: ${breakdown.total_costs:,.2f}")
print(f"\nNet Profit: ${breakdown.net_profit:,.2f} ({breakdown.net_profit_pct:+.3f}%)")
print(f"Net Profit (USD): ${breakdown.net_profit_usd:,.2f}")
print(f"\nBreakeven spread: {breakdown.breakeven_spread_pct:.3f}%")
print(f"Profit per $1000: ${float(breakdown.profit_per_dollar) * 1000:.2f}")
if breakdown.is_profitable:
print(f"\n✓ PROFITABLE")
else:
print(f"\n✗ NOT PROFITABLE")
# Calculate minimum amount
print(f"\n{'─' * 50}")
print("MINIMUM TRADE ANALYSIS")
print(f"{'─' * 50}")
min_amount = calc.calculate_minimum_amount(
buy_exchange="binance",
sell_exchange="coinbase",
spread_pct=0.09, # 0.09% spread
target_profit_usd=Decimal("100"),
)
if min_amount > 0:
print(f"\nTo make $100 profit with 0.09% spread:")
print(f"Minimum trade: ${min_amount:,.2f}")
else:
print(f"\n0.09% spread is not profitable after fees")
if __name__ == "__main__":
demo()