Intra-Venue Arbitrage
This document explains the intra-venue arbitrage system that finds internal inconsistencies within a single prediction market venue.
Overview
Intra-venue arbitrage identifies related contracts that should sum to 1 after fees, such as: - Binary Mirrors: YES/NO pairs for the same event - Bucketed Ranges: Non-overlapping numeric intervals (e.g., 0-10, 10-20, 20-30) - Over/Under Variants: Single strike over/under pairs
Mathematical Foundation
Binary Mirrors
For YES/NO pairs, the relationship should be:
p_yes + p_no = 1 + fee_adjustment
Where:
- p_yes = price of YES contract (including taker fees)
- p_no = price of NO contract (including taker fees)
- fee_adjustment = venue-specific fee structure
Gross Edge: (p_yes + p_no) - 1
Net Edge: gross_edge - taker_fees - expected_slippage - withdrawal_costs
Bucketed Ranges
For non-overlapping ranges that should cover the full probability space:
sum(p_i) = 1 + fee_adjustment
Where p_i is the price of the i-th range contract.
Over/Under Variants
For over/under contracts with the same strike:
p_over + p_under = 1 + fee_adjustment
Implementation
Contract Grouping
The system groups related contracts using:
-
Canonical Event ID: Normalized event title with: - Date/time normalization (
2024-12-31→[DATE]) - Exchange boilerplate removal ((Polymarket)→ removed) - Case normalization and punctuation removal -
Pattern Detection: - Binary: Detects YES/NO pairs with same base event - Ranges: Parses numeric intervals in titles - Over/Under: Identifies strike-based over/under pairs
Edge Computation
# Binary mirror example
gross_edge = (yes_price + no_price) - 1.0
net_edge = gross_edge - taker_fees - expected_slippage - withdrawal_costs
Risk Management
- Position Sizing: Neutralize resolution risk by taking opposite positions
- Risk Budget: Maximum 20% of account balance per trade
- Liquidity Requirements: Minimum liquidity threshold per contract
- Slippage Protection: Maximum slippage tolerance in basis points
Execution Safety
- Atomic Execution: Place all orders within 250-500ms window
- Cancel-on-Fail: Cancel remaining orders if first leg fills < 90%
- Price Bands: Do not chase prices beyond slippage tolerance
- Order Types: Use IOC (Immediate or Cancel) for atomicity
Configuration
Environment Variables
# Enable intra-venue arbitrage
INTRA_ARB_ENABLED=true
# Risk management
INTRA_ARB_RISK_BUDGET_EUR=200
INTRA_ARB_MAX_POSITION_SIZE_EUR=50
# Edge detection thresholds
INTRA_ARB_MIN_NET_EDGE=0.012 # 1.2%
INTRA_ARB_MAX_SLIPPAGE_BPS=15
INTRA_ARB_MIN_LIQUID_SIZE=150.0
# Execution parameters
INTRA_ARB_ORDER_TIMEOUT_MS=1200
INTRA_ARB_CANCEL_IF_FILL_RATIO_LT=0.9
# Fee models
POLYMARKET_TAKER_FEE=0.02 # 2%
KALSHI_TAKER_FEE=0.0 # 0%
WITHDRAWAL_COST_EUR=0.0
Default Configuration
MIN_NET_EDGE = 0.012 # 1.2%
MAX_SLIPPAGE_BPS = 15
MIN_LIQUID_SIZE = 150.0 # EUR notional
ORDER_TIMEOUT_MS = 1200
CANCEL_IF_FILL_RATIO_LT = 0.9
Usage
CLI Interface
# Scan Polymarket for opportunities (dry run)
python intraarb.py --venue=polymarket --dryrun
# Scan Kalshi for opportunities (live execution)
python intraarb.py --venue=kalshi --dryrun=false
Programmatic Usage
from arbitrage.intra_venue import group_related, detect_inconsistencies, create_order_plan, execute_arbitrage
from arbitrage.intra_venue.config import IntraArbConfig
# Load markets
markets = load_markets("polymarket")
# Group related contracts
groups = group_related(markets)
# Detect inconsistencies
config = IntraArbConfig.from_env()
results = detect_inconsistencies(groups, config)
# Create and execute order plans
for result in results:
if result.is_tradeable:
plan = create_order_plan(result, config)
execution_result = await execute_arbitrage(plan, config, dry_run=True)
Examples
Binary Mirror Example
Contracts: - YES: “Will Bitcoin reach $100k by 2024?” @ 0.45 - NO: “Will Bitcoin NOT reach $100k by 2024?” @ 0.52
Analysis: - Gross Edge: (0.45 + 0.52) - 1.0 = -0.03 (3% edge against us) - Net Edge: -0.03 - 0.02 - 0.0015 - 0.0 = -0.0515 (5.15% against us) - Result: Not tradeable (negative edge)
Profitable Example
Contracts: - YES: “Will Ethereum reach $10k by 2024?” @ 0.30 - NO: “Will Ethereum NOT reach $10k by 2024?” @ 0.75
Analysis: - Gross Edge: (0.30 + 0.75) - 1.0 = 0.05 (5% edge in our favor) - Net Edge: 0.05 - 0.02 - 0.0015 - 0.0 = 0.0285 (2.85% net edge) - Result: Tradeable (exceeds 1.2% minimum)
Execution: - Buy YES: $100 @ 0.30 - Sell NO: $100 @ 0.75 - Expected Profit: $2.85 - Risk: Neutralized (opposite positions)
Caveats and Considerations
Resolution Criteria Mismatches
Critical: Always verify that contracts have identical resolution criteria before trading:
- ✅ Same event description
- ✅ Same resolution date/time
- ✅ Same resolution source
- ✅ Same settlement method
Example of Mismatch: - Contract A: “Will Bitcoin reach $100k by 2024-12-31 23:59 UTC?” - Contract B: “Will Bitcoin reach $100k by 2024-12-31 15:00 EST?”
These contracts have different resolution times and should NOT be arbitraged.
Market Structure Risks
- Liquidity Risk: Large positions may move prices
- Execution Risk: Orders may not fill at expected prices
- Settlement Risk: Different settlement mechanisms
- Fee Risk: Fee structures may change
Monitoring Checklist
Before trading any arbitrage opportunity:
- [ ] Verify identical resolution criteria
- [ ] Check recent price movements
- [ ] Confirm sufficient liquidity
- [ ] Validate fee calculations
- [ ] Test order placement (dry run)
- [ ] Monitor execution quality
Testing
Unit Tests
# Run all tests
python -m pytest arbitrage/intra_venue/tests/
# Run specific test file
python -m pytest arbitrage/intra_venue/tests/test_relations.py
# Run with coverage
python -m pytest --cov=arbitrage.intra_venue arbitrage/intra_venue/tests/
Replay Tests
The system includes replay tests that use recorded orderbook snapshots to verify: - Edge calculation accuracy - Order plan generation - Execution simulation - Risk management
Property Tests
- Fee Impact: Higher fees should decrease net edge
- Liquidity Impact: Lower liquidity should increase rejection rate
- Position Neutrality: Opposite positions should neutralize resolution risk
Performance
Scalability
- Market Processing: O(n²) for n markets (can be optimized with indexing)
- Group Detection: O(n) for n markets
- Edge Calculation: O(1) per group
- Order Planning: O(1) per group
Memory Usage
- Market Data: ~1KB per market
- Group Data: ~2KB per group
- Order Plans: ~1KB per plan
- Total: ~10MB for 10,000 markets
Latency
- Discovery: 1-5 seconds for 10,000 markets
- Edge Calculation: <1ms per group
- Order Planning: <1ms per group
- Execution: 100-500ms per arbitrage
Troubleshooting
Common Issues
- No Groups Found: Check market data quality and parsing logic
- No Tradeable Opportunities: Lower
min_net_edgeor check market conditions - Execution Failures: Verify order placement logic and venue connectivity
- High Rejection Rate: Check liquidity thresholds and fee models
Debug Mode
Enable debug logging to see detailed processing:
import logging
logging.basicConfig(level=logging.DEBUG)
Monitoring
Monitor key metrics: - Groups detected per scan - Tradeable opportunities found - Execution success rate - Average profit per trade - Risk utilization
Future Enhancements
- Multi-Venue Support: Extend to cross-venue arbitrage
- Machine Learning: Improve pattern detection with ML
- Real-time Processing: Stream processing for live opportunities
- Advanced Risk Models: Dynamic position sizing based on volatility
- Portfolio Management: Coordinate multiple arbitrage positions