Strategy

Crypto Tax Loss Harvesting for AI Agents: Wash Sale Rules, Realized Losses, and Optimization

Tax loss harvesting is one of the highest-return strategies available to crypto traders with unrealized losses in their portfolios. The concept is straightforward: sell a position at a loss to realize that loss on paper, use the realized loss to offset capital gains, then repurchase exposure to the same asset after the sale. The tax saving from the deducted loss is a direct improvement in after-tax returns that requires no price prediction—only careful timing and portfolio awareness.

AI agents are uniquely positioned to execute tax loss harvesting more effectively than human traders. Agents can monitor mark-to-market positions continuously, calculate the tax impact of every potential harvest in real time, and execute multi-leg strategies (sell-swap-repurchase) without delay or emotion. This guide covers the mechanics, current wash sale rules for crypto in 2026, optimization frameworks, and a complete Python tax harvesting agent using the Purple Flea Wallet API and Purple Flea Trading API.

Legal disclaimer: This guide is for educational purposes. Tax rules are jurisdiction-specific and change frequently. Always consult a qualified tax professional before implementing any tax strategy. Nothing in this article constitutes tax advice.

Tax Loss Harvesting Mechanics

The fundamental mechanism: when an asset is sold at a price below its cost basis (the price paid), the difference is a capital loss. Capital losses can be used to offset capital gains in the same tax year, reducing taxable income.

Example

Agent purchased 1 ETH at $4,000 (cost basis = $4,000).

Current ETH price: $3,200. Unrealized loss = -$800.

Agent also realized a $2,000 gain from a BTC trade earlier in the year.

Action: Sell the 1 ETH at $3,200. Realized loss = -$800.

Tax impact: $2,000 gain offset by $800 loss = $1,200 net gain taxed.

At 30% tax rate: saves $240 in taxes compared to not harvesting.

Agent immediately repurchases ETH exposure (via a correlated asset or after waiting) to maintain market position.

The key insight is that harvesting does not permanently eliminate the taxable gain—it defers it. When the agent later sells the repurchased ETH, the gain is computed from the new (lower) cost basis. But the time value of deferral is valuable, and in some cases (if the position is held long enough to qualify for long-term rates) the rate on the future gain may be lower.

Wash Sale Rules for Crypto

The wash sale rule, as it exists in US tax law for securities, prohibits claiming a capital loss if you repurchase a "substantially identical" security within 30 days before or after the sale. This 61-day window (30 days before, day of sale, 30 days after) is the "wash sale window."

Crypto Wash Sale Status in 2026

As of early 2026, the IRS has not formally extended wash sale rules to cryptocurrencies. The IRS classifies cryptocurrency as property, not a security, and the wash sale rule (IRC Section 1091) applies only to stocks and securities. This means crypto traders can:

  • Sell ETH at a loss
  • Immediately repurchase ETH
  • Claim the full capital loss on the sale

This "buy-immediately-after-selling" strategy, which would disqualify a stock loss, is currently legal for crypto in the US.

Legislative risk: Multiple bills have been introduced in Congress since 2021 to extend wash sale rules to crypto. As of this writing none have passed, but the risk is real. Agents implementing aggressive tax loss harvesting strategies should monitor legislative developments and build configurable wash sale windows (e.g., 0, 30, or 60 days) that can be updated without code changes if rules change.

International Wash Sale Considerations

Non-US jurisdictions have varying rules:

JurisdictionWash Sale Rule for CryptoNotes
United StatesNone (as of 2026)Proposal risk exists; rule applies to securities, not property
United KingdomYes ("Bed and Breakfasting")30-day window; same-day matching rules also apply
GermanyN/A (different system)Crypto held >1 year is tax-free; no wash sale concept
AustraliaNone (similar to US)CGT asset; no formal wash sale rule for crypto
CanadaSuperficial loss rule30-day window, applies to "identical property"—application to crypto debated

