◈   ⌬ bots · Intermediate

Tax Loss Harvesting Crypto Bot: Automate Your Tax Strategy

Learn how to build a crypto tax loss harvesting bot using Python that automatically detects underwater positions and sells them to offset capital gains on Binance, Bybit, and OKX.

Uncle Solieditor · voc · 06.05.2026 ·views 14
◈   Contents
  1. → What Is Tax Loss Harvesting in Crypto?
  2. → How a Tax Loss Harvesting Bot Automates the Process
  3. → Building the Core Bot Logic in Python
  4. → Connecting to Binance, Bybit, and OKX
  5. → Configuration, Risk Controls, and Wash Sale Warnings
  6. → Frequently Asked Questions

Every bear market is a tax opportunity in disguise. When your ETH is down 40% and your SOL is bleeding, the worst thing you can do is sit there watching red candles. Tax loss harvesting lets you sell those losing positions, lock in the loss for tax purposes, and immediately redeploy the capital — all while the IRS effectively subsidizes part of your portfolio recovery. The problem is that doing this manually across 10-20 assets, while monitoring prices around the clock, is nearly impossible for a human. That is exactly what a tax loss harvesting bot is built for.

What Is Tax Loss Harvesting in Crypto?

Tax loss harvesting is the practice of selling an asset at a loss to realize that loss on paper, then using it to offset capital gains elsewhere in your portfolio. In traditional markets, this strategy has been used for decades. In crypto, it is even more powerful because the wash sale rule — which bars investors from buying back a substantially identical security within 30 days of selling at a loss — does not currently apply to cryptocurrencies in the US. Crypto is classified as property, not a security, meaning you can sell ETH at a loss today and rebuy ETH immediately, while still claiming the tax loss. That loophole does not exist in stock markets.

Here is a concrete example. You bought 2 ETH at $3,500 each — $7,000 total. ETH is now at $2,100. Your unrealized loss is $2,800. If you sell now, you can use that $2,800 loss to offset $2,800 in gains from other trades, potentially saving hundreds or thousands in taxes depending on your bracket. Then you immediately rebuy 2 ETH at $2,100. Your tax position improved, and your ETH exposure is unchanged. A bot handles the timing, execution, and record-keeping for every position in your portfolio automatically.

How a Tax Loss Harvesting Bot Automates the Process

The bot runs a continuous loop: fetch your current balances, compare each position's current price against your average cost basis, and trigger a market sell when the unrealized loss crosses a defined threshold — say, negative 10%. After execution, it logs the transaction with a timestamp, order ID, and fill price for your accountant or tax software. If configured, it queues a rebuy after a cooldown period to future-proof the strategy against potential wash sale legislation.

The scan interval is typically hourly for most portfolios. Running it more frequently than every few minutes risks hitting exchange rate limits and also generates noise — a position that dips 10% and recovers within minutes is not a genuine harvesting opportunity. On Binance and Bybit, the public API is generous enough to support scans every 5 to 10 minutes if needed for more aggressive strategies. VoiceOfChain users often combine this bot with real-time signal alerts — when VoiceOfChain flags a bearish trend continuation on an asset you hold, that is a compelling trigger to harvest the loss rather than wait for a deeper drawdown that may never recover.

Tax laws change. Several US legislative proposals have included crypto under wash sale rules. Always consult a crypto tax professional before deploying an automated harvesting strategy. Tools like Koinly, CoinTracker, or TaxBit can reconcile bot transactions automatically.

Building the Core Bot Logic in Python

The foundation is a class that handles portfolio scanning and trade execution. The ccxt library provides a unified interface to over 100 exchanges including Binance, Bybit, OKX, Coinbase Advanced, and KuCoin. Install it with pip install ccxt. The bot requires your average cost basis per asset — either hardcoded, loaded from a CSV, or pulled from a portfolio tracking API. We pass it in as a plain dictionary so the class stays exchange-agnostic.

import ccxt
from datetime import datetime


