Skip to main content
Glama
knishioka

IB Analytics MCP Server

by knishioka
tax.py5.34 kB
"""Tax liability analyzer""" from datetime import date from decimal import Decimal from typing import Any from ib_sec_mcp.analyzers.base import AnalysisResult, BaseAnalyzer from ib_sec_mcp.core.calculator import PerformanceCalculator from ib_sec_mcp.models.position import Position from ib_sec_mcp.models.trade import AssetClass class TaxAnalyzer(BaseAnalyzer): """ Analyze tax liabilities Focuses on phantom income (OID) for zero-coupon bonds """ def __init__(self, tax_rate: Decimal = Decimal("0.30"), **kwargs: Any) -> None: """ Initialize tax analyzer Args: tax_rate: Marginal tax rate (default 30%) **kwargs: Passed to BaseAnalyzer """ super().__init__(**kwargs) self.tax_rate = tax_rate def analyze(self) -> AnalysisResult: """ Run tax analysis Returns: AnalysisResult with tax metrics """ trades = self.get_trades() positions = self.get_positions() # Realized gains/losses (capital gains tax) total_realized_pnl: Decimal = sum((t.fifo_pnl_realized for t in trades), Decimal("0")) short_term_gains: Decimal = sum( ( t.fifo_pnl_realized for t in trades if t.fifo_pnl_realized > 0 and t.settle_date and (t.settle_date - t.trade_date).days <= 365 ), Decimal("0"), ) long_term_gains: Decimal = sum( ( t.fifo_pnl_realized for t in trades if t.fifo_pnl_realized > 0 and t.settle_date and (t.settle_date - t.trade_date).days > 365 ), Decimal("0"), ) # Phantom income (OID) for zero-coupon bonds bond_positions = [p for p in positions if p.asset_class == AssetClass.BOND] phantom_income_analysis = self._analyze_phantom_income(bond_positions) # Estimated tax liabilities capital_gains_tax = ( total_realized_pnl * self.tax_rate if total_realized_pnl > 0 else Decimal("0") ) phantom_income_tax = phantom_income_analysis["total_phantom_income"] * self.tax_rate total_estimated_tax = capital_gains_tax + phantom_income_tax return self._create_result( tax_rate=str(self.tax_rate), # Capital gains total_realized_pnl=str(total_realized_pnl), short_term_gains=str(short_term_gains), long_term_gains=str(long_term_gains), estimated_capital_gains_tax=str(capital_gains_tax), # Phantom income phantom_income_total=str(phantom_income_analysis["total_phantom_income"]), phantom_income_by_position=phantom_income_analysis["by_position"], estimated_phantom_income_tax=str(phantom_income_tax), # Total total_estimated_tax=str(total_estimated_tax), # Disclaimer disclaimer=( "This is an estimate only. Tax treatment depends on your residency, " "filing status, and applicable tax treaties. Consult a tax professional." ), ) def _analyze_phantom_income(self, bond_positions: list[Position]) -> dict[str, Any]: """Analyze phantom income for zero-coupon bonds""" total_phantom_income = Decimal("0") by_position = [] current_year = date.today().year for position in bond_positions: if not position.maturity_date or position.coupon_rate and position.coupon_rate > 0: # Not a zero-coupon bond continue # Calculate days held this year year_start = date(current_year, 1, 1) year_end = date(current_year, 12, 31) # Assume position held for entire year (simplified) # In practice, need to track purchase date days_held = (year_end - year_start).days + 1 # Calculate phantom income face_value = abs(position.quantity) years_to_maturity = Decimal((position.maturity_date - date.today()).days) / Decimal( "365.25" ) phantom_income = PerformanceCalculator.calculate_phantom_income( purchase_price=position.cost_basis, face_value=face_value, years_to_maturity=years_to_maturity, days_held=days_held, ) total_phantom_income += phantom_income by_position.append( { "symbol": position.symbol, "description": position.description, "face_value": str(face_value), "cost_basis": str(position.cost_basis), "maturity_date": position.maturity_date.isoformat(), "years_to_maturity": str(years_to_maturity), "days_held_this_year": days_held, "phantom_income": str(phantom_income), "estimated_tax": str(phantom_income * self.tax_rate), } ) return { "total_phantom_income": total_phantom_income, "by_position": by_position, }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/knishioka/ib-sec-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server