Opportunity cost measures the value forgone by harvesting a loss now rather than retaining the possibility of a more valuable later tax outcome. In tax-loss harvesting, that foregone possibility is treated like an American put option: it may become profitable if the asset price falls, but may expire worthless. section 14.3.2
Estimate the cost by pricing an option with inputs that describe the lot and market assumptions:
- Use the current asset price, strike price, remaining life, annualized volatility, risk-free rate, and dividend yield. section 14.3.2
- Price an American put with the option-price package and its binomial-tree method rather than deriving an options model. section 14.3.2
- Accept a harvesting trade only when its tax benefit exceeds both trading costs and opportunity cost. section 14.3.3 section 14.5
Evaluating a loss lot
For a lot trading at a loss, calculate net tax benefit after trading costs and after opportunity cost. Generate a trade only if both results are positive; then select eligible replacement ETFs while avoiding wash sales. section 14.3.3
Listing 14.12 Calculating TLH benefit after opportunity cost
def evaluate_opp_cost(lot: pd.Series, price: float, current_date: dt.date,
tax_params: Dict, sigma: float, risk_free: float,
div_yield: float):
""" Evaluate a loss harvest based on the opportunity cost
vs the tax benefit """
purchase_date = dt.date.fromisoformat(lot['purchase_date'])
holding_period = (current_date - purchase_date).days
if holding_period >= tax_params['lt_cutoff']: #1
rate_now = rate_later = tax_params['lt_rate']
else:
rate_now = tax_params['st_rate']
rate_later = tax_params['lt_rate']
price = float(price)
o_full = Option(european=False, kind='put', s0=price, k=price,
t=30, sigma=sigma, r=risk_free, dv=div_yield)
p_full = o_full.getPrice('BT') #2
days_to_switch = tax_params['lt_cutoff'] - holding_period
if 0 < days_to_switch <= 30:
o_pre_switch = Option(european=False, kind='put', s0=price,
k=price, t=days_to_switch, sigma=sigma,
r=risk_free, dv=div_yield)
p_til_switch = o_pre_switch.getPrice('BT') #3
p_after_switch = p_full - p_til_switch #4
opp_cost = rate_now * p_til_switch + rate_later * p_after_switch
else:
opp_cost = rate_now * p_full
benefit = rate_now * (lot['purchase_price'] - price)
return benefit - opp_cost
The function prices the forgone option value from price, volatility, rates, dividends, holding period, and tax rates, then compares it with the tax benefit of harvesting. section 14.3.2