class TaxLossHarvestingBot:
    def __init__(self, exchange_id, api_key, api_secret, loss_threshold=-0.10):
        self.exchange = getattr(ccxt, exchange_id)({
            'apiKey': api_key,
            'secret': api_secret,
            'enableRateLimit': True
        })
        self.loss_threshold = loss_threshold  # -0.10 = trigger at 10% loss
        self.harvest_log = []

    def get_positions(self):
        # Fetch all non-stablecoin balances with live prices
        balance = self.exchange.fetch_balance()
        stables = ('USDT', 'USDC', 'BUSD', 'DAI', 'TUSD')
        positions = []
        for coin, amount in balance['total'].items():
            if amount > 0 and coin not in stables:
                ticker = self.exchange.fetch_ticker(f'{coin}/USDT')
                positions.append({
                    'symbol': coin,
                    'amount': amount,
                    'current_price': ticker['last']
                })
        return positions

    def calculate_pnl(self, avg_buy_price, current_price):
        # Returns decimal: -0.15 means down 15%
        return (current_price - avg_buy_price) / avg_buy_price

    def should_harvest(self, pnl):
        return pnl <= self.loss_threshold

    def harvest(self, symbol, amount):
        pair = f'{symbol}/USDT'
        order = self.exchange.create_market_sell_order(pair, amount)
        entry = {
            'timestamp': datetime.utcnow().isoformat(),
            'symbol': symbol,
            'amount': amount,
            'order_id': order['id'],
            'status': order['status']
        }
        self.harvest_log.append(entry)
        return entry

    def run(self, cost_basis: dict):
        # Pass cost_basis as {symbol: avg_buy_price}, e.g. {'ETH': 3500}
        positions = self.get_positions()
        harvested = []
        for pos in positions:
            sym = pos['symbol']
            if sym not in cost_basis:
                continue
            pnl = self.calculate_pnl(cost_basis[sym], pos['current_price'])
            if self.should_harvest(pnl):
                print(f'{sym} down {pnl:.1%} — harvesting loss')
                result = self.harvest(sym, pos['amount'])
                harvested.append(result)
        return harvested

The run() method is the heart of the bot. Call it on a schedule — via a cron job, APScheduler, or a simple asyncio loop — passing in your cost basis dictionary. It returns a list of all harvested positions in that iteration, which you write to a CSV or database for tax reporting. The harvest_log attribute accumulates across iterations for the session.

Connecting to Binance, Bybit, and OKX

Each exchange requires slightly different API setup. On Binance, create an API key with Spot trading permission enabled — withdrawal permission is not needed. Bybit uses a V5 unified account API. OKX requires an additional passphrase that you set when creating the key. The ccxt library abstracts the underlying differences, so once the client is instantiated, the order placement code is identical across all three.

import ccxt

# Binance — Spot API, no withdrawal permission needed
binance = ccxt.binance({
    'apiKey': 'YOUR_BINANCE_KEY',
    'secret': 'YOUR_BINANCE_SECRET',
    'enableRateLimit': True,
    'options': {'defaultType': 'spot'}
})

# Bybit — V5 unified account
bybit = ccxt.bybit({
    'apiKey': 'YOUR_BYBIT_KEY',
    'secret': 'YOUR_BYBIT_SECRET',
    'enableRateLimit': True
})

# OKX — requires passphrase set at key creation time
okx = ccxt.okx({
    'apiKey': 'YOUR_OKX_KEY',
    'secret': 'YOUR_OKX_SECRET',
    'password': 'YOUR_OKX_PASSPHRASE',
    'enableRateLimit': True
})


def execute_harvest(exchange, symbol, amount, dry_run=True):
    pair = f'{symbol}/USDT'
    if dry_run:
        ticker = exchange.fetch_ticker(pair)
        price = ticker['last']
        print(f'[DRY RUN] Would sell {amount} {symbol} at ~{price}')
        return {'dry_run': True, 'symbol': symbol, 'amount': amount}
    try:
        order = exchange.create_market_sell_order(pair, amount)
        return {
            'success': True,
            'order_id': order['id'],
            'filled': order['filled'],
            'cost': order['cost']
        }
    except ccxt.InsufficientFunds as e:
        return {'success': False, 'error': f'Insufficient funds: {e}'}
    except ccxt.NetworkError as e:
        return {'success': False, 'error': f'Network error: {e}'}
    except ccxt.ExchangeError as e:
        return {'success': False, 'error': f'Exchange error: {e}'}


# Example: harvest 0.5 ETH on Binance — dry run first
result = execute_harvest(binance, 'ETH', 0.5, dry_run=True)
print(result)

Always start in dry_run=True mode and run for at least a week before going live. The bot will log exactly what it would have sold and at what price, giving you confidence that thresholds are calibrated correctly. KuCoin and Gate.io follow the same pattern — swap the exchange name in the constructor. Coinbase Advanced Trade via ccxt works identically but has lower rate limits, so increase your scan interval to 15-30 minutes if using it.

Configuration, Risk Controls, and Wash Sale Warnings

A misconfigured harvesting bot can cause real damage — selling positions you did not intend to touch, generating dozens of taxable events in a single session, or triggering fees that exceed the tax benefit on small positions. A clean config file separates thresholds and safety limits from execution logic, making the bot auditable and easy to adjust without touching core code.

# config.py

