def summarize_account(self): account_summary = self.ib.accountSummary(self.config["account"]["number"]) click.echo() click.secho(f"Account summary:", fg="green") click.echo() account_summary = account_summary_to_dict(account_summary) click.secho( f" Excess liquidity = {justify(account_summary['ExcessLiquidity'].value)}", fg="cyan", ) click.secho( f" Net liquidation = {justify(account_summary['NetLiquidation'].value)}", fg="cyan", ) click.secho( f" Cushion = {account_summary['Cushion'].value} ({round(float(account_summary['Cushion'].value) * 100, 1)}%)", fg="cyan", ) click.secho( f" Full maint margin = {justify(account_summary['FullMaintMarginReq'].value)}", fg="cyan", ) click.secho( f" Buying power = {justify(account_summary['BuyingPower'].value)}", fg="cyan", ) click.secho( f" Total cash value = {justify(account_summary['TotalCashValue'].value)}", fg="cyan", ) portfolio_positions = self.get_portfolio_positions() click.echo() click.secho("Portfolio positions:", fg="green") click.echo() for symbol in portfolio_positions.keys(): click.secho(f" {symbol}:", fg="cyan") for p in portfolio_positions[symbol]: if isinstance(p.contract, Stock): pnl = round(position_pnl(p) * 100, 2) click.secho( f" Stock Qty={int(p.position)} Price={round(p.marketPrice,2)} Value={round(p.marketValue,2)} AvgCost={round(p.averageCost,2)} P&L={pnl}%", fg="cyan", ) elif isinstance(p.contract, Option): pnl = round(position_pnl(p) * 100, 2) def p_or_c(p): return "Call" if p.contract.right.startswith("C") else "Put " click.secho( f" {p_or_c(p)} Qty={int(p.position)} Price={round(p.marketPrice,2)} Value={round(p.marketValue,2)} AvgCost={round(p.averageCost,2)} P&L={pnl}% Strike={p.contract.strike} Exp={p.contract.lastTradeDateOrContractMonth}", fg="cyan", ) else: click.secho(f" {p.contract}", fg="cyan") return (account_summary, portfolio_positions)
def put_can_be_rolled(self, put): # Check if this put is ITM, and if it's o.k. to roll if not self.config["roll_when"]["puts"]["itm"] and self.put_is_itm( put.contract): return False dte = option_dte(put.contract.lastTradeDateOrContractMonth) pnl = position_pnl(put) roll_when_dte = self.config["roll_when"]["dte"] roll_when_pnl = self.config["roll_when"]["pnl"] roll_when_min_pnl = self.config["roll_when"]["min_pnl"] if dte <= roll_when_dte: if pnl >= roll_when_min_pnl: click.secho( f" {put.contract.localSymbol} can be rolled because DTE of {dte} is <= {self.config['roll_when']['dte']} and P&L of {round(pnl * 100, 1)}% is >= {round(roll_when_min_pnl * 100, 1)}%", fg="blue", ) return True else: click.secho( f" {put.contract.localSymbol} can't be rolled because P&L of {round(pnl * 100, 1)}% is < {round(roll_when_min_pnl * 100, 1)}%", fg="red", ) if pnl >= roll_when_pnl: click.secho( f" {put.contract.localSymbol} can be rolled because P&L of {round(pnl * 100, 1)}% is >= {round(roll_when_pnl * 100, 1)}", fg="blue", ) return True return False
def call_can_be_rolled(self, call): # Check if this call is ITM, and it's o.k. to roll if ("calls" in self.config["roll_when"] and not self.config["roll_when"]["calls"]["itm"] ) and self.call_is_itm(call.contract): return False dte = option_dte(call.contract.lastTradeDateOrContractMonth) pnl = position_pnl(call) if dte <= self.config["roll_when"]["dte"]: click.secho( f"{call.contract.localSymbol} can be rolled because DTE of {dte} is <= {self.config['roll_when']['dte']}", fg="blue", ) return True if pnl >= self.config["roll_when"]["pnl"]: click.secho( f"{call.contract.localSymbol} can be rolled because P&L of {round(pnl * 100, 1)}% is >= {round(self.config['roll_when']['pnl'] * 100, 1)}", fg="blue", ) return True return False
def call_can_be_rolled(self, call): # Ignore long positions, we only roll shorts if call.position > 0: return False # Check if this call is ITM, and it's o.k. to roll if not self.config["roll_when"]["calls"]["itm"] and self.call_is_itm( call.contract ): return False dte = option_dte(call.contract.lastTradeDateOrContractMonth) pnl = position_pnl(call) roll_when_dte = self.config["roll_when"]["dte"] roll_when_pnl = self.config["roll_when"]["pnl"] roll_when_min_pnl = self.config["roll_when"]["min_pnl"] if ( "max_dte" in self.config["roll_when"] and dte > self.config["roll_when"]["max_dte"] ): return False if dte <= roll_when_dte: if pnl >= roll_when_min_pnl: click.secho( f" {call.contract.localSymbol} can be rolled because DTE of {dte} is <= {self.config['roll_when']['dte']} and P&L of {round(pnl * 100, 1)}% is >= {round(roll_when_min_pnl * 100, 1)}%", fg="blue", ) return True else: click.secho( f" {call.contract.localSymbol} can't be rolled because P&L of {round(pnl * 100, 1)}% is < {round(roll_when_min_pnl * 100, 1)}%", fg="red", ) if pnl >= roll_when_pnl: click.secho( f" {call.contract.localSymbol} can be rolled because P&L of {round(pnl * 100, 1)}% is >= {round(roll_when_pnl * 100, 1)}", fg="blue", ) return True return False
def call_can_be_rolled(self, call): dte = option_dte(call.contract.lastTradeDateOrContractMonth) pnl = position_pnl(call) if dte <= self.config["roll_when"]["dte"]: click.secho( f"{call.contract.localSymbol} can be rolled because DTE of {dte} is <= {self.config['roll_when']['dte']}", fg="blue", ) return True if pnl >= self.config["roll_when"]["pnl"]: click.secho( f"{call.contract.localSymbol} can be rolled because P&L of {round(pnl * 100, 1)}% is >= {round(self.config['roll_when']['pnl'] * 100,1)}", fg="blue", ) return True return False
def put_can_be_rolled(self, put): # Check if this put is ITM. Do not roll ITM puts. if self.put_is_itm(put.contract): return False dte = option_dte(put.contract.lastTradeDateOrContractMonth) pnl = position_pnl(put) if dte <= self.config["roll_when"]["dte"]: click.secho( f" {put.contract.localSymbol} can be rolled because DTE of {dte} is <= {self.config['roll_when']['dte']}", fg="blue", ) return True if pnl >= self.config["roll_when"]["pnl"]: click.secho( f" {put.contract.localSymbol} can be rolled because P&L of {round(pnl * 100, 1)}% is >= {round(self.config['roll_when']['pnl'] * 100,1)}", fg="blue", ) return True return False
def test_position_pnl(): qqq_put = PortfolioItem( contract=Option( conId=397556522, symbol="QQQ", lastTradeDateOrContractMonth="20201218", strike=300.0, right="P", multiplier="100", primaryExchange="AMEX", currency="USD", localSymbol="QQQ 201218P00300000", tradingClass="QQQ", ), position=-1.0, marketPrice=4.1194396, marketValue=-411.94, averageCost=222.4293, unrealizedPNL=-189.51, realizedPNL=0.0, account="DU2962946", ) assert round(position_pnl(qqq_put), 2) == -0.85 spy = PortfolioItem( contract=Stock( conId=756733, symbol="SPY", right="0", primaryExchange="ARCA", currency="USD", localSymbol="SPY", tradingClass="SPY", ), position=100.0, marketPrice=365.4960022, marketValue=36549.6, averageCost=368.42, unrealizedPNL=-292.4, realizedPNL=0.0, account="DU2962946", ) assert round(position_pnl(spy), 4) == -0.0079 spy_call = PortfolioItem( contract=Option( conId=454208258, symbol="SPY", lastTradeDateOrContractMonth="20201214", strike=373.0, right="C", multiplier="100", primaryExchange="AMEX", currency="USD", localSymbol="SPY 201214C00373000", tradingClass="SPY", ), position=-1.0, marketPrice=0.08, marketValue=-8.0, averageCost=96.422, unrealizedPNL=88.42, realizedPNL=0.0, account="DU2962946", ) assert round(position_pnl(spy_call), 2) == 0.92 spy_put = PortfolioItem( contract=Option( conId=458705534, symbol="SPY", lastTradeDateOrContractMonth="20210122", strike=352.5, right="P", multiplier="100", primaryExchange="AMEX", currency="USD", localSymbol="SPY 210122P00352500", tradingClass="SPY", ), position=-1.0, marketPrice=5.96710015, marketValue=-596.71, averageCost=528.9025, unrealizedPNL=-67.81, realizedPNL=0.0, account="DU2962946", ) assert round(position_pnl(spy_put), 2) == -0.13