def __init__(self, tickers=()): self._portfolio = {} self._transactions = slurp.transactions() portfolio_is_complete = False if bool(len(tickers) == 0): portfolio_is_complete = True tickers = tuple(api.symbols(remove='expired')) self._portfolio.update({ ticker: stocks.Stock(self, ticker) for ticker in filter(api.whitelisted, tickers) }) self._stocks = slurp.stocks( self._transactions, self._portfolio, portfolio_is_complete=portfolio_is_complete, ) profile = api.rh.build_user_profile() self._equity = F(profile['equity']) self._extended_hours_equity = F(profile['extended_hours_equity']) self._cash = F(profile['cash']) self._dividends_total = F(profile['dividend_total'])
def timed(*args, **kwargs): start = F(time()) try: return func(*args, **kwargs) finally: MEASURED[func.__name__]['count'] += 1 MEASURED[func.__name__]['time'] += F(time()) - start
def _option_orders(): closed = defaultdict(list) premiums = defaultdict(lambda: 0) data = api.orders('options', 'all') for option in [o for o in data if o['state'] not in ('cancelled', )]: ticker = option['chain_symbol'] strategies = [] o, c = option['opening_strategy'], option['closing_strategy'] if o: tokens = o.split('_') strategies.append('o[%s]' % ' '.join(tokens)) if c: tokens = c.split('_') strategies.append('c[%s]' % ' '.join(tokens)) legs = [] premium = 0 last_leg_strike_price = 0 for leg in option['legs']: uri = leg['option'] instrument = api.instrument(uri) legs.append('%s to %s K=%s X=%s' % ( leg['side'], leg['position_effect'], util.color.mulla(instrument['strike_price']), instrument['expiration_date'], )) premium += sum([ 100 * F(x['price']) * F(x['quantity']) for x in leg['executions'] ]) last_leg_strike_price = instrument['strike_price'] premium *= -1 if option['direction'] == 'debit' else +1 premiums[ticker] += premium closed[ticker].append("%s %s %s x%s P=%s K=%s" % ( option['state'], option['type'], '/'.join(strategies), util.color.qty(option['quantity'], 0), util.color.mulla(premium), util.color.mulla(last_leg_strike_price), )) for l in legs: closed[ticker].append(' + l:%s' % l) return dict( closed=closed, premiums=premiums, )
def ebitda(ticker): """ Earnings Before Interest, Taxes, Depreciation and Amortization Used to measure the cash flow of a business. """ key = 'EBITDA' return F(stats(ticker)[key])
def _qty(m): m = F(m) if isnan(m): return NaN, None if abs(m) < 1000: s = '{:,.2f}'.format(abs(m)) c = 'red' if m < 0 else 'yellow' elif abs(m) < 1000000: s = '{:,}'.format(abs(round(m))) c = 'red' if m < 0 else 'green' if m >= 10000 else 'yellow' else: compressors = [ ('K', 'yellow'), ('M', 'green'), ('B', 'blue'), ('T', 'magenta'), ] i = -1 while abs(m) > 1000: m //= 1000 i += 1 s = '{:,.1f}'.format(abs(round(m))) if i > -1: s += compressors[i][0] c = compressors[i][1] return s, c
def dividends_paid(ticker): """ This is dividends paid by the company, nothing to do with Robinhood profile and individual's account. """ key = 'dividendsPaid' f = financials(ticker) return NaN if f is None else F(f[key])
def _sharpe_on_ticker(ticker): ''' Sharpe Ratio for a single stock ''' risk_free_ror = risk_free_rate_of_return() adj_close_price = slurp.stock_historic_prices(ticker)['Adj Close'] daily_returns = adj_close_price.pct_change() returns_mean = F(np.mean(daily_returns)) variance = daily_returns.cov(daily_returns) # AKA ~np.var(daily_returns) volatility = F(np.sqrt(variance)) # AKA Standard Deviation, AKA Risk sharpe = util.numbers.NaN if volatility: sharpe = (ANNUAL_TRADING_DAYS * returns_mean - risk_free_ror) / volatility return sharpe
def cp(ticker, cp): return F( stats(ticker)[dict( y5cp='year5ChangePercent', y2cp='year2ChangePercent', y1cp='year1ChangePercent', m6cp='month6ChangePercent', m3cp='month3ChangePercent', m1cp='month1ChangePercent', d30cp='day30ChangePercent', d5cp='day5ChangePercent', )[cp]])
def revenue(ticker, total=False, per=None): assert per is None or total is False if per == 'share': key = 'revenuePerShare' elif per == 'employee': key = 'revenuePerEmployee' elif total: key = 'totalRevenue' else: key = 'revenue' return F(stats(ticker)[key])
def ebit(ticker): """ Earnings Before Interest and Taxes; also referred to as `operating earnings', `operating profit', and `profit before interest and taxes'. EBIT is used to measure a firm's operating income. EBIT can be calculated as revenue minus expenses excluding tax and interest. """ key = 'ebit' f = financials(ticker) return NaN if f is None else F(f[key])
def ev(ticker, ratio=None): """ Enterprise Value To calculate enterprise value, add the company's market capitalization to its outstanding preferred stock and all debt obligations, then subtract all of its cash and cash equivalents. """ numerator = 'enterpriseValue' denominator = None if ratio == 'ev2r': numerator = 'enterpriseValueToRevenue' elif ratio == 'ev2gp': numerator = 'enterpriseValue' denominator = 'grossProfit' elif ratio == 'ev2ebitda': denominator = 'EBITDA' elif ratio == 'ebit2ev': denominator = numerator numerator = 'EBIT' s = stats(ticker) return F(s[numerator]) / F(1 if denominator is None else s[denominator])
def price(ticker, ratio=None): """ Valuation Multiples/Ratios - P/E Ratio: what it is that you are actually paying for; aka, "earnings multiple" - P/E/G Ratio (G is expected growth) """ if ratio is None: return _price_agg()[ticker] key = { 'peg': 'pegRatio', 'p2e': 'peRatio', 'p2s': 'priceToSales', 'p2b': 'priceToBook', }[ratio] return F(stats(ticker)[key])
def treynor(ticker, beta): ''' Treynor Ratio for a single stock ''' risk_free_ror = risk_free_rate_of_return() adj_close_price = slurp.stock_historic_prices(ticker)['Adj Close'] daily_returns = adj_close_price.pct_change() returns_mean = F(np.mean(daily_returns)) variance = daily_returns.cov(daily_returns) # AKA ~np.var(daily_returns) volatility = beta # AKA correlation to S&P500/Other Index, Risk sharpe = util.numbers.NaN if volatility: sharpe = (ANNUAL_TRADING_DAYS * returns_mean - risk_free_ror) / volatility return sharpe
def _option_positions(prices): next_expiries = {} data = api.positions('options', 'all') collaterals = defaultdict(lambda: {'put': 0, 'call': 0}) opened = defaultdict(list) urgencies = defaultdict(F) for option in [o for o in data if F(o['quantity']) != 0]: tocker = option['chain_symbol'] ticker = api.tocker2ticker(tocker) price = F(prices[ticker]) uri = option['option'] instrument = api.instrument(uri) premium = 0 if instrument['state'] != 'queued': premium -= F(option['quantity']) * F(option['average_price']) itype = instrument['type'] otype = option['type'] s_k_ratio = price / F(instrument['strike_price']) urgencies[ticker] = max( (urgencies[ticker], util.color.wtm_urgency(s_k_ratio, otype, itype))) opened[ticker].append( "%s %s %s x%s P=%s K=%s X=%s %s" % (instrument['state'], util.color.otype(otype), util.color.itype(itype), util.color.qty( option['quantity'], 0), util.color.mulla(premium), util.color.mulla( instrument['strike_price']), instrument['expiration_date'], util.color.wtm(s_k_ratio, otype, itype))) expiry = util.datetime.parse(instrument['expiration_date']) if next_expiries.get(ticker) is None: next_expiries[ticker] = expiry else: next_expiries[ticker] = min(next_expiries[ticker], expiry) collaterals[ticker][itype] += 100 * F( dict(put=instrument['strike_price'], call=option['quantity'])[itype]) return dict( collaterals=collaterals, urgencies=urgencies, next_expiries=next_expiries, opened=opened, )
def roe(ticker): """ Return on equity (ROE) is a measure of financial performance calculated by dividing net income by shareholders' equity. Because shareholders' equity is equal to a company’s assets minus its debt, ROE is considered the return on net assets. ROE is considered a measure of the profitability of a corporation in relation to stockholders’ equity. https://www.investopedia.com/terms/r/returnonequity.asp The ROE bridges the gap between the income statement and the balance sheet. ROE analysis is unreliable when: - special dividends - negative equity (cash buybacks) - negative equity (recent return to profitability) - no peer group (ROE is only meaningful when used comparitively) """ netinc = earnings(ticker) bs = balancesheet(ticker, period='quarter', last=1) if len(bs) == 0: return NaN return netinc / F(bs[0]['shareholderEquity'])
def _extensions(S, T): return ( Field( name='roe', getter=SimpleField(caller=api.roe), pullcast=F, pushcast=util.color.mpct, description='Return On Equity', documentation='https://www.investopedia.com/terms/r/returnonequity.asp', ), Field( name='roic', getter=SimpleField(caller=api.roic), pullcast=F, pushcast=util.color.pct, description='ROIC', documentation='https://www.investopedia.com/terms/r/returnoninvestmentcapital.asp', ), Field( name='ebt', getter=SimpleField(caller=api.ebt), pullcast=F, pushcast=util.color.mulla, description='EBT', documentation='https://www.investopedia.com/terms/e/ebt.asp', ), Field( name='ebit', getter=SimpleField(caller=api.ebit), pullcast=F, pushcast=util.color.mulla, description='EBIT', documentation='https://www.investopedia.com/terms/e/ebit.asp', ), Field( name='ebitda', getter=SimpleField(caller=api.ebitda), pullcast=F, pushcast=util.color.mulla, description='EBITDA', documentation='https://www.investopedia.com/terms/e/ebitda.asp', ), Field( name='p2e', getter=SimpleField(caller=lambda ticker: api.price(ticker, ratio='p2e')), pullcast=F, pushcast=util.color.qty, description='P/E Ratio', documentation='https://www.investopedia.com/terms/i/industry.asp', ), Field( name='p2b', getter=SimpleField(caller=lambda ticker: api.price(ticker, ratio='p2b')), pullcast=F, pushcast=util.color.qty, description='P/B Ratio', documentation='https://www.investopedia.com/terms/i/industry.asp', ), Field( name='p2s', getter=SimpleField(caller=lambda ticker: api.price(ticker, ratio='p2s')), pullcast=F, pushcast=util.color.qty, description='P/S Ratio', documentation='https://www.investopedia.com/terms/i/industry.asp', ), Field( name='peg', getter=SimpleField(caller=lambda ticker: api.price(ticker, ratio='peg')), pullcast=F, pushcast=util.color.qty, description='PEG Ratio', documentation='https://www.investopedia.com/terms/i/industry.asp', ), Field( name='industry', getter=SimpleField(caller=api.industry), pullcast=str, pushcast=util.color.colhashwrap, description='Industry', documentation='https://www.investopedia.com/terms/i/industry.asp', ), Field( name='sector', getter=SimpleField(caller=api.sector), pullcast=str, pushcast=util.color.colhashwrap, description='Sector', documentation='https://www.investopedia.com/terms/s/sector.asp', ), Field( name='ev', getter=SimpleField(caller=api.ev), pullcast=F, pushcast=util.color.mulla, description='Enterprise Value; the market value plus the net interest-bearing debt', documentation='https://www.investopedia.com/ask/answers/111414/whats-difference-between-enterprise-value-and-market-capitalization.asp', ), Field( name='so', getter=SimpleField(caller=api.shares_outstanding), pullcast=F, pushcast=util.color.qty0, description='Shares Outstanding', documentation='https://www.investopedia.com/terms/o/outstandingshares.asp', ), Field( name='marketcap', getter=SimpleField(caller=api.marketcap), pullcast=F, pushcast=util.color.mulla, description='Market Capitalization', documentation='https://www.investopedia.com/terms/m/marketcapitalization.asp', ), Field( name='beta', getter=SimpleField(caller=api.beta), pullcast=F, pushcast=util.color.qty, description='Beta', documentation='https://www.investopedia.com/terms/b/beta.asp', ), Field( name='premium_collected', getter=None, pullcast=F, pushcast=util.color.mulla, description='Options Premium Collected', documentation='', ), Field( name='dividends_collected', getter=None, pullcast=F, pushcast=util.color.mulla, description='Stock Dividends Collected', documentation='', ), Field( name='d50ma', getter=SimpleField(caller=lambda ticker: api.ma(ticker, 'd50ma')), pullcast=F, pushcast=util.color.mulla, description='50-Day Moving Average', documentation='', ), Field( name='d200ma', getter=SimpleField(caller=lambda ticker: api.ma(ticker, 'd200ma')), pullcast=F, pushcast=util.color.mulla, description='200-Day Moving Average', documentation='', ), Field( name='y5c%', getter=SimpleField(caller=lambda ticker: api.cp(ticker, 'y5cp')), pullcast=F, pushcast=util.color.mpct, description='5-Year Percentage Change', documentation='', ), Field( name='y2c%', getter=SimpleField(caller=lambda ticker: api.cp(ticker, 'y2cp')), pullcast=F, pushcast=util.color.mpct, description='2-Year Percentage Change', documentation='', ), Field( name='y1c%', getter=SimpleField(caller=lambda ticker: api.cp(ticker, 'y1cp')), pullcast=F, pushcast=util.color.mpct, description='1-Year Percentage Change', documentation='', ), Field( name='m6c%', getter=SimpleField(caller=lambda ticker: api.cp(ticker, 'm6cp')), pullcast=F, pushcast=util.color.mpct, description='6-Month Percentage Change', documentation='', ), Field( name='m3c%', getter=SimpleField(caller=lambda ticker: api.cp(ticker, 'm3cp')), pullcast=F, pushcast=util.color.mpct, description='3-Month Percentage Change', documentation='', ), Field( name='m1c%', getter=SimpleField(caller=lambda ticker: api.cp(ticker, 'm1cp')), pullcast=F, pushcast=util.color.mpct, description='1-Month Percentage Change', documentation='', ), Field( name='d30c%', getter=SimpleField(caller=lambda ticker: api.cp(ticker, 'd30cp')), pullcast=F, pushcast=util.color.mpct, description='30-Day Percentage Change', documentation='', ), Field( name='d5c%', getter=SimpleField(caller=lambda ticker: api.cp(ticker, 'd5cp')), pullcast=F, pushcast=util.color.mpct, description='5-Day Percentage Change', documentation='', ), Field( name='d1c%', getter=SimpleField(caller=lambda ticker: api.quote(ticker)['changePercent']), pullcast=F, pushcast=util.color.mpct, description="Change percent from previous trading day's close", documentation='', ), Field( name='malps%', getter=ComplexField( attributes=[ 'd200ma', 'd50ma', 'price', ], chain=[ lambda R: list(R.values()), lambda R: -util.numbers.growth_score(R) / R[-1], ], ), pullcast=F, pushcast=util.color.mpct, description='Moving average loss per dollar of share', documentation='', ), Field( name='cbps', getter=ComplexField( attributes=['ticker'], chain=[ lambda R: T[T['symbol'] == R['ticker']], lambda df: df['cbps'].values[-1], ] ), pullcast=F, pushcast=util.color.mulla, description='Cost-Basis per Share (Excluding Option Premiums and Dividends', documentation='https://www.investopedia.com/terms/c/costbasis.asp', ), Field( name='cbps%', getter=ComplexField( attributes=['ticker'], chain=[ lambda R: ( T[T['symbol'] == R['ticker']]['cbps'].values[-1] ) / ( S[S['ticker'] == R['ticker']].price.item() ), ] ), pullcast=F, pushcast=util.color.mpct, description='Cost-Basis per Share (as a percentage of share price)', documentation='https://www.investopedia.com/terms/c/costbasis.asp', ), Field( name='dyps%', getter=ComplexField( attributes=['ticker'], chain=[ lambda R: R['ticker'], lambda ticker: dict( price=np.mean(( api.ma(ticker, ma='d200ma'), api.ma(ticker, ma='d50ma'), api.price(ticker), )), divs=list(zip(*( (F(div['amount']), F(div['position'])) for div in api.dividends(ticker) ))), ), lambda d: sum( d['divs'][0] # dividend amount ) / sum( d['divs'][1] # number of stocks held at time of dividend payout ) / ( d['price'] # recency-weighted average of share price ) if len(d['divs']) > 0 else 0, ] ), pullcast=F, pushcast=util.color.mpct, description='Dividend yield per dollar of share price', documentation='...', ), Field( name='pcps%', getter=ComplexField( attributes=['ticker'], chain=[ lambda R: R['ticker'], lambda ticker: dict( price=np.mean(( api.ma(ticker, ma='d200ma'), api.ma(ticker, ma='d50ma'), api.price(ticker), )), premcol=S[S['ticker'] == ticker]['premium_collected'], bought=T[T['symbol'] == ticker]['bought'].tail(1).item(), ), lambda d: ( d['premcol'] # premium collected in total ) / ( d['bought'] # number of shares ever purchased ) / ( d['price'] # recency-weighted average of share price ), ] ), pullcast=F, pushcast=util.color.mpct, description='Premium collected per dollar of share price ever purchased', documentation='...', ), Field( name='first_traded', getter=ComplexField( attributes=['ticker'], chain=[ lambda R: T[T['symbol'] == R['ticker']].date, lambda dates: min(dates) if len(dates) else -1, ] ), pullcast=util.datetime.datetime, pushcast=lambda d: util.color.qty0(util.datetime.age(d)), description='Days since first trade', documentation='', ), Field( name='last_traded', getter=ComplexField( attributes=['ticker'], chain=[ lambda R: T[T['symbol'] == R['ticker']].date, lambda dates: max(dates) if len(dates) else 999, ] ), pullcast=util.datetime.datetime, pushcast=lambda d: util.color.qty0(util.datetime.age(d)), description='Days since last trade', documentation='', ), Field( name='momentum', getter=ComplexField( attributes=[ 'y5c%', 'y2c%', 'y1c%', 'm6c%', 'm3c%', 'm1c%', 'd30c%', 'd5c%', 'roe', ], chain=[ lambda R: [ sp.stats.percentileofscore(S[period], value) for period, value in R.items() ], statistics.mean, lambda pct: pct / 100.0, ] ), pullcast=F, pushcast=util.color.mpct, description='Momentum Percentile (compared to the rest of this Portfolio)', documentation='', ), Field( name='analyst_score', getter=ComplexField( attributes=['ticker'], chain=[ lambda R: R['ticker'], api.ratings, ], ), pullcast=F, pushcast=util.color.qty, description='Analyst (Morning Star) Ratings, converted to a single number', documentation='', ), Field( name='you_score%', getter=ComplexField( attributes=['cbps%', 'dyps%', 'pcps%'], chain=[ lambda R: [ sp.stats.percentileofscore(S[period], value) for period, value in R.items() ], statistics.mean ] ), pullcast=F, pushcast=util.color.pct, description='You-Score for owning this stock (how well has the investor done)', documentation='', ), Field( name='stock_score%', getter=ComplexField( attributes=['malps%', 'momentum'], chain=[ lambda R: R.values(), sum, ] ), pullcast=F, pushcast=util.color.mpct, description='Stock-Score (how well has the stock done)', documentation='', ), Field( name='sharpe', getter=ComplexField( attributes=['ticker'], chain=[ lambda R: models.sharpe(ticker=R['ticker']), ] ), pullcast=F, pushcast=util.color.pct, description='Sharpe Ratio', documentation='https://www.investopedia.com/terms/s/sharperatio.asp', ), Field( name='treynor', getter=ComplexField( attributes=[ 'ticker', 'beta' ], chain=[ lambda R: models.treynor(R['ticker'], R['beta']), ] ), pullcast=F, pushcast=util.color.pct, description='Treynor Ratio', documentation='https://www.investopedia.com/terms/t/treynorratio.asp', ), Field( name='ebit2ev', getter=ComplexField( attributes=['ebit', 'ev'], chain=[ lambda R: R['ebit'] / R['ev'], ] ), pullcast=F, pushcast=util.color.mpct, description='Earnings yield; what the business earns in relation to its share price', documentation='', ), )
def _pull_processed_holdings_data(portfolio): now = util.datetime.now() data = [] with ShadyBar('%48s' % 'Refreshing Robinhood Portfolio Data', max=len(portfolio) + 3) as bar: # 1. Pull holdings holdings = api.holdings() bar.next() # 2. Pull Option Positions _data = _option_positions(api._price_agg()) collaterals = _data['collaterals'] next_expiries = _data['next_expiries'] urgencies = _data['urgencies'] opened = _data['opened'] del _data bar.next() # 3. Pull Options Orders _data = _option_orders() premiums = _data['premiums'] closed = _data['closed'] del _data bar.next() # 4. Add all rows to Python list first _timers = {} for ticker, stock in portfolio.items(): bar.next() if ticker not in holdings: continue holding = holdings[ticker] _opened = '\n'.join(opened.get(ticker, [])) _closed = '\n'.join(closed.get(ticker, [])) _collateral = F(collaterals[ticker]['call']) _optionable = (F(holding['quantity']) - _collateral) // 100 activities_data = [] if _opened: activities_data.append(_opened) if _closed: activities_data.append(_closed) if _optionable > 0: activities_data.append('%sx100 available' % (util.color.qty0(_optionable))) activities = ('\n%s\n' % ('─' * 32)).join(activities_data) collateral = collaterals[ticker] premium = premiums[ticker] dividend = api.dividends(ticker) next_expiry = next_expiries.get(ticker, pd.NaT) ttl = util.datetime.delta(now, next_expiry).days if next_expiry else -1 #fundamentals = api.fundamentals(ticker) quote = api.quote(ticker) row = dict( ticker=ticker, price=api.price(ticker), pcp=F(quote['previousClose']), beta=api.beta(ticker), quantity=holding['quantity'], average_buy_price=holding['average_buy_price'], equity=holding['equity'], percent_change=holding['percent_change'], equity_change=holding['equity_change'], type=holding['type'], name=holding['name'], percentage=holding['percentage'], cnt=len(stock.events), trd=stock.traded(), qty=stock._quantity, premium_collected=premium, dividends_collected=sum(F(div['amount']) for div in dividend), collateral_call=F(collateral['call']), collateral_put=F(collateral['put']), ttl=ttl, urgency=urgencies[ticker], activities=activities, next_expiry=next_expiry, ) data.append(row) return data
def _costbasis(row): if api.blacklisted(row.symbol, full=True): return pd.Series([NaN, NaN, NaN, NaN]) data = cbtally[row.symbol] signum = {'buy': -1, 'sell': +1}[row.side] datum = [ 'trade', F(signum) * F(row.quantity), F(row.average_price), row.date ] if len(data) > 0: _, _, _, d0 = data[-1] _, _, _, d2 = datum splits = api.splits(row.symbol) for split in splits: d1 = util.datetime.parse(split['exDate']) if not d0 < d1 < d2: continue for existing in data: existing[1] *= F(split['toFactor']) existing[1] /= F(split['fromFactor']) existing[2] *= F(split['fromFactor']) existing[2] /= F(split['toFactor']) data.append(datum) _, _, price, _ = datum bought = -sum(qty for cls, qty, pps, date in data if qty < 0) sold = +sum(qty for cls, qty, pps, date in data if qty > 0) held = bought - sold return pd.Series([ F(bought), F(sold), F(held), F((sum(qty * pps for cls, qty, pps, date in data) + held * price) / bought) if bought > 0 else 0 ])
def price(self): return F(api.price(self.ticker))
def beta(self): return F(api.beta(self.ticker))
def intrades(self): return { t['transactionDate']: F(t['transactionShares']) for t in api.insider_transactions(self.ticker) }
def marketcap(self): return F(api.marketcap(self.ticker))
from util.numbers import F def colorize(f, n, d, v): c = d[0] if not isnan(n): for i, v in enumerate(v): if v < n: c = d[i + 1] return colored(f(n), c) # Percentage spc = ['red', 'yellow', 'green', 'cyan', 'magenta'] pct = lambda p: colorize(lambda n: '%0.2f%%' % n, F(p), spc, [F(0), F(50), F(65), F(80)]) mpct = lambda p: pct(100 * p) spcr = ['blue', 'cyan', 'green', 'yellow', 'red'] pctr = lambda p: colorize(lambda n: '%0.2f%%' % n, F(p), spcr, [F(0), F(50), F(65), F(80)]) mpctr = lambda p: pctr(100 * p) # Quantity def _qty(m): m = F(m) if isnan(m): return NaN, None
def _sharpe_on_holdings(holdings): ''' holdings: api.rh.build_holdings() ''' df = pd.DataFrame(index=None) equities = {} with ShadyBar('%32s' % 'Pulling Historic Quote Prices', max=len(holdings)) as bar: for ticker, datum in holdings.items(): df[ticker] = slurp.stock_historic_prices(ticker)['Adj Close'] equities[ticker] = F(datum['equity']) bar.next() # Calculate the current weight of each stock in the portfolio equity = sum(equities.values()) weights = np.array( [equities[ticker] / equity for ticker in equities.keys()], dtype=np.float) # Calculate historic returns (percentage change close-to-close) returns = df.pct_change() # Number of Trading Days per Year (roughly) annual_trading_days = int(365 * 5 / 7 - 6 - 3 * 5 / 7) # Calculate the Annual Covariance # - The diagonal of this matrix is the variance (sqrt of the variance is volatility) # - Every other cell is the covariance (between the two non-identical tickes) annual_covariance = returns.cov() * annual_trading_days # Calculate the Portfolio Variance annual_variance = np.dot(weights.T, np.dot(annual_covariance, weights)) # Calculate the Portfolio Volatility annual_volatility = np.sqrt(annual_variance) # Calculate the Simple Annual Return simple_annual_return = np.sum(returns.mean() * weights * annual_trading_days) # Measure the Portfolio response = dict(status_quo=dict( expected_annual_return=simple_annual_return, annual_volatility=annual_volatility, annual_variance=annual_variance, ), efficient_frontier=dict()) # Improve the Portfolio # Calculate expected returns and annualized sample covariance matrix of asset returns mu = ppo.expected_returns.mean_historical_return(df) S = ppo.risk_models.sample_cov(df) # Optimize for max sharpe ratio (k) - measures the performance of the porfolio compared # to risk-free investments like bonds or treasuries ef = EfficientFrontier(mu, S) reweighted = ef.max_sharpe() cleanweighted = ef.clean_weights() response['efficient_frontier']['portfolio'] = { ticker: pct for ticker, pct in cleanweighted.items() if pct > 0 } expected_annual_return, annual_volatility, sharpe_ratio = ef.portfolio_performance( ) risk_free_ror = expected_annual_return - sharpe_ratio * annual_volatility response['efficient_frontier']['expectation'] = dict( expected_annual_return=expected_annual_return, annual_volatility=annual_volatility, sharpe_ratio=sharpe_ratio, risk_free_ror=risk_free_ror, ) response['status_quo']['risk_free_rate'] = risk_free_ror response['status_quo']['sharpe_ratio'] = ( simple_annual_return - risk_free_ror) / annual_volatility return response
def risk_free_rate_of_return(): ''' TODO: Make this dynamic ''' return F(0.02)
import numpy as np import pandas as pd from pypfopt.efficient_frontier import EfficientFrontier import pypfopt as ppo from progress.bar import ShadyBar import slurp import util from util.numbers import F ANNUAL_TRADING_DAYS = F(365 * 5 / 7 - 6 - 3 * 5 / 7) def risk_free_rate_of_return(): ''' TODO: Make this dynamic ''' return F(0.02) def treynor(ticker, beta): ''' Treynor Ratio for a single stock ''' risk_free_ror = risk_free_rate_of_return() adj_close_price = slurp.stock_historic_prices(ticker)['Adj Close'] daily_returns = adj_close_price.pct_change() returns_mean = F(np.mean(daily_returns)) variance = daily_returns.cov(daily_returns) # AKA ~np.var(daily_returns)
def _wtm(s_k_ratio, otype, itype): ''' Where-The-Money? otype: short / long itype: call / put S: Current Price K: Strike Price S_K_Ratio: S/K ''' keys = ['DITM', 'ITM', 'ATM', 'OTM', 'WOTM'] thresholds = [F(0.75), F(0.95), F(1 / 0.95), F(1 / 0.75)] index = 0 for i, limit in enumerate(thresholds): if s_k_ratio >= limit: index = i + 1 else: break style = { 'call': [ ('red', ('reverse', )), ('red', ()), ('yellow', ('reverse', 'blink')), ('cyan', ()), ('cyan', ('reverse', )), ], 'put': [ ('red', ('reverse', )), ('red', ()), ('yellow', ('reverse', 'blink')), ('green', ()), ('green', ('reverse', )), ] }[itype] themoney = { 'put': keys[index], 'call': keys[len(thresholds) - index], }[itype] style = { 'short': style, 'long': reversed(style), }[otype] # how likely it is that this contract will not expire worthless, and that it will # be assigned if seller, and hold intrinsic value if buyer impact = [F(0.40), F(0.7), F(1), F(1.3), F(1.6)] urgency = { 'put': F(1) / F(s_k_ratio) * impact[len(thresholds) - index], 'call': F(s_k_ratio) * impact[index], }[itype] urgency = { 'short': urgency, 'long': F(1) / urgency, }[otype] c, attrs = dict(zip(keys, style))[themoney] return dict( style_c=c, style_attrs=attrs, urgency=urgency, string=themoney, )
def mstart(name): assert 'activesince' not in MEASURED[name] MEASURED[name]['activesince'] = F(time())
def mulla(m): m = F(m) s, c = _qty(m) return colored(('+$%s' if m > 0 else '-$%s') % s, c)
def mstop(name): assert 'activesince' in MEASURED[name] MEASURED[name]['count'] += 1 MEASURED[name]['time'] = F(time()) - MEASURED[name]['activesince'] del MEASURED[name]['activesince']