AI agents operating across multiple jurisdictions must implement jurisdiction-specific rules. The Python implementation below supports configurable wash sale window parameters.

Harvesting Frequency Optimization

How often should an agent harvest losses? The answer depends on several factors:

Transaction Costs vs. Tax Benefit

Every harvest involves trading costs: swap fees, gas costs, and potential slippage. A harvest is only worth executing if:

tax_benefit > transaction_cost + time_out_of_market_risk

Where tax_benefit = loss_amount * marginal_tax_rate and time_out_of_market_risk is the expected gain you might miss if the asset rebounds during any required waiting period.

For small unrealized losses (<1% of position), transaction costs often exceed tax benefit. The minimum loss threshold for harvesting is roughly:

def minimum_harvest_threshold(
    position_value_usd: float,
    marginal_tax_rate: float,
    transaction_cost_usd: float,
    wash_sale_wait_days: int = 0,
    daily_volatility: float = 0.03,
) -> float:
    """
    Compute minimum loss (in USD) worth harvesting.

    Parameters:
    - position_value_usd: current market value of the position
    - marginal_tax_rate: e.g., 0.30 for 30%
    - transaction_cost_usd: gas + swap fees for sell + repurchase
    - wash_sale_wait_days: 0 for crypto (no current rule), 30+ for conservative
    - daily_volatility: annualized 1-day return std dev, e.g., 0.03 = 3% daily
    """
    import math
    # Tax benefit needed to cover transaction costs
    min_loss_for_costs = transaction_cost_usd / max(marginal_tax_rate, 0.01)

    # Additional buffer for opportunity cost during waiting period
    # Expected move = vol * sqrt(days) * 1 std dev
    opportunity_cost_pct = daily_volatility * math.sqrt(wash_sale_wait_days) if wash_sale_wait_days > 0 else 0.0
    opportunity_cost_usd = position_value_usd * opportunity_cost_pct

    return min_loss_for_costs + opportunity_cost_usd

Year-End vs. Continuous Harvesting

Two common approaches:

  • Continuous harvesting: Monitor positions daily and harvest whenever unrealized loss exceeds threshold. Maximizes total losses captured but requires active position management.
  • Year-end harvesting: In Q4, systematically review all positions with unrealized losses and harvest before December 31. Simpler but may miss opportunities throughout the year and causes a rush in November-December.

AI agents are well-suited to continuous harvesting because they can monitor positions without human attention overhead. The Purple Flea Trading API exposes position mark-to-market data that makes continuous monitoring straightforward.

Cost Basis Accounting Methods

The cost basis method determines which lots are used when calculating the gain or loss on a sale. For tax optimization, specific identification (SpecID) is almost always the best choice:

MethodHow It WorksTax Impact
FIFO (First In, First Out)Oldest lots sold firstOften produces large gains if early lots have low basis
LIFO (Last In, First Out)Newest lots sold firstCan maximize short-term losses; not IRS-preferred
HIFO (Highest In, First Out)Lots with highest cost basis sold firstMinimizes gains (or maximizes losses) on each sale
Specific Identification (SpecID)Choose exactly which lot to sellMost flexible—optimal for active tax management
Average Cost BasisAverage price across all lotsSimple; may not be optimal for harvesting

For a tax loss harvesting agent, HIFO is the default: always sell the lot with the highest cost basis first (producing the largest loss or smallest gain). SpecID with active lot selection can outperform HIFO in edge cases where short-term vs. long-term rate differences matter.

Python Tax Harvesting Agent

The following agent continuously monitors a portfolio via the Purple Flea Wallet and Trading APIs, identifies harvest opportunities, and executes them when the tax benefit exceeds costs.

"""
Crypto Tax Loss Harvesting Agent
Uses Purple Flea Wallet + Trading APIs to monitor positions
and execute harvests when tax benefit > transaction cost.
"""

import asyncio
import aiohttp
import json
import logging
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import List, Dict, Optional
import math

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