BOT_CONFIG = {
    # Exchange selection
    'exchange': 'binance',  # binance | bybit | okx | coinbase | kucoin | gate

    # Harvesting thresholds
    'thresholds': {
        'loss_pct': -0.10,          # Sell when unrealized loss >= 10%
        'min_position_usd': 50,      # Skip positions worth less than $50
        'max_harvests_per_day': 5    # Hard cap on daily sell executions
    },

    # Timing
    'timing': {
        'scan_interval_sec': 3600,   # Scan portfolio every 60 minutes
        'rebuy_cooldown_days': 31    # Wait before rebuying same asset
    },

    # Asset filters
    'assets': {
        'whitelist': ['BTC', 'ETH', 'SOL', 'AVAX', 'MATIC', 'LINK', 'ARB'],
        'blacklist': ['USDT', 'USDC', 'BUSD', 'DAI']  # Never sell stables
    },

    # Safety limits
    'safety': {
        'max_portfolio_pct': 0.25,  # Never harvest more than 25% at once
        'dry_run': True             # Set False only when ready for live trading
    }
}

The rebuy_cooldown_days setting is a courtesy safeguard, not a legal requirement in the US given current crypto tax law. But because legislation has repeatedly come close to applying wash sale rules to crypto, the 31-day buffer future-proofs your strategy without meaningful cost. Keep loss_pct conservative — 10% to 15% is a reasonable range that avoids triggering on ordinary intraday volatility. Platforms like Gate.io and Bitget have tighter rate limits, so you may need to push scan_interval_sec to 1800 or more when connecting through their APIs.

Exchange API Suitability for Tax Loss Harvesting Bots
Exchangeccxt SupportRequests / MinSpot APINotes
BinanceFull1200YesBest liquidity, easiest setup
BybitFull600YesV5 API, unified account
OKXFull600YesRequires API passphrase
Coinbase AdvancedFull300YesLower limits, US-friendly
KuCoinFull600YesStrong altcoin selection
Gate.ioFull300YesWidest asset coverage
BitgetFull600YesGrowing liquidity, low fees

Pairing this bot with a real-time signal source dramatically improves timing decisions. VoiceOfChain provides live market alerts that can confirm whether a drawdown reflects a genuine trend shift or just noise — context that a raw percentage threshold alone cannot provide. If VoiceOfChain signals sustained bearish momentum on an asset already sitting at a loss, that is a much stronger case to harvest than a dip caused by a single large liquidation cascade.

Frequently Asked Questions

Is the wash sale rule applied to crypto in the US?
As of 2025, the wash sale rule does not apply to cryptocurrencies in the US — crypto is classified as property, not a security. You can sell ETH at a loss and immediately rebuy it while still claiming the tax loss. However, multiple legislative proposals have sought to change this, so monitor updates and consult a crypto-specialized CPA annually.
How do I get accurate cost basis data into the bot?
Most traders maintain a spreadsheet or use a crypto tax tool like Koinly, CoinTracker, or TaxBit that exports average cost per asset. Feed this into the bot as a Python dictionary keyed by ticker symbol. If you bought the same asset multiple times, use a consistent method — FIFO or average cost — and stick to it across all exchanges including Binance, Bybit, and OKX.
Can I run this bot across multiple exchanges at once?
Yes. Instantiate a separate bot for each exchange and run them in parallel threads or async tasks. Make sure your cost basis data aggregates holdings across all exchanges — a position on Bybit and a position on Binance in the same asset should share a combined average cost basis, not be treated as independent.
What if I sell to harvest a loss and the price immediately pumps?
This is the primary risk of tax loss harvesting. If you sell ETH at $2,100 and it recovers to $2,800 within hours, you have missed the upside. The mitigation is simple: rebuy immediately after harvesting. Since there is no wash sale rule for crypto, you preserve market exposure while locking in the tax loss. Only delay the rebuy if a bearish signal — from VoiceOfChain or your own analysis — suggests the drop will continue.
Does the bot work for DeFi or on-chain assets?
The ccxt-based implementation works only with centralized exchanges like Binance, OKX, KuCoin, and Gate.io. Harvesting losses on DeFi positions requires a different approach using Web3.py and direct interaction with DEX smart contracts or portfolio management protocols, which involves gas cost modeling and slippage management — significantly more complex to automate reliably.
How much can tax loss harvesting realistically save?
It depends entirely on your bracket and the scale of your gains. In the US, short-term capital gains can be taxed at rates up to 37%. If you have $15,000 in net gains and $10,000 in harvested losses, you pay tax on only $5,000 — saving potentially $1,850 to $3,700 depending on your rate. In volatile crypto markets with frequent swings, active harvesting over a full year can eliminate a substantial portion of your tax liability.

A tax loss harvesting bot is one of the highest-ROI tools a crypto trader can build. The code is approachable, ccxt handles exchange complexity for Binance, Bybit, OKX, and others with a consistent interface, and the tax savings are real and immediate. Start in dry_run mode, validate your cost basis data, monitor the logs for a week, then flip the switch to live. Combine it with signal context from VoiceOfChain to time your harvests with intention rather than just reacting to arbitrary thresholds — and make every bear market work for your bottom line.

◈   more on this topic
⌘ api Kraken API Documentation for Crypto Traders: Essentials and Examples ◉ basics Mastering the ccxt library documentation for crypto traders