Skip to main content
Glama
knishioka

IB Analytics MCP Server

by knishioka
console.py8.61 kB
"""Console report generator""" from rich.console import Console from rich.panel import Panel from rich.table import Table from ib_sec_mcp.analyzers.base import AnalysisResult from ib_sec_mcp.reports.base import BaseReport class ConsoleReport(BaseReport): """ Generate formatted console report using Rich Provides beautiful console output with tables and formatting """ def __init__(self, results: list[AnalysisResult]): """Initialize console report""" super().__init__(results) self.console = Console() def render(self) -> str: """Render report to console""" for result in self.results: analyzer_name = result.get("analyzer", "Unknown") # Create section header header = f"📊 {analyzer_name.upper()} ANALYSIS" self.console.print(f"\n{'=' * 80}", style="bold blue") self.console.print(header, style="bold blue") self.console.print(f"{'=' * 80}\n", style="bold blue") # Render based on analyzer type if analyzer_name == "Performance": self._render_performance(result) elif analyzer_name == "Cost": self._render_cost(result) elif analyzer_name == "Bond": self._render_bond(result) elif analyzer_name == "Tax": self._render_tax(result) elif analyzer_name == "Risk": self._render_risk(result) else: self._render_generic(result) return "" def save(self, filepath: str) -> None: """Save report to file""" # Use Rich's export to HTML or text with open(filepath, "w") as f: # For now, just write a simple text version f.write("IB Analytics Report\n") f.write("=" * 80 + "\n\n") for result in self.results: f.write(f"{result.get('analyzer', 'Unknown')} Analysis\n") f.write("-" * 80 + "\n") for key, value in result.items(): if key not in ["analyzer", "timestamp"]: f.write(f"{key}: {value}\n") f.write("\n") def _render_performance(self, result: AnalysisResult) -> None: """Render performance analysis""" # Overall metrics table table = Table(title="Overall Performance Metrics", show_header=True) table.add_column("Metric", style="cyan") table.add_column("Value", style="green") table.add_row("Total Trades", str(result.get("total_trades", 0))) table.add_row("Total Positions", str(result.get("total_positions", 0))) table.add_row("Realized P&L", f"${result.get('total_realized_pnl', '0')}") table.add_row("Unrealized P&L", f"${result.get('total_unrealized_pnl', '0')}") table.add_row("Total P&L", f"${result.get('total_pnl', '0')}") table.add_row("", "") table.add_row("Win Rate", f"{result.get('win_rate', '0')}%") table.add_row("Winning Trades", str(result.get("winning_trades", 0))) table.add_row("Losing Trades", str(result.get("losing_trades", 0))) table.add_row("Profit Factor", result.get("profit_factor", "0")) table.add_row("", "") table.add_row("Average Win", f"${result.get('avg_win', '0')}") table.add_row("Average Loss", f"${result.get('avg_loss', '0')}") table.add_row("Risk/Reward Ratio", result.get("risk_reward_ratio", "0")) self.console.print(table) # By symbol breakdown by_symbol = result.get("by_symbol", {}) if by_symbol: self.console.print("\n📈 Performance by Symbol\n", style="bold") for symbol, data in by_symbol.items(): self.console.print(f"\n[bold]{symbol}[/bold]") self.console.print(f" Trades: {data['trade_count']}") self.console.print(f" Win Rate: {data['win_rate']}%") self.console.print(f" Profit Factor: {data['profit_factor']}") self.console.print(f" Realized P&L: ${data['realized_pnl']}") def _render_cost(self, result: AnalysisResult) -> None: """Render cost analysis""" table = Table(title="Cost Analysis", show_header=True) table.add_column("Metric", style="cyan") table.add_column("Value", style="yellow") table.add_row("Total Commissions", f"${result.get('total_commissions', '0')}") table.add_row("Total Volume", f"${result.get('total_volume', '0')}") table.add_row("Commission Rate", f"{result.get('commission_rate', '0')}%") table.add_row("Avg Commission/Trade", f"${result.get('avg_commission_per_trade', '0')}") table.add_row("", "") table.add_row("Commission Impact on P&L", f"{result.get('commission_impact_pct', '0')}%") self.console.print(table) def _render_bond(self, result: AnalysisResult) -> None: """Render bond analysis""" if not result.get("has_bonds"): self.console.print("No bond positions or trades found.", style="yellow") return # Current holdings holdings = result.get("current_holdings", []) if holdings: self.console.print("\n[bold]Current Bond Holdings[/bold]\n") for holding in holdings: panel = Panel( f"Symbol: {holding['symbol']}\n" f"Description: {holding.get('description', 'N/A')}\n" f"Value: ${holding['position_value']}\n" f"Unrealized P&L: ${holding['unrealized_pnl']} ({holding['unrealized_pnl_pct']}%)\n" f"Maturity: {holding.get('maturity_date', 'N/A')}\n" f"YTM: {holding['ytm']}%\n" f"Duration: {holding['duration']} years", title=holding["symbol"], border_style="green", ) self.console.print(panel) def _render_tax(self, result: AnalysisResult) -> None: """Render tax analysis""" table = Table(title=f"Tax Analysis (Rate: {result.get('tax_rate', '0')})", show_header=True) table.add_column("Item", style="cyan") table.add_column("Amount", style="red") table.add_row("Realized P&L", f"${result.get('total_realized_pnl', '0')}") table.add_row( "Estimated Capital Gains Tax", f"${result.get('estimated_capital_gains_tax', '0')}" ) table.add_row("", "") table.add_row("Phantom Income (OID)", f"${result.get('phantom_income_total', '0')}") table.add_row( "Estimated Phantom Income Tax", f"${result.get('estimated_phantom_income_tax', '0')}" ) table.add_row("", "") table.add_row( "[bold]Total Estimated Tax[/bold]", f"[bold]${result.get('total_estimated_tax', '0')}[/bold]", ) self.console.print(table) self.console.print(f"\n⚠️ {result.get('disclaimer', '')}", style="yellow italic") def _render_risk(self, result: AnalysisResult) -> None: """Render risk analysis""" if result.get("has_bond_exposure"): self.console.print("\n[bold]Interest Rate Risk Scenarios[/bold]\n") scenarios = result.get("interest_rate_scenarios", {}).get("scenarios", []) for scenario in scenarios: self.console.print(f"\n{scenario['scenario']}: {scenario['total_value_change']}") # Concentration concentration = result.get("concentration", {}) if concentration.get("top_positions"): self.console.print("\n[bold]Portfolio Concentration[/bold]\n") self.console.print( f"Max Concentration: {concentration.get('max_concentration', '0')}%\n" ) table = Table(show_header=True) table.add_column("Symbol") table.add_column("Value", justify="right") table.add_column("Allocation", justify="right") for pos in concentration["top_positions"][:5]: table.add_row( pos["symbol"], f"${pos['value']}", f"{pos['allocation_pct']}%", ) self.console.print(table) def _render_generic(self, result: AnalysisResult) -> None: """Render generic result""" for key, value in result.items(): if key not in ["analyzer", "timestamp", "is_multi_account"]: self.console.print(f"{key}: {value}")

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