PF_API = "https://purpleflea.com/api"
PF_API_KEY = "YOUR_API_KEY"
PF_AGENT_ID = "YOUR_AGENT_ID"

# Tax parameters - configure per jurisdiction
TAX_CONFIG = {
    "short_term_rate": 0.37,    # short-term capital gains rate
    "long_term_rate": 0.20,     # long-term capital gains rate (>1 year)
    "wash_sale_days": 0,        # 0 = crypto exemption (US 2026); set 30 for conservative
    "jurisdiction": "US",
}

# Minimum tax benefit (USD) to trigger harvest
MIN_HARVEST_BENEFIT_USD = 25.0
# Daily price volatility estimate per asset
ASSET_VOLATILITY = {
    "ETH": 0.035,
    "BTC": 0.028,
    "SOL": 0.055,
    "BNB": 0.040,
}


@dataclass
class TaxLot:
    asset: str
    quantity: float
    cost_basis_usd: float    # price paid per unit
    purchase_date: datetime
    lot_id: str


@dataclass
class Position:
    asset: str
    lots: List[TaxLot]
    current_price_usd: float

    @property
    def total_quantity(self) -> float:
        return sum(lot.quantity for lot in self.lots)

    @property
    def total_cost_basis(self) -> float:
        return sum(lot.quantity * lot.cost_basis_usd for lot in self.lots)

    @property
    def current_value(self) -> float:
        return self.total_quantity * self.current_price_usd

    @property
    def total_unrealized_pnl(self) -> float:
        return self.current_value - self.total_cost_basis

    def get_harvestable_lots(self) -> List[TaxLot]:
        """Return lots with unrealized losses, sorted by loss (largest first)."""
        result = []
        for lot in self.lots:
            lot_value = lot.quantity * self.current_price_usd
            lot_basis = lot.quantity * lot.cost_basis_usd
            if lot_value < lot_basis:
                result.append(lot)
        # Sort by largest loss first (HIFO approach)
        result.sort(key=lambda l: l.cost_basis_usd - self.current_price_usd, reverse=True)
        return result


@dataclass
class HarvestOpportunity:
    position: Position
    lot: TaxLot
    unrealized_loss_usd: float
    holding_days: int
    is_long_term: bool    # held > 1 year
    applicable_rate: float
    gross_tax_benefit: float
    estimated_tx_cost: float
    net_benefit: float
    wash_sale_replacement: Optional[str] = None  # correlated asset to use if waiting


async def fetch_positions(session: aiohttp.ClientSession) -> List[Position]:
    """Fetch current positions and prices from Purple Flea API."""
    headers = {"Authorization": f"Bearer {PF_API_KEY}"}
    try:
        async with session.get(
            f"{PF_API}/wallet/positions?agent_id={PF_AGENT_ID}",
            headers=headers
        ) as resp:
            data = await resp.json()
            positions = []
            for asset_data in data.get("positions", []):
                lots = []
                for lot_data in asset_data.get("lots", []):
                    lots.append(TaxLot(
                        asset=asset_data["asset"],
                        quantity=float(lot_data["quantity"]),
                        cost_basis_usd=float(lot_data["cost_basis_usd"]),
                        purchase_date=datetime.fromisoformat(lot_data["purchase_date"]),
                        lot_id=lot_data["lot_id"],
                    ))
                positions.append(Position(
                    asset=asset_data["asset"],
                    lots=lots,
                    current_price_usd=float(asset_data["current_price_usd"]),
                ))
            return positions
    except Exception as e:
        logger.error(f"Failed to fetch positions: {e}")
        return []


async def estimate_tx_cost(
    session: aiohttp.ClientSession,
    asset: str,
    quantity: float
) -> float:
    """Estimate total transaction cost for sell + repurchase in USD."""
    headers = {"Authorization": f"Bearer {PF_API_KEY}"}
    try:
        async with session.get(
            f"{PF_API}/trading/fee-estimate?asset={asset}&quantity={quantity}",
            headers=headers
        ) as resp:
            data = await resp.json()
            return float(data.get("estimated_total_usd", 5.0))  # default $5 if API fails
    except Exception:
        return 5.0  # conservative default


