def display_ef( stocks: List[str], period: str = "3mo", n_portfolios: int = 300, risk_free: bool = False, ): """Display efficient frontier Parameters ---------- stocks : List[str] List of the stocks to be included in the weights other_args : List[str] argparse other args """ fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) ef, rets, stds = optimizer_model.generate_random_portfolios( stocks, period, n_portfolios) # The ef needs to be deep-copied to avoid error in plotting sharpe ef2 = copy.deepcopy(ef) sharpes = rets / stds ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r") for ticker, ret, std in zip(ef.tickers, ef.expected_returns, np.sqrt(np.diag(ef.cov_matrix))): ax.annotate(ticker, (std * 1.01, ret)) plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True) # Find the tangency portfolio rfrate = get_rf() ef2.max_sharpe(risk_free_rate=rfrate) ret_sharpe, std_sharpe, _ = ef2.portfolio_performance( verbose=True, risk_free_rate=rfrate) ax.scatter(std_sharpe, ret_sharpe, marker="*", s=100, c="r", label="Max Sharpe") # Add risk free line if risk_free: y = ret_sharpe * 1.2 b = get_rf() m = (ret_sharpe - b) / std_sharpe x2 = (y - b) / m x = [0, x2] y = [b, y] line = Line2D(x, y, color="#FF0000", label="Capital Allocation Line") ax.set_xlim(xmin=min(stds) * 0.8) ax.add_line(line) ax.set_title(f"Efficient Frontier simulating {n_portfolios} portfolios") ax.legend() fig.tight_layout() ax.grid(b=True, which="major", color="#666666", linestyle="-") if gtff.USE_ION: plt.ion() plt.show() console.print("")
def __init__(self, ticker: str, audit: bool): self.audit: bool = audit self.wb: Workbook = Workbook() self.ws1: worksheet = self.wb.active self.ws2: worksheet = self.wb.create_sheet("Free Cash Flows") self.ws3: worksheet = self.wb.create_sheet("Explanations") self.ws4: worksheet = self.wb.create_sheet("Ratios") self.ws1.title = "Financials" self.ticker: str = ticker self.now: str = datetime.now().strftime("%Y-%m-%d") self.letter: int = 0 self.is_start: int = 4 self.bs_start: int = 18 self.cf_start: int = 47 self.len_data: int = 0 self.len_pred: int = 10 self.years: List[str] = [] self.rounding: int = 0 self.df_bs: pd.DataFrame = self.get_data("BS", self.bs_start, False) self.df_is: pd.DataFrame = self.get_data("IS", self.is_start, True) self.df_cf: pd.DataFrame = self.get_data("CF", self.cf_start, False) self.info: pd.DataFrame = yf.Ticker(ticker).info self.t_bill: float = get_rf() self.r_ff: float = dcf_model.get_fama_coe(self.ticker) self.sisters: List[str] = dcf_model.others_in_sector( self.ticker, self.info["sector"], self.info["industry"] ) self.sister_data: List[List[pd.DataFrame]] = [[pd.DataFrame()]]
def call_maxsharpe(self, other_args: List[str]): """Process maxsharpe command""" parser = argparse.ArgumentParser( add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, prog="maxsharpe", description="Maximise the Sharpe Ratio", ) parser.add_argument( "-p", "--period", default="3mo", dest="period", help="period to get yfinance data from", choices=period_choices, ) parser.add_argument( "-v", "--value", dest="value", help="Amount to allocate to portfolio", type=float, default=1.0, ) parser.add_argument( "--pie", action="store_true", dest="pie", default=False, help="Display a pie chart for weights", ) parser.add_argument( "-r", "--risk-free-rate", type=float, dest="risk_free_rate", default=get_rf(), help="""Risk-free rate of borrowing/lending. The period of the risk-free rate should correspond to the frequency of expected returns.""", ) try: ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return if len(self.tickers) < 2: print("Please have at least 2 loaded tickers to calculate weights.\n") return optimizer_view.display_max_sharpe( stocks=self.tickers, period=ns_parser.period, value=ns_parser.value, rfrate=ns_parser.risk_free_rate, pie=ns_parser.pie, ) except Exception as e: print(e, "\n")
def __init__( self, ticker: str, audit: bool = False, ratios: bool = True, len_pred: int = 10, max_similars: int = 3, no_filter: bool = False, ): """ Creates a detialed DCF for a given company Parameters ---------- ticker : str The ticker to create a DCF for audit : bool Whether or not to show that the balance sheet and income statement tie-out ratios : bool Whether to show ratios for the company and for similar companies len_pred : int The number of years to make predictions for before assuming a terminal value max_similars : int The maximum number of similar companies to show, will be less if there are not enough similar companies no_filter : bool Disable filtering of similar companies to being in the same market cap category """ self.info: Dict[str, Any] = { "len_data": 0, "len_pred": len_pred, "max_similars": max_similars, "rounding": 0, "ticker": ticker, "audit": audit, "ratios": ratios, "no_filter": no_filter, } self.letter: int = 0 self.starts: Dict[str, int] = {"IS": 4, "BS": 18, "CF": 47} self.wb: Workbook = Workbook() self.ws: Dict[int, Any] = { 1: self.wb.active, 2: self.wb.create_sheet("Free Cash Flows"), 3: self.wb.create_sheet("Explanations"), } self.df: Dict[str, pd.DataFrame] = { "BS": self.get_data("BS", self.starts["BS"], False), "IS": self.get_data("IS", self.starts["IS"], True), "CF": self.get_data("CF", self.starts["CF"], False), } self.data: Dict[str, Any] = { "now": datetime.now().strftime("%Y-%m-%d"), "info": yf.Ticker(ticker).info, "t_bill": get_rf(), "r_ff": dcf_model.get_fama_coe(self.info["ticker"]), }
def __init__(self, df: pd.DataFrame, hist: pd.DataFrame, m_tick: str, n: int): """Generate financial reports. Financial reports allow users to show the how they have been performing in trades. This allows for a simple way to show progress and analyze metrics that track portfolio performance Parameters ---------- df : pd.DataFrame The dataframe with previous holdings information hist : pd.DataFrame The dataframe with previous prices for stocks in the portfolio m_tick : str The market asset to be identified n : int The number of days to analyze Attributes ---------- generate_report : None Generates a report with the given parameters generate_pg1 : None Creates the first page of the PDF report generate_pg2 : None Creates the second page of the PDF report """ self.df = df self.hist = hist self.m_tick = m_tick self.df_m = yfinance_model.get_market(self.df.index[0], self.m_tick) self.returns, self.variance = portfolio_model.get_return( df, self.df_m, n) self.rf = get_rf() self.betas = portfolio_model.get_rolling_beta(self.df, self.hist, self.df_m, 365)
def show_binom( ticker: str, expiration: str, strike: float, put: bool, europe: bool, export: bool, plot: bool, vol: float, ) -> None: """Get binomial pricing for option Parameters ---------- ticker : str The ticker of the option's underlying asset expiration : str The expiration for the option strike : float The strike price for the option put : bool Value a put instead of a call europe : bool Value a European option instead of an American option export : bool Export the options data to an excel spreadsheet plot : bool Show a graph of expected ending prices vol : float The annualized volatility for the underlying asset """ # Base variables to calculate values info = yfinance_model.get_info(ticker) price = info["regularMarketPrice"] if vol is None: closings = yfinance_model.get_closing(ticker) vol = (closings / closings.shift()).std() * (252**0.5) div_yield = (info["trailingAnnualDividendYield"] if info["trailingAnnualDividendYield"] is not None else 0) delta_t = 1 / 252 rf = get_rf() exp_date = datetime.strptime(expiration, "%Y-%m-%d").date() today = date.today() days = (exp_date - today).days # Binomial pricing specific variables up = math.exp(vol * (delta_t**0.5)) down = 1 / up prob_up = (math.exp((rf - div_yield) * delta_t) - down) / (up - down) prob_down = 1 - prob_up discount = math.exp(delta_t * rf) und_vals: List[List[float]] = [[price]] # Binomial tree for underlying values for i in range(days): cur_date = today + timedelta(days=i + 1) if cur_date.weekday() < 5: last = und_vals[-1] new = [x * up for x in last] new.append(last[-1] * down) und_vals.append(new) # Binomial tree for option values if put: opt_vals = [[max(strike - x, 0) for x in und_vals[-1]]] else: opt_vals = [[max(x - strike, 0) for x in und_vals[-1]]] j = 2 while len(opt_vals[0]) > 1: new_vals = [] for i in range(len(opt_vals[0]) - 1): if europe: value = (opt_vals[0][i] * prob_up + opt_vals[0][i + 1] * prob_down) / discount else: if put: value = max( (opt_vals[0][i] * prob_up + opt_vals[0][i + 1] * prob_down) / discount, strike - und_vals[-j][i], ) else: value = max( (opt_vals[0][i] * prob_up + opt_vals[0][i + 1] * prob_down) / discount, und_vals[-j][i] - strike, ) new_vals.append(value) opt_vals.insert(0, new_vals) j += 1 if export: export_binomial_calcs(up, prob_up, discount, und_vals, opt_vals, days, ticker) if plot: plot_expected_prices(und_vals, prob_up, ticker, expiration) option = "put" if put else "call" console.print( f"{ticker} {option} at ${strike:.2f} expiring on {expiration} is worth ${opt_vals[0][0]:.2f}\n" )
def risk_neutral_vals( ticker: str, exp: str, put: bool, df: pd.DataFrame, mini: float, maxi: float, risk: float, ) -> None: """Prints current options prices and risk neutral values [Source: Yahoo Finance] Parameters ---------- ticker : str Ticker to get expirations for exp : str Expiration to use for options put : bool Whether to use puts or calls df : pd.DataFrame Estimates for stocks prices and probabilities mini : float Minimum strike price to show maxi : float Maximum strike price to show risk : float The risk-free rate for the asset """ if put: chain = get_option_chain(ticker, exp).puts else: chain = get_option_chain(ticker, exp).calls r_date = datetime.strptime(exp, "%Y-%m-%d").date() delta = (r_date - date.today()).days vals = [] if risk is None: risk = get_rf() for _, row in chain.iterrows(): vals.append([ row["strike"], row["lastPrice"], op_helpers.rn_payoff(row["strike"], df, put, delta, risk), ]) new_df = pd.DataFrame(vals, columns=["Strike", "Last Price", "Value"], dtype=float) new_df["Difference"] = new_df["Last Price"] - new_df["Value"] if mini is None: mini = new_df.Strike.quantile(0.25) if maxi is None: maxi = new_df.Strike.quantile(0.75) new_df = new_df[new_df["Strike"] >= mini] new_df = new_df[new_df["Strike"] <= maxi] print_rich_table( new_df, headers=[x.title() for x in new_df.columns], show_index=False, title="Risk Neutral Values", ) console.print()
def show_parity(ticker: str, exp: str, put: bool, ask: bool, mini: float, maxi: float, export: str) -> None: """Prints options and whether they are under or over priced [Source: Yahoo Finance] Parameters ---------- ticker : str Ticker to get expirations for exp : str Expiration to use for options put : bool Whether to use puts or calls ask : bool Whether to use ask or lastPrice mini : float Minimum strike price to show maxi : float Maximum strike price to show export : str Export data """ r_date = datetime.strptime(exp, "%Y-%m-%d").date() delta = (r_date - date.today()).days rate = ((1 + get_rf())**(delta / 365)) - 1 stock = get_price(ticker) div_info = yfinance_model.get_dividend(ticker) div_dts = div_info.index.values.tolist() if div_dts: last_div = pd.to_datetime(div_dts[-1]) if len(div_dts) > 3: avg_div = np.mean(div_info.to_list()[-4:]) else: avg_div = np.mean(div_info.to_list()) next_div = last_div + timedelta(days=91) dividends = [] while next_div < datetime.strptime(exp, "%Y-%m-%d"): day_dif = (next_div - datetime.now()).days dividends.append((avg_div, day_dif)) next_div += timedelta(days=91) div_pvs = [x[0] / ((1 + get_rf())**(x[1] / 365)) for x in dividends] pv_dividend = sum(div_pvs) else: pv_dividend = 0 chain = get_option_chain(ticker, exp) name = "ask" if ask else "lastPrice" o_type = "put" if put else "call" calls = chain.calls[["strike", name]].copy() calls = calls.rename(columns={name: "callPrice"}) puts = chain.puts[["strike", name]].copy() puts = puts.rename(columns={name: "putPrice"}) opts = pd.merge(calls, puts, on="strike") opts = opts.dropna() opts = opts.loc[opts["callPrice"] * opts["putPrice"] != 0] opts["callParity"] = (opts["putPrice"] + stock - (opts["strike"] / (1 + rate)) - pv_dividend) opts["putParity"] = ((opts["strike"] / (1 + rate)) + opts["callPrice"] - stock + pv_dividend) diff = o_type + " Difference" opts[diff] = opts[o_type + "Price"] - opts[o_type + "Parity"] opts["distance"] = abs(stock - opts["strike"]) filtered = opts.copy() if mini is None: mini = filtered.strike.quantile(0.25) if maxi is None: maxi = filtered.strike.quantile(0.75) filtered = filtered.loc[filtered["strike"] >= mini] filtered = filtered.loc[filtered["strike"] <= maxi] show = filtered[["strike", diff]].copy() if ask: console.print( "Warning: Options with no current ask price not shown.\n") print_rich_table( show, headers=[x.title() for x in show.columns], show_index=False, title="Warning: Low volume options may be difficult to trade.", ) export_data( export, os.path.dirname(os.path.abspath(__file__)), "parity", show, ) console.print()
def show_greeks( ticker: str, div_cont: float, expire: str, rf: float = None, opt_type: int = 1, mini: float = None, maxi: float = None, show_all: bool = False, ) -> None: """ Shows the greeks for a given option Parameters ---------- ticker : str The ticker value of the option div_cont : float The dividend continuous rate expire : str The date of expiration rf : float The risk-free rate opt_type : Union[1, -1] The option type 1 is for call and -1 is for put mini : float The minimum strike price to include in the table maxi : float The maximum strike price to include in the table all : bool Whether to show all greeks """ s = yfinance_model.get_price(ticker) chains = yfinance_model.get_option_chain(ticker, expire) chain = chains.calls if opt_type == 1 else chains.puts if mini is None: mini = chain.strike.quantile(0.25) if maxi is None: maxi = chain.strike.quantile(0.75) chain = chain[chain["strike"] >= mini] chain = chain[chain["strike"] <= maxi] risk_free = rf if rf is not None else get_rf() expire_dt = datetime.strptime(expire, "%Y-%m-%d") dif = (expire_dt - datetime.now()).seconds / (60 * 60 * 24) strikes = [] for _, row in chain.iterrows(): vol = row["impliedVolatility"] opt = Option(s, row["strike"], risk_free, div_cont, dif, vol, opt_type) result = [ row["strike"], row["impliedVolatility"], opt.Delta(), opt.Gamma(), opt.Vega(), opt.Theta(), ] if show_all: result += [ opt.Rho(), opt.Phi(), opt.Charm(), opt.Vanna(0.01), opt.Vomma(0.01), ] strikes.append(result) columns = [ "Strike", "Implied Vol", "Delta", "Gamma", "Vega", "Theta", ] if show_all: columns += ["Rho", "Phi", "Charm", "Vanna", "Vomma"] df = pd.DataFrame(strikes, columns=columns) print_rich_table(df, headers=list(df.columns), show_index=False, title="Greeks") console.print() return None
def display_ef( stocks: List[str], period: str = "3mo", n_portfolios: int = 300, risk_free: bool = False, external_axes: Optional[List[plt.Axes]] = None, ): """Display efficient frontier Parameters ---------- stocks : List[str] List of the stocks to be included in the weights period : str Time period to get returns for n_portfolios: int Number of portfolios to simulate external_axes: Optional[List[plt.Axes]] Optional axes to plot on """ if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: ax = external_axes[0] ef, rets, stds = optimizer_model.generate_random_portfolios( stocks, period, n_portfolios) # The ef needs to be deep-copied to avoid error in plotting sharpe ef2 = copy.deepcopy(ef) sharpes = rets / stds ax.scatter(stds, rets, marker=".", c=sharpes) plotting.plot_efficient_frontier(ef, ax=ax, show_assets=False) for ticker, ret, std in zip(ef.tickers, ef.expected_returns, np.sqrt(np.diag(ef.cov_matrix))): ax.scatter(std, ret, s=50, marker=".", c="w") ax.annotate(ticker, (std * 1.01, ret)) # Find the tangency portfolio rfrate = get_rf() ef2.max_sharpe(risk_free_rate=rfrate) ret_sharpe, std_sharpe, _ = ef2.portfolio_performance( verbose=True, risk_free_rate=rfrate) ax.scatter(std_sharpe, ret_sharpe, marker="*", s=100, c="r", label="Max Sharpe") # Add risk free line if risk_free: y = ret_sharpe * 1.2 b = get_rf() m = (ret_sharpe - b) / std_sharpe x2 = (y - b) / m x = [0, x2] y = [b, y] line = Line2D(x, y, label="Capital Allocation Line") ax.set_xlim(xmin=min(stds) * 0.8) ax.add_line(line) ax.set_title(f"Efficient Frontier simulating {n_portfolios} portfolios") ax.legend(loc="best", scatterpoints=1) theme.style_primary_axis(ax) if external_axes is None: theme.visualize_output()