A high-frequency market-neutral spread arbitrage trading strategy for BZ/USDT (Brent Crude) and CL/USDT (WTI Crude) perpetual swaps on Bybit, built with the NautilusTrader algorithmic trading framework.
- Overview
- Features
- Strategy Logic
- Architecture
- Installation
- Configuration
- Usage
- Monitoring
- Risk Management
- Performance
- Troubleshooting
- Contributing
- Disclaimer
- License
CrudeOilArb is an automated trading strategy that exploits price spreads between two highly correlated crude oil futures:
- BZ (Brent Crude): Brent crude oil futures perpetual swap
- CL (WTI Crude): West Texas Intermediate crude oil futures perpetual swap
By trading perpetual swap contracts on Bybit, the strategy captures arbitrage opportunities while maintaining a market-neutral hedged position.
- Market Neutral: Long one asset, short the other - no directional exposure
- Grid-Based: Multiple price levels with predefined entry/exit points
- High Frequency: Real-time quote monitoring and rapid order execution
- Risk Controlled: Built-in position limits, notional caps, and extreme spread stops
- Maker Orders: Uses limit orders to earn maker rebates and minimize fees
- โ Real-time market data streaming from Bybit WebSocket
- โ Automated quote tick subscription for accurate spread calculation
- โ Multi-level grid trading with configurable spread thresholds
- โ Position reconciliation on startup and periodic snapshots
- โ Comprehensive logging (JSON format, DEBUG/INFO levels)
- โ Docker containerization for easy deployment
- โ Testnet support for risk-free testing
- โ Emergency stop mechanisms for extreme market conditions
- โ Graceful shutdown with order cancellation and position management
-
Spread Calculation
spread = (BZ_price - CL_price) / CL_price -
Grid Entry Logic
- When
spread > grid_level: Open hedged position- SELL BZ (expensive asset)
- BUY CL (cheap asset)
- Each grid level represents a specific spread threshold (e.g., 0.10%, 0.20%, etc.)
- When
-
Grid Exit Logic
- When spread reverts below the grid level: Close position
- Profit is locked in from spread convergence
-
Order Execution
- Opening orders are market orders (fast execution)
- Closing orders are limit orders (maker rebates)
- 60-second timeout for unfilled orders (cancel and resubmit)
1. Initial State:
BZ = $72.00
CL = $68.80
Spread = 4.65% (above grid levels)
2. Entry:
โ SELL 1.5 BZ @ $72.00
โ BUY 1.5 CL @ $68.80
โ Position opened at grid level
3. Spread Converges:
BZ = $71.00
CL = $68.50
Spread = 3.65% (below previous level)
4. Exit:
โ BUY 1.5 BZ @ $71.00 (close short)
โ SELL 1.5 CL @ $68.50 (close long)
โ Profit realized from spread convergence
- Framework: NautilusTrader v1.221.0
- Exchange: Bybit (Unified Trading Account)
- Language: Python 3.12
- Containerization: Docker with multi-stage builds
- Logging: Structured JSON logging with rotation
CrudeOilArb/
โโโ bz_cl_grid_strategy.py # Core strategy implementation
โโโ config_live.py # Live trading configuration
โโโ run_live.py # Main entry point
โโโ check_spread.py # Utility to check current spread
โโโ close_all_positions.py # Emergency position closer
โโโ requirements.txt # Python dependencies
โโโ Dockerfile # Docker image definition
โโโ .env.example # Environment template
โโโ .dockerignore # Docker ignore patterns
โโโ logs/ # Trading logs (auto-created)
โ โโโ bz_cl_grid.json
โโโ data/ # Historical data (optional)
โโโ README.md # This file
- CPU: 2+ cores (ARM64 or x86_64)
- RAM: 1GB minimum, 2GB recommended
- Storage: 5GB for Docker images and logs
- Network: Stable internet connection (low latency preferred)
- OS: Linux (Ubuntu 20.04+), macOS, or Windows with WSL2
Docker provides the easiest and most reliable deployment method.
- Docker 20.10+
- Docker Compose 1.29+
- Bybit API credentials
-
Clone the repository
git clone https://github.com/Patrick-code-Bot/CrudeOilArb.git cd CrudeOilArb -
Configure environment variables
cp .env.example .env nano .env # Edit with your API credentialsRequired variables:
BYBIT_API_KEY=your_api_key_here BYBIT_API_SECRET=your_api_secret_here BYBIT_TESTNET=false # Set to 'true' for testnet -
Build and run with Docker Compose (if using orchestration)
cd ../trading-deployment docker-compose up -d crudeoilarbOr run standalone:
docker build -t crudeoilarb . docker run -d \ --name crudeoilarb \ --env-file .env \ -v $(pwd)/logs:/app/logs \ --restart unless-stopped \ crudeoilarb
-
Monitor logs
docker logs -f crudeoilarb
- โ Multi-stage builds for optimized image size
- โ Non-root user for security
- โ Health checks for container monitoring
- โ Automatic restarts on failure
- โ Volume mounts for persistent logs
For development or testing without Docker:
-
Install Python 3.10+
python3 --version # Should be 3.10 or higher -
Clone and install dependencies
git clone https://github.com/Patrick-code-Bot/CrudeOilArb.git cd CrudeOilArb pip install -r requirements.txt -
Configure environment
cp .env.example .env nano .env # Add your API credentials -
Run the strategy
python run_live.py
Edit config_live.py to customize the trading parameters:
strategy_config = BzClGridConfig(
# Instruments (Bybit LINEAR perpetual swaps)
bz_instrument_id="BZUSDT-LINEAR.BYBIT",
cl_instrument_id="CLUSDT-LINEAR.BYBIT",
# Grid levels (spread as decimal percentage)
grid_levels=[
0.0010, # 0.10% spread
0.0020, # 0.20% spread
0.0030, # 0.30% spread
0.0040, # 0.40% spread
0.0050, # 0.50% spread
0.0060, # 0.60% spread
0.0070, # 0.70% spread
0.0080, # 0.80% spread
0.0090, # 0.90% spread
0.0100, # 1.00% spread
0.0120, # 1.20% spread
0.0150, # 1.50% spread
0.0200, # 2.00% spread
0.0300, # 3.00% spread
],
# Risk management
base_notional_per_level=88.5, # USDT per grid level
max_total_notional=3000.0, # Maximum total exposure (USDT)
target_leverage=10.0, # Target leverage (for reference)
# Trading parameters
maker_offset_bps=1.0, # 0.01% price offset for limit orders
order_timeout_sec=60.0, # Cancel and resubmit after 60s
rebalance_threshold_bps=10.0, # 0.10% imbalance triggers rebalance
extreme_spread_stop=0.035, # 3.5% spread triggers emergency stop
# Features
enable_high_levels=True, # Allow upper grid levels
auto_subscribe=True, # Auto-subscribe to market data
order_id_tag="001", # Unique strategy identifier
)base_notional_per_level=50.0 # $50 per level
max_total_notional=500.0 # $500 max exposure
target_leverage=5.0 # 5x leverageRecommended Capital: $1,000+ USDT
base_notional_per_level=88.5 # $88.5 per level
max_total_notional=3000.0 # $3,000 max exposure
target_leverage=10.0 # 10x leverageRecommended Capital: $2,500+ USDT
base_notional_per_level=500.0 # $500 per level
max_total_notional=10000.0 # $10,000 max exposure
target_leverage=15.0 # 15x leverageRecommended Capital: $15,000+ USDT
# Using Docker
docker start crudeoilarb
# Using Python
python run_live.pyExpected output:
================================================================================
BZ-CL Grid Strategy - Live Trading
================================================================================
โ
Running in LIVE mode
================================================================================
[1/5] Loading configuration...
[2/5] Building trading node...
[3/5] Registering Bybit adapters...
[4/5] Initializing trading node...
[5/5] Starting trading node...
================================================================================
๐ Trading node started successfully!
================================================================================
Strategy: BZ-CL Grid Arbitrage
Venue: Bybit (Live)
Instruments:
- BZUSDT-LINEAR
- CLUSDT-LINEAR
Press Ctrl+C to stop the trading node...
================================================================================
python check_spread.pyOutput:
Fetching BZ and CL ticker data from Bybit...
BZ: Bid=72.07, Ask=72.09, Last=72.07
CL: Bid=68.83, Ask=68.84, Last=68.84
BZ Mid: 72.08
CL Mid: 68.84
Spread: 0.047142 (4.7142%)
Abs Spread: 0.047142 (4.7142%)
Grid Level Analysis:
------------------------------------------------------------
Level 0.10% (prev= 0.00%): SHOULD OPEN
Level 0.20% (prev= 0.10%): SHOULD OPEN
...
For risk-free testing:
- Create testnet API keys at https://testnet.bybit.com
- Update
.env:BYBIT_TESTNET=true BYBIT_API_KEY=testnet_api_key BYBIT_API_SECRET=testnet_api_secret
- Restart the strategy
Graceful Shutdown:
# Docker
docker stop crudeoilarb
# Python (Press Ctrl+C in terminal)The strategy will:
- Cancel all pending orders
- Log final positions
- Save state snapshots
- Shut down cleanly
# Follow live logs
docker logs -f crudeoilarb
# Last 100 lines
docker logs --tail 100 crudeoilarb
# Search for errors
docker logs crudeoilarb 2>&1 | grep ERRORLocation: logs/bz_cl_grid.json
Format: Structured JSON with fields:
timestamp: ISO 8601 timestamptrader_id: TRADER-001level: DEBUG, INFO, WARNING, ERRORcomponent: Strategy, ExecEngine, DataClient, etc.message: Log message
Monitor these critical metrics:
| Metric | Description | Alert Threshold |
|---|---|---|
| Spread | Current BZ-CL price difference | > 3.5% (extreme) |
| Active Grids | Number of open grid positions | Approaching max |
| Total Notional | Current exposure vs max limit | > 90% of max |
| Order Fill Rate | % of orders filled | < 80% |
| Unrealized PnL | Open position P&L | Large negative |
| API Latency | Bybit API response time | > 500ms |
Monitor positions and orders:
-
Position Limits
max_total_notional: Hard cap on total exposure- Prevents over-leveraging
-
Extreme Spread Stop
extreme_spread_stop = 0.035(3.5%)- Pauses strategy if spread becomes abnormal
- Prevents trading during market dislocation
-
Order Timeouts
order_timeout_sec = 60.0- Cancels stale orders
- Ensures fresh pricing
-
Position Reconciliation
- On startup: Reconciles local state with exchange
- Periodic snapshots every 5 minutes
- Prevents state drift
-
Paired Order Tracking
- Detects imbalanced fills (one leg fills, other doesn't)
- Automatically closes naked positions
- Prevents directional exposure
- Test on testnet for 24+ hours
- Verify API keys have correct permissions
- Set Bybit account leverage (10x recommended)
- Fund account with sufficient margin
- Configure position size alerts on Bybit mobile app
- Document emergency procedures
If something goes wrong:
-
Stop the Strategy
docker stop crudeoilarb
-
Close All Positions
python close_all_positions.py
-
Review Logs
tail -1000 logs/bz_cl_grid.json | grep ERROR
Cause: Strategy has reached maximum exposure limit.
Solution:
- This is expected behavior when positions are at max
- Wait for positions to close before new grids open
- Or increase
max_total_notionalin config (higher risk)
Symptoms: Strategy running but no OrderSubmitted logs.
Diagnosis:
# Check if quote data is flowing
docker logs crudeoilarb 2>&1 | grep QuoteTick
# Check spread warnings
docker logs crudeoilarb 2>&1 | grep spreadSolutions:
- Verify instruments are correct:
BZUSDT-LINEAR.BYBIT - Check quote tick subscription is active
- Ensure spread is crossing grid levels
- Review
extreme_spread_stopthreshold
Symptoms: OrderRejected events in logs.
Common Causes:
- Insufficient margin/balance
- Incorrect leverage settings
- Position limits exceeded
- Price too far from market (stale)
Solutions:
- Check Bybit account balance
- Verify leverage is set correctly on Bybit
- Review
maker_offset_bpsin config - Check API rate limits
- NautilusTrader Docs: https://nautilustrader.io/docs
- NautilusTrader Discord: Join Community
- Bybit API Docs: https://bybit-exchange.github.io/docs
- Bybit Support: https://www.bybit.com/en-US/help-center
IMPORTANT RISK DISCLOSURE
- Trading involves substantial risk of loss
- This strategy is for educational purposes only
- Past performance does not guarantee future results
- The authors are not responsible for any financial losses
- This is not financial advice
- Market Risk: Spread may widen unexpectedly
- Liquidity Risk: Positions may be difficult to exit
- Technical Risk: Software bugs, API failures
- Execution Risk: Slippage, partial fills
- Funding Risk: Negative funding rates on perpetual swaps
- Correlation Risk: BZ and CL correlation may break down
- โ Only trade with capital you can afford to lose
- โ Understand the strategy before deploying
- โ Start with small position sizes
- โ Monitor actively, especially initially
- โ Have a plan for extreme scenarios
Use at your own risk.
This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0).
See the LICENSE file for full license text.
- GitHub: @Patrick-code-Bot
- Repository: CrudeOilArb
- Issues: Report a bug
Built with:
- NautilusTrader - High-performance algorithmic trading platform
- Bybit - Cryptocurrency derivatives exchange
Special thanks to the NautilusTrader community for their excellent framework and support.
โญ If this project helps you, consider giving it a star! โญ
Made with โค๏ธ for the algorithmic trading community