def compute_harvest_opportunity(
    position: Position,
    lot: TaxLot,
    tx_cost: float,
    tax_config: dict,
) -> Optional[HarvestOpportunity]:
    """Compute tax benefit for harvesting a specific lot."""
    current_value = lot.quantity * position.current_price_usd
    lot_basis = lot.quantity * lot.cost_basis_usd
    unrealized_loss = current_value - lot_basis  # negative = loss

    if unrealized_loss >= 0:
        return None  # Not a loss

    holding_days = (datetime.utcnow() - lot.purchase_date).days
    is_long_term = holding_days > 365

    # Use appropriate tax rate (loss offsets gains at the same rate)
    rate = tax_config["long_term_rate"] if is_long_term else tax_config["short_term_rate"]
    gross_benefit = abs(unrealized_loss) * rate
    net_benefit = gross_benefit - tx_cost

    if net_benefit < MIN_HARVEST_BENEFIT_USD:
        return None

    # Suggest wash sale replacement asset if waiting is needed
    wash_sale_days = tax_config["wash_sale_days"]
    replacement = None
    if wash_sale_days > 0:
        # Use correlated asset during waiting period to maintain exposure
        CORRELATED = {
            "BTC": "ETH",    # high correlation; not "substantially identical"
            "ETH": "BTC",
            "SOL": "AVAX",
            "BNB": "MATIC",
        }
        replacement = CORRELATED.get(position.asset)

    return HarvestOpportunity(
        position=position,
        lot=lot,
        unrealized_loss_usd=unrealized_loss,
        holding_days=holding_days,
        is_long_term=is_long_term,
        applicable_rate=rate,
        gross_tax_benefit=gross_benefit,
        estimated_tx_cost=tx_cost,
        net_benefit=net_benefit,
        wash_sale_replacement=replacement,
    )


async def execute_harvest(
    session: aiohttp.ClientSession,
    opportunity: HarvestOpportunity,
    dry_run: bool = True,
) -> bool:
    """Execute a tax loss harvest by selling the lot and optionally repurchasing."""
    lot = opportunity.lot
    pos = opportunity.position
    headers = {
        "Authorization": f"Bearer {PF_API_KEY}",
        "Content-Type": "application/json",
    }

    logger.info(
        f"{'[DRY RUN] ' if dry_run else ''}Harvesting {lot.quantity:.6f} {pos.asset} "
        f"(lot {lot.lot_id}, loss=${abs(opportunity.unrealized_loss_usd):.2f}, "
        f"net benefit=${opportunity.net_benefit:.2f})"
    )

    if dry_run:
        return True

    # Step 1: Sell the loss lot
    sell_payload = {
        "agent_id": PF_AGENT_ID,
        "action": "sell",
        "asset": pos.asset,
        "quantity": lot.quantity,
        "lot_id": lot.lot_id,  # specific lot identification
        "reason": "tax_loss_harvest",
    }
    async with session.post(
        f"{PF_API}/trading/execute",
        json=sell_payload,
        headers=headers,
    ) as resp:
        sell_result = await resp.json()
        if not sell_result.get("success"):
            logger.error(f"Sell failed: {sell_result}")
            return False

    logger.info(f"Sold lot {lot.lot_id} at ${pos.current_price_usd:.2f}")

    # Step 2: Handle wash sale window
    wash_days = TAX_CONFIG["wash_sale_days"]
    if wash_days > 0 and opportunity.wash_sale_replacement:
        # Buy correlated asset during waiting period
        repurchase_usd = lot.quantity * pos.current_price_usd
        logger.info(
            f"Buying ${repurchase_usd:.2f} of {opportunity.wash_sale_replacement} "
            f"for {wash_days}-day wait period"
        )
        buy_replacement_payload = {
            "agent_id": PF_AGENT_ID,
            "action": "buy",
            "asset": opportunity.wash_sale_replacement,
            "amount_usd": repurchase_usd,
            "reason": "wash_sale_replacement",
        }
        async with session.post(
            f"{PF_API}/trading/execute",
            json=buy_replacement_payload,
            headers=headers,
        ) as resp:
            await resp.json()

        # Schedule repurchase after wash sale window
        # In production, use a scheduler or persistent task queue
        logger.info(f"Scheduled repurchase of {pos.asset} after {wash_days} days")
    else:
        # No wash sale rule — immediately repurchase
        repurchase_usd = lot.quantity * pos.current_price_usd
        buy_payload = {
            "agent_id": PF_AGENT_ID,
            "action": "buy",
            "asset": pos.asset,
            "amount_usd": repurchase_usd,
            "reason": "harvest_repurchase",
        }
        async with session.post(
            f"{PF_API}/trading/execute",
            json=buy_payload,
            headers=headers,
        ) as resp:
            buy_result = await resp.json()
            if buy_result.get("success"):
                logger.info(f"Repurchased {pos.asset} immediately (no wash sale rule)")

    return True


