Portfolio Rebalancing Crypto with Python: A Trader's Guide
Learn how to automate crypto portfolio rebalancing with Python — from basic threshold logic to live exchange APIs on Binance and Bybit.
Learn how to automate crypto portfolio rebalancing with Python — from basic threshold logic to live exchange APIs on Binance and Bybit.
Your crypto portfolio drifts. Bitcoin pumps 40%, suddenly it's 70% of your holdings instead of the 50% you planned. You're now overexposed to a single asset — and most traders don't notice until it's already cost them. Portfolio rebalancing fixes this automatically, and Python makes it repeatable, fast, and emotionless. This guide walks you through building a rebalancing system from scratch, connecting it to real exchanges like Binance and Bybit, and making it production-ready.
Portfolio rebalancing is the process of realigning your asset weights back to a target allocation. Think of it like a balanced diet — if you plan to eat 40% protein, 40% carbs, and 20% fat, but you spend a week eating only pizza, you need to course-correct. The same principle applies to crypto.
Imagine you start with a simple three-asset portfolio: 50% BTC, 30% ETH, 20% SOL. After a strong SOL rally, your actual allocation drifts to 40% BTC, 25% ETH, 35% SOL. Without rebalancing, you're now implicitly making a concentrated bet on SOL continuing to outperform — which may or may not be intentional.
Key Takeaway: Rebalancing is not about predicting the market. It's about staying aligned with the risk profile you chose when you had a clear head — not in the middle of a pump.
Before writing a single line of Python, you need to decide which strategy fits your trading style. There are two main approaches.
| Strategy | How It Works | Best For | Drawback |
|---|---|---|---|
| Time-Based | Rebalance on a fixed schedule (daily, weekly) | Passive holders | May trade in bad conditions |
| Threshold-Based | Rebalance when any asset drifts beyond X% | Active traders | Can trigger too often in volatile markets |
| Hybrid | Threshold check on a schedule | Most traders | Slightly more complex to implement |
For most traders, the hybrid approach works best: check thresholds daily, but only execute trades when drift exceeds 5%. This avoids churning fees during sideways markets while still catching meaningful drift. On Binance, maker fees start at 0.1% — small individually, but rebalancing too often compounds those costs fast.
The cleanest way to connect Python to crypto exchanges is through the ccxt library — it supports over 100 exchanges including Binance, Bybit, OKX, and Coinbase with a unified API interface. You write the logic once and it works across all of them.
pip install ccxt pandas python-dotenv
Store your API keys in a .env file and never hardcode them in your script. Both Binance and Bybit let you create read-only API keys for monitoring and trade-enabled keys for execution — always use read-only during development.
import ccxt
import os
from dotenv import load_dotenv
load_dotenv()
# Connect to Binance
exchange = ccxt.binance({
'apiKey': os.getenv('BINANCE_API_KEY'),
'secret': os.getenv('BINANCE_SECRET'),
'enableRateLimit': True,
})
# Or switch to Bybit with the same interface
# exchange = ccxt.bybit({
# 'apiKey': os.getenv('BYBIT_API_KEY'),
# 'secret': os.getenv('BYBIT_SECRET'),
# 'enableRateLimit': True,
# })
# Fetch current balances
balance = exchange.fetch_balance()
print(balance['total']) # {'BTC': 0.5, 'ETH': 2.1, 'SOL': 45.0, 'USDT': 1200}
Key Takeaway: Always enable rate limiting in ccxt (`enableRateLimit: True`). Exchanges like Binance will ban your IP temporarily if you hammer their API — this one line prevents it automatically.
The algorithm has four steps: fetch current balances, calculate current weights, compare to target weights, and generate the trade orders needed to close the gap. Here is a complete working implementation.
import ccxt
import pandas as pd
from dotenv import load_dotenv
import os
load_dotenv()
# Target portfolio allocation (must sum to 1.0)
TARGET_WEIGHTS = {
'BTC': 0.50,
'ETH': 0.30,
'SOL': 0.20,
}
REBALANCE_THRESHOLD = 0.05 # Only rebalance if drift > 5%
QUOTE_CURRENCY = 'USDT'
def get_portfolio_value(exchange, assets):
"""Fetch current balances and compute USD values."""
balance = exchange.fetch_balance()['total']
tickers = exchange.fetch_tickers([f"{a}/{QUOTE_CURRENCY}" for a in assets])
portfolio = {}
total_value = 0.0
for asset in assets:
symbol = f"{asset}/{QUOTE_CURRENCY}"
price = tickers[symbol]['last']
amount = balance.get(asset, 0.0)
value = amount * price
portfolio[asset] = {'amount': amount, 'price': price, 'value': value}
total_value += value
return portfolio, total_value
def compute_drift(portfolio, total_value, target_weights):
"""Calculate current weights and drift from target."""
result = []
for asset, target in target_weights.items():
current_value = portfolio[asset]['value']
current_weight = current_value / total_value if total_value > 0 else 0
drift = current_weight - target
result.append({
'asset': asset,
'current_weight': round(current_weight, 4),
'target_weight': target,
'drift': round(drift, 4),
'value': current_value,
'price': portfolio[asset]['price'],
})
return pd.DataFrame(result)
def generate_orders(drift_df, total_value, threshold):
"""Return list of trades needed to reach target weights."""
orders = []
for _, row in drift_df.iterrows():
if abs(row['drift']) < threshold:
continue # Within acceptable range, skip
target_value = row['target_weight'] * total_value
delta_value = target_value - row['value']
delta_amount = delta_value / row['price']
side = 'buy' if delta_amount > 0 else 'sell'
orders.append({
'asset': row['asset'],
'side': side,
'amount': abs(round(delta_amount, 6)),
'symbol': f"{row['asset']}/{QUOTE_CURRENCY}",
})
return orders
def execute_rebalance(exchange, orders, dry_run=True):
"""Execute orders. Set dry_run=False to send real trades."""
for order in orders:
print(f"[{'DRY RUN' if dry_run else 'LIVE'}] {order['side'].upper()} "
f"{order['amount']} {order['asset']}")
if not dry_run:
exchange.create_market_order(
symbol=order['symbol'],
side=order['side'],
amount=order['amount']
)
if __name__ == '__main__':
exchange = ccxt.binance({
'apiKey': os.getenv('BINANCE_API_KEY'),
'secret': os.getenv('BINANCE_SECRET'),
'enableRateLimit': True,
})
assets = list(TARGET_WEIGHTS.keys())
portfolio, total_value = get_portfolio_value(exchange, assets)
print(f"Total Portfolio Value: ${total_value:.2f}")
drift_df = compute_drift(portfolio, total_value, TARGET_WEIGHTS)
print(drift_df.to_string(index=False))
orders = generate_orders(drift_df, total_value, REBALANCE_THRESHOLD)
if not orders:
print("Portfolio is balanced. No trades needed.")
else:
execute_rebalance(exchange, orders, dry_run=True) # Change to False for live trading
Run this in dry_run mode first and inspect the output for a few days. You want to see that the drift numbers make sense before letting it touch real funds. On Bybit and OKX, you can also run this against their testnet environments — both exchanges provide sandbox API endpoints specifically for this purpose.
A rebalancer you run manually isn't really automated — it's just a calculator. The goal is to have it run on a schedule without your involvement. There are two practical approaches for this.
For a simple setup on a personal machine or VPS, use cron on Linux/Mac. Add a cron job to run your script every day at 9:00 AM UTC, which covers the daily candle open across most exchanges.
# Edit crontab
crontab -e
# Run rebalancer daily at 09:00 UTC
0 9 * * * /usr/bin/python3 /home/user/rebalancer/main.py >> /home/user/rebalancer/logs/rebalancer.log 2>&1
For a more robust setup, wrap your script in a Docker container and deploy it to a small VPS or cloud instance. This keeps it running 24/7 regardless of whether your local machine is on. Platforms like Bybit and OKX also offer webhook triggers — you can combine VoiceOfChain signal alerts with your rebalancer so that a major trend shift can trigger an emergency rebalance even outside the scheduled window.
Key Takeaway: Always log every trade your bot executes — timestamp, asset, side, amount, price, and the drift that triggered it. You cannot improve what you cannot measure, and exchange history only goes back 90 days on most platforms.
Pure mechanical rebalancing works well in ranging markets but can hurt you in strong trends. If BTC is in a confirmed bull leg, selling it every time it hits 55% of your portfolio is fighting the trend. This is where combining rebalancing with market signals becomes powerful.
VoiceOfChain provides real-time order-flow signals based on on-chain activity, whale movements, and exchange inflows — exactly the kind of data that helps you decide whether a drift is noise or a structural shift. You can integrate a signal check into your rebalancer: if VoiceOfChain flags strong bullish momentum for an asset, widen the threshold before you sell it. If it flags a whale dump, tighten the threshold and rebalance sooner.
# Pseudocode: integrate signal bias into threshold
def get_dynamic_threshold(asset, base_threshold=0.05):
"""
Fetch signal strength from VoiceOfChain or internal data.
Adjust threshold so strong-trending assets rebalance less aggressively.
"""
signal = fetch_voc_signal(asset) # Returns: 'bullish', 'neutral', 'bearish'
if signal == 'bullish':
return base_threshold + 0.03 # Let winners run a bit longer
elif signal == 'bearish':
return base_threshold - 0.02 # Trim faster on weakness
else:
return base_threshold
This hybrid approach — rules-based rebalancing enhanced by real-time signal context — is significantly closer to how professional quant desks operate than a naive time-based reset. The signal data doesn't override the system; it just adjusts the sensitivity of the trigger.
Portfolio rebalancing with Python is one of the highest-leverage automation tasks for a crypto trader. It removes emotion, enforces discipline, and compounds over time into measurably better risk-adjusted returns. You've now got the core building blocks: target weights, drift calculation, order generation, scheduling via cron, and signal integration for smarter thresholds.
Start with dry_run mode against your Binance or Bybit account. Watch the logs for a week. Once you trust the numbers, flip it live with a small allocation. Extend the asset list gradually. Add VoiceOfChain signals to contextualize your rebalancing decisions. The bot won't make you rich overnight — but over months and years, a portfolio that stays aligned with your actual risk intent will consistently outperform one managed by impulse.
Key Takeaway: The best rebalancer is one you actually trust and leave running. Keep it simple, log everything, and only add complexity when you've validated the basics work.