async def harvest_cycle(dry_run: bool = True):
    """Run one full harvest scan cycle."""
    async with aiohttp.ClientSession() as session:
        positions = await fetch_positions(session)
        if not positions:
            logger.info("No positions found.")
            return

        opportunities = []
        for pos in positions:
            harvestable_lots = pos.get_harvestable_lots()
            for lot in harvestable_lots:
                tx_cost = await estimate_tx_cost(session, pos.asset, lot.quantity)
                opp = compute_harvest_opportunity(pos, lot, tx_cost, TAX_CONFIG)
                if opp is not None:
                    opportunities.append(opp)

        # Sort by net benefit, highest first
        opportunities.sort(key=lambda o: o.net_benefit, reverse=True)

        if not opportunities:
            logger.info("No harvest opportunities meet the threshold.")
            return

        total_potential_benefit = sum(o.net_benefit for o in opportunities)
        logger.info(
            f"Found {len(opportunities)} harvest opportunities, "
            f"total net benefit: ${total_potential_benefit:.2f}"
        )

        for opp in opportunities:
            await execute_harvest(session, opp, dry_run=dry_run)


async def run_continuous(interval_hours: float = 6.0, dry_run: bool = True):
    """Run harvest cycles continuously."""
    while True:
        logger.info(f"--- Harvest cycle starting {'(DRY RUN) ' if dry_run else ''}---")
        await harvest_cycle(dry_run=dry_run)
        logger.info(f"Cycle complete. Sleeping {interval_hours}h.")
        await asyncio.sleep(interval_hours * 3600)


if __name__ == "__main__":
    # Run in dry-run mode first to verify positions and opportunities
    asyncio.run(run_continuous(interval_hours=6.0, dry_run=True))

Tax Loss Harvesting with Perpetual Futures

For agents using Purple Flea's perpetual futures trading (275 markets via Hyperliquid integration), tax treatment of perp positions differs from spot holdings in most jurisdictions. In the US, futures contracts are typically treated under IRC Section 1256—60% long-term / 40% short-term regardless of holding period, with mark-to-market treatment at year end.

Key implications for harvesting agents:

  • Section 1256 contracts are marked-to-market at December 31—all unrealized gains and losses are recognized at year end regardless of whether positions are closed
  • This means perp traders automatically realize both gains and losses annually, eliminating the need to actively sell for tax purposes
  • However, agents can still optimize by timing the closing of Section 1256 positions against spot portfolio losses/gains
  • Non-US jurisdictions generally do not have the Section 1256 rule—perps are taxed only on close

Perps and the 1256 exemption: Whether exchange-traded crypto perpetuals qualify as Section 1256 contracts is an open IRS question in 2026. Many tax practitioners treat them as property (like spot crypto) rather than Section 1256. Until IRS guidance is clear, agents should consult a tax professional on this classification.

Wash Sale Avoidance Strategies

Even where wash sale rules do not currently apply to crypto, building avoidance strategies now is prudent defensive engineering. If rules change mid-year, an agent without wash sale logic will inadvertently disqualify harvested losses.

Correlated Asset Replacements

The wash sale rule applies to "substantially identical" assets. Using a different but correlated asset during the waiting period maintains market exposure without triggering wash sale disqualification:

  • Sell ETH, buy BTC or WBTC during wait period (high correlation, not identical)
  • Sell SOL, buy AVAX (layer-1 competitors, correlated but not identical)
  • Sell a layer-2 token, buy a different layer-2 token
  • Sell a DeFi governance token, hold ETH as a proxy

Different Entity Harvesting

The wash sale rule in traditional finance applies when the same taxpayer repurchases. Some agent architectures use separate legal entities (different agent wallet IDs) for different strategies. Whether purchases by a related entity trigger wash sale disqualification depends on the facts and entity relationships—consult a tax professional before relying on this approach.

Using Options or Synthetic Exposure

Rather than repurchasing the spot asset, an agent can maintain economic exposure via a call option or a synthetic long position (long call + short put). Options are not "substantially identical" to the underlying in most interpretations, making this a clean wash sale avoidance technique where the rule applies.

Record-Keeping for AI Agents

Agents executing frequent trades across multiple chains generate complex tax records. Automated record-keeping is essential:

import json
from datetime import datetime
from pathlib import Path

TAX_LOG_FILE = Path("/var/log/agent-tax/harvest-log.jsonl")

def log_harvest_event(
    lot_id: str,
    asset: str,
    quantity: float,
    cost_basis_usd: float,
    sale_price_usd: float,
    sale_date: datetime,
    realized_loss_usd: float,
    holding_days: int,
    tx_hash: Optional[str],
    repurchase_date: Optional[datetime],
    repurchase_price_usd: Optional[float],
):
    """Log a harvest event to persistent JSONL file for tax reporting."""
    TAX_LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
    event = {
        "timestamp": datetime.utcnow().isoformat(),
        "event_type": "tax_loss_harvest",
        "lot_id": lot_id,
        "asset": asset,
        "quantity": quantity,
        "cost_basis_usd": cost_basis_usd,
        "sale_price_usd": sale_price_usd,
        "sale_date": sale_date.isoformat(),
        "realized_loss_usd": realized_loss_usd,
        "holding_days": holding_days,
        "is_long_term": holding_days > 365,
        "tx_hash": tx_hash,
        "repurchase_date": repurchase_date.isoformat() if repurchase_date else None,
        "repurchase_price_usd": repurchase_price_usd,
        "new_cost_basis_usd": repurchase_price_usd,  # new basis after harvest + repurchase
    }
    with open(TAX_LOG_FILE, "a") as f:
        f.write(json.dumps(event) + "\n")


def generate_tax_summary(tax_year: int) -> dict:
    """Generate a summary of harvested losses for a tax year."""
    if not TAX_LOG_FILE.exists():
        return {}
    total_short_term = 0.0
    total_long_term = 0.0
    events = []
    with open(TAX_LOG_FILE) as f:
        for line in f:
            event = json.loads(line)
            sale_date = datetime.fromisoformat(event["sale_date"])
            if sale_date.year != tax_year:
                continue
            loss = abs(event["realized_loss_usd"])
            if event["is_long_term"]:
                total_long_term += loss
            else:
                total_short_term += loss
            events.append(event)
    return {
        "tax_year": tax_year,
        "total_short_term_losses": total_short_term,
        "total_long_term_losses": total_long_term,
        "total_losses": total_short_term + total_long_term,
        "harvest_count": len(events),
    }

Tax-Efficient Trading with Purple Flea

Access 275 perpetual markets, multi-chain wallets, and the full Purple Flea API suite to implement tax-optimized agent trading strategies.

Register Your Agent

Summary

Tax loss harvesting is one of the clearest examples of where AI agent automation produces direct, measurable financial benefit. Key takeaways:

  • Crypto is currently not subject to the US wash sale rule—agents can sell and immediately repurchase, capturing the loss while maintaining exposure
  • This favorable treatment has legislative risk; build configurable wash sale windows into any implementation
  • Use HIFO or SpecID lot selection to maximize the loss realized from each harvest sale
  • Only harvest when net benefit (tax saving minus transaction costs) exceeds a meaningful threshold—small losses are not worth the trading overhead
  • Continuous monitoring (every 6-12 hours) outperforms year-end-only harvesting by capturing losses throughout the year
  • Purple Flea's Trading and Wallet APIs provide the position data and execution endpoints needed for a fully automated harvesting agent
  • Keep meticulous JSONL logs of every harvest event; crypto tax reporting requires lot-level trade records

The Python implementation above is a functional starting point. For production, extend with integration to a tax software API (Koinly, TaxBit) for automated form generation, add per-exchange cost basis import, and implement jurisdiction-aware rule switching for agents operating across multiple legal entities.

Combining Spot Harvesting with Perpetual Positions

AI agents using Purple Flea's Trading API across 275 perpetual markets have a powerful additional tool: short perpetual positions as temporary hedges during harvest waiting periods. When an agent sells spot ETH to harvest a loss and wants to maintain market exposure during a wash sale window, opening a long ETH-PERP position creates a synthetic long without touching the spot asset.

This approach:

  • Maintains full market delta (1:1 with spot ETH price movement)
  • Avoids any wash sale concern since the perp is a derivative, not the underlying asset
  • Incurs a funding rate cost (can be positive or negative depending on market conditions)
  • Requires sufficient margin in the trading account
async def hedge_with_perp(
    session: aiohttp.ClientSession,
    asset: str,
    quantity: float,
    pf_api_key: str,
    agent_id: str,
):
    """
    Open a long perp position to hedge spot sale during wash sale window.
    Uses Purple Flea Trading API (Hyperliquid integration).
    """
    payload = {
        "agent_id": agent_id,
        "market": f"{asset}-PERP",
        "side": "long",
        "size": quantity,
        "order_type": "market",
        "reduce_only": False,
    }
    headers = {
        "Authorization": f"Bearer {pf_api_key}",
        "Content-Type": "application/json",
    }
    async with session.post(
        "https://purpleflea.com/api/trading/open",
        json=payload,
        headers=headers,
    ) as resp:
        data = await resp.json()
        if data.get("success"):
            print(f"Opened long {quantity} {asset}-PERP at ${data['fill_price']:.2f}")
        else:
            print(f"Failed to open hedge: {data.get('error')}")
        return data

The funding cost of holding a perpetual hedge for 30 days is typically less than 0.5% in normal market conditions—far less than the tax benefit from harvesting a 5%+ unrealized loss. During periods of extreme positive funding (when longs pay shorts heavily), agents should weigh the funding cost against the tax benefit before using a perp hedge.

Year-Round Tax Calendar for Agent Operators

Beyond continuous harvesting, there are calendar-driven optimization windows:

MonthActionRationale
JanuaryReview prior year harvest log, confirm loss carry-forwardsSet baseline for new tax year
March-AprilFile prior year return or extension; confirm estimated paymentsAvoid underpayment penalties
JuneMid-year portfolio review; harvest losses that have accumulatedDon't wait until year-end rush
SeptemberQ3 review; check if short-term positions can become long-term before year-endRate planning (STCG vs. LTCG)
October-NovemberAggressive harvest scan; identify all positions with >$500 unrealized lossLeave time for wash sale windows if needed
December 31Final deadline for all harvests to count in current tax yearSettlement must occur on or before Dec 31

AI agents running continuously should treat the October-November period as a "harvest intensification" window—lowering the minimum harvest threshold (e.g., from $25 to $10 net benefit) to capture every available loss before the year closes.