Example #1
0
def visualize_dvd_df(df_dvd):
    # Div/ATR histogram
    fig = px.histogram(df_dvd,
                       x='div/ATR',
                       color='ticker',
                       title='div/ATR Histogram')
    show_plotly(fig)

    # Days vs Div/ATR scatter
    if 'Days-to-ex' in df_dvd.columns:
        fig = px.scatter(df_dvd,
                         x='Days-to-ex',
                         y='div/ATR',
                         color='ticker',
                         title=f'Days-to-ex vs div/ATR')  #, hover_data = [''])
        show_plotly(fig)

    if 'TR_ex' in df_dvd.columns:
        max_Div_delta = int(max([df_dvd['TR_ex'].max(), df_dvd['Div'].max()]))
        fig = px.scatter(
            df_dvd,
            x='TR_ex',
            y='Div',
            color='ticker',
            title=f'Div vs True Range (on ex-date)')  #, hover_data = [''])
        fig.add_trace(go.Scatter(x=[0, max_Div_delta],
                                 y=[0, max_Div_delta],
                                 name='break-even',
                                 marker_symbol='x-thin',
                                 marker_size=1,
                                 line={
                                     'dash': 'solid',
                                     'color': 'GhostWhite',
                                     'width': 0.5
                                 }),
                      row=1,
                      col=1)
        show_plotly(fig)

    if 'capture_r_multiplier' in df_dvd.columns:
        df_p = df_dvd[df_dvd['capture_risk'] > 0]
        fig = px.histogram(df_p,
                           x='capture_r_multiplier',
                           color='ticker',
                           title='Dividend Capture R-Multiplier Distribution')
        show_plotly(fig)
Example #2
0
def compare_returns(df_returns, df_prices, chart_size=500):
    '''
    plot multi stocks returns in ST using PX
    '''
    l_tickers = df_returns.columns.tolist()
    l_col, r_col = st.columns(2)

    # returns
    fig = px.line(df_returns,
                  y=df_returns.columns.tolist(),
                  title=f'stock returns')
    show_plotly(fig, height=chart_size, st_asset=l_col)

    # return dist
    fig = px.histogram(df_returns,
                       y=l_tickers,
                       opacity=0.8,
                       title=f'returns distribution')
    show_plotly(fig, height=chart_size, st_asset=r_col)

    # cumulative returns
    fig = px.line(
        (df_returns + 1).cumprod(),
        y=df_returns.columns.tolist(),
        title=f'growtht of $1 invested on {df_returns.index[0]}',
        labels={'value': f'cumulative returns'},
        # color_discrete_sequence = ["#b58900"]
    )
    show_plotly(fig, height=chart_size, st_asset=l_col)

    # compare volume
    for t in l_tickers:
        df_prices['Dollar_Traded',
                  t] = df_prices['Volume', t] * df_prices['Adj Close', t]

    fig = px.line(
        df_prices['Dollar_Traded'],
        y=l_tickers,
        title=f'Dollar Traded',
        labels={'value': f'Dollar Traded'},
        # color_discrete_sequence = ["#b58900"]
    )
    show_plotly(fig, height=chart_size, st_asset=r_col)
Example #3
0
def plot_returns(df_returns,
                 df_prices,
                 target_ticker,
                 chart_size=500,
                 show_ohlc=True):
    '''
    plot target_ticker returns in ST using PX
    '''
    # TODO: remove show_ohlc arg
    l_col, r_col = st.columns(2)

    # returns
    fig = px.line(
        df_returns,
        y=target_ticker,
        title=f'{target_ticker} returns',
        color_discrete_sequence=["#b58900"],
    )
    show_plotly(fig, height=chart_size, st_asset=l_col)

    # simple price chart
    # if show_ohlc:
    df_plot = pd.DataFrame({
        'Open': df_prices['Open'][target_ticker],
        'High': df_prices['High'][target_ticker],
        'Low': df_prices['Low'][target_ticker],
        'Close': df_prices['Close'][target_ticker],
        'Volume': df_prices['Volume'][target_ticker],
        'Date': df_prices.index
    })
    #     fig = plotly_ohlc_chart(df_plot, vol_col = 'Volume', date_col = 'Date',
    #         show_legend = False, show_volume_profile = False)
    fig = plotly_ohlc_chart(df_plot,
                            date_col='Date',
                            show_legend=False,
                            show_volume_profile=False,
                            show_range_slider=False)
    # else:
    #     fig = px.line(df_prices['Adj Close'], y = target_ticker,
    #             color_discrete_sequence = ["#b58900"])
    show_plotly(fig,
                height=chart_size,
                title=f'price of {target_ticker}',
                st_asset=l_col)

    # return dist
    fig = px.histogram(
        df_returns,
        y=target_ticker,
        color_discrete_sequence=["#b58900"],
        title=f'{target_ticker} returns distribution (with 2stdv lines)')
    two_stdv = df_returns[target_ticker].mean(
    ) + df_returns[target_ticker].std() * 2
    plotly_hist_draw_hline(fig,
                           l_value_format=[{
                               'value': v
                           } for v in [two_stdv, -two_stdv]])
    show_plotly(fig, height=chart_size, st_asset=r_col)

    # if show_ohlc:
    #     #cumulative returns
    #     fig = px.line((df_returns+1).cumprod(), y = target_ticker,
    #             title = f'growtht of $1 invested in {target_ticker} on {df_returns.index[0]}',
    #             labels = {target_ticker: f'{target_ticker} cumulative returns'},
    #             color_discrete_sequence = ["#b58900"]
    #             )
    #     show_plotly(fig, height = chart_size)
    # else:
    # volume at price
    # https://medium.com/swlh/how-to-analyze-volume-profiles-with-python-3166bb10ff24
    idx = pd.IndexSlice
    df_vp = df_prices.loc[:, idx[['Adj Close', 'Volume'], target_ticker]]
    df_vp.columns = ['Adj Close', 'Volume']

    fig = px.histogram(df_vp,
                       y='Adj Close',
                       x='Volume',
                       title=f'Volume-at-price: {target_ticker}',
                       orientation='h',
                       nbins=100,
                       color_discrete_sequence=["#b58900"])
    show_plotly(fig, height=chart_size, st_asset=r_col)
Example #4
0
def visualize_features(df_dict,
                       chart_configs,
                       atr_period,
                       start_date,
                       use_ema=True):
    '''
    Args:
        df_dict: dictionary of {ticker: df, ...}
    '''
    l_tickers = list(df_dict.keys())
    is_single = len(l_tickers) == 1

    # Add KER
    df_dict = {
        t: add_KER(df, atr_period)[df.index > pd.Timestamp(start_date)]
        for t, df in df_dict.items()
    }
    norm_atr = st.checkbox('normalize ATR', value=True)

    if is_single:  # Special Handling for Single Ticker
        tickers = None
        df_dict[l_tickers[0]]['ATR'] = df_dict[l_tickers[0]]['ATR']/ df_dict[l_tickers[0]]['Close'] \
                        if norm_atr else df_dict[l_tickers[0]]['ATR']
        atr_periods = st.text_input(
            'more ATR periods to test (comma separated)')
        atr_periods = [int(i) for i in atr_periods.split(',')
                       ] if atr_periods else None
        if atr_periods:
            for p in atr_periods:
                df = df_dict[l_tickers[0]]
                df_dict[l_tickers[0]] = add_ATR(df,
                                                period=p,
                                                normalize=norm_atr,
                                                use_ema=use_ema,
                                                channel_dict=None,
                                                col_name=f'ATR_{p}')
        # MA of ATR
        # str_ma_period = st.text_input('moving averages period (comma-separated for multiple periods)')
        # if str_ma_period:
        #     t = df_p.columns[0]
        #     for p in str_ma_period.split(','):
        #         df_p[f'{p}bars_MA'] = df_p[t].rolling(int(p)).mean().shift()

        # ATR Time-Series
        df_p = df_dict[l_tickers[0]]
        df_p = df_p[[c for c in df_p.columns if 'ATR' in c]]
        fig = px.line(
            df_p,
            y=df_p.columns,
            labels={
                'x': 'Date',
                'y': 'ATR'
            },
            title=f'Historical Average True Range ({atr_period} bars)')
        show_plotly(fig)
    else:
        # View ATR time series of all given stocks
        atr_dict = {
            ticker : (df['ATR']/df['Close']).dropna().to_dict() \
                    if norm_atr else df['ATR'].dropna().to_dict()
            for ticker, df in df_dict.items()
            }
        df_p = pd.DataFrame.from_dict(atr_dict)

        # tickers Selection
        tickers = st.multiselect(f'ticker',
                                 options=[''] + list(df_dict.keys()))

        # ATR Time-Series
        fig = px.line(
            df_p,
            y=tickers if tickers else df_p.columns,
            labels={
                'x': 'Date',
                'y': 'ATR'
            },
            title=f'Historical Average True Range ({atr_period} bars)')
        show_plotly(
            fig
        )  #, height = chart_size, title = f"Price chart({interval}) for {l_tickers[0]}")

    # ATR Histogram
    fig = px.histogram(
        df_p,
        x=tickers if tickers else df_p.columns,
        barmode=chart_configs['barmode'],
        title=f'Average True Range ({atr_period} bars) Distribution',
        nbins=chart_configs['n_bins'])
    show_plotly(fig)

    # KER Time-Series
    KER_dict = {
        ticker: df['KER'].dropna().to_dict()
        for ticker, df in df_dict.items()
    }
    df_p = pd.DataFrame.from_dict(KER_dict)

    fig = px.line(df_p,
                  y=tickers if tickers else df_p.columns,
                  labels={
                      'x': 'Date',
                      'y': 'KER'
                  },
                  title=f'Historical efficiency ratio ({atr_period} bars)')
    show_plotly(fig)
    # KER histogram
    fig = px.histogram(
        df_p,
        x=tickers if tickers else df_p.columns,
        barmode=chart_configs['barmode'],
        title=f'Efficiency Ratio ({atr_period} bars) Distribution',
        nbins=chart_configs['n_bins'])
    show_plotly(fig)

    # Volume
    if is_single:
        str_ma_period = st.text_input(
            'Volume moving averages period (comma-separated for multiple periods)'
        )
        df_p = df_dict[l_tickers[0]]
        if str_ma_period:
            t = df_p.columns[0]
            for p in str_ma_period.split(','):
                df_p[f'Volume_{p}bars_MA'] = df_p["Volume"].rolling(
                    int(p)).mean().shift()
        df_p = df_p[[c for c in df_p.columns if 'Volume' in c]]
    else:
        volume_dict = {
            ticker: df['Volume'].dropna().to_dict()
            for ticker, df in df_dict.items()
        }
        df_p = pd.DataFrame.from_dict(volume_dict)

    volume_scatter = px.line(df_p,
                             y=tickers if tickers else df_p.columns,
                             labels={
                                 'x': 'Date',
                                 'y': 'Volume'
                             },
                             title=f'volume scatter plot')
    volume_hist = px.histogram(df_p,
                               x=tickers if tickers else df_p.columns,
                               barmode=chart_configs['barmode'],
                               title=f'Volume Distribution',
                               nbins=chart_configs['n_bins'])
    show_plotly(volume_scatter)
    show_plotly(volume_hist)
Example #5
0
def Main():
    with st.sidebar.expander("GP"):
        st.info(f'''
            Graph Prices (open-high-low-close)

            * inspired by this [blog post](https://towardsdatascience.com/creating-a-finance-web-app-in-3-minutes-8273d56a39f8)
                and this [youtube video](https://youtu.be/OhvQN_yIgCo)
            * plots by Plotly with thanks to this [kaggle notebook](https://www.kaggle.com/mtszkw/technical-indicators-for-trading-stocks)
        ''')

    tickers = tickers_parser(st.text_input('enter stock ticker'), max_items = 1)
    with st.sidebar.expander('timeframe', expanded = True):
        today = datetime.date.today()
        end_date = st.date_input('Period End Date', value = today)
        if st.checkbox('pick start date'):
            start_date = st.date_input('Period Start Date', value = today - datetime.timedelta(days = 365))
        else:
            tenor = st.text_input('Period', value = '6m')
            start_date = (BusinessDate(end_date) - tenor).to_date()
            st.info(f'period start date: {start_date}')

        # TODO: allow manual handling of data_start_date
        # l_interval = ['1d','1wk','1m', '2m','5m','15m','30m','60m','90m','1h','5d','1mo','3mo']
        interval = st.selectbox('interval', options = ['1d', '1wk', '1mo'])
        is_intraday = interval.endswith(('m','h'))
        data_start_date = start_date if is_intraday else \
                        (BusinessDate(start_date) - "1y").to_date()
        if is_intraday:
            st.warning(f'''
                intraday data cannot extend last 60 days\n
                also, some features below might not work properly
                ''')

    if tickers:
        stock_obj = yf.Ticker(tickers)
        if not valid_stock(stock_obj):
            st.error(f'''
            {tickers} is an invalid ticker.\n
            Having trouble finding the right ticker?\n
            Check it out first in `DESC` :point_left:
            ''')
            return None

        side_config = st.sidebar.expander('charts configure', expanded = False)
        with side_config:
            show_df = st.checkbox('show price dataframe', value = False)
            chart_size = st.number_input('Chart Size', value = 1200, min_value = 400, max_value = 1500, step = 50)

        side_stock_info = get_stock_info_container(stock_obj.info, st_asset= st.sidebar)

        data = get_stocks_ohlc(tickers,
                start_date = data_start_date, end_date = end_date,
                interval = interval,
                proxies = get_proxies())

        with st.expander('Indicators'):
            l_col, m_col , r_col = st.columns(3)
            with l_col:
                st.write('#### the moving averages')
                ma_type = st.selectbox('moving average type', options = ['', 'ema', 'sma', 'vwap'])
                periods = st.text_input('moving average periods (comma separated)', value = '22,11')
                if ma_type:
                    for p in periods.split(','):
                        data = add_moving_average(data, period = int(p), type = ma_type)
                st.write('#### volume-based indicators')
                # do_volume_profile = st.checkbox('Volume Profile')
                data = add_AD(data) if st.checkbox('Show Advance/ Decline') else data
                data = add_OBV(data)  if st.checkbox('Show On Balance Volume') else data
            with m_col:
                st.write('#### MACD')
                do_MACD = st.checkbox('Show MACD?', value = True)
                fast = st.number_input('fast', value = 12)
                slow = st.number_input('slow', value = 26)
                signal = st.number_input('signal', value = 9)
                if do_MACD:
                    data = add_MACD(data, fast = fast, slow = slow, signal = signal )
            with r_col:
                st.write('#### oscillator')
                do_RSI = st.checkbox('RSI')
                data = add_RSI(data, n = st.number_input('RSI period', value = 13)) if do_RSI else data
                tup_RSI_hilo = st.text_input('RSI chart high and low line (comma separated):', value = '70,30').split(',') \
                                if do_RSI else None
                tup_RSI_hilo = [int(i) for i in tup_RSI_hilo] if tup_RSI_hilo else None
                if do_RSI:
                    data_over_hilo_pct = sum(
                        ((data['RSI']> tup_RSI_hilo[0]) | (data['RSI']< tup_RSI_hilo[1])) & (data.index > pd.Timestamp(start_date))
                        ) / len(data[data.index > pd.Timestamp(start_date)])
                    st.info(f"""
                    {round(data_over_hilo_pct * 100, 2)}% within hilo\n
                    5% of peaks and valley should be within hilo
                    """)

                st.write('#### True Range Related')
                atr_period = int(st.number_input('Average True Range Period', value = 13))
                atr_ema = st.checkbox('use EMA for ATR', value = True)
                show_ATR = st.checkbox('show ATR?', value = False)
                if ma_type:
                    st.write('##### ATR Channels')
                    atr_ma_name = st.selectbox('select moving average for ATR channel',
                                    options = [''] + get_moving_average_col(data.columns))
                    atr_channels = st.text_input('Channel Lines (comma separated)', value = "1,2,3") \
                                    if atr_ma_name else None
                    fill_channels = st.checkbox('Fill Channels with color', value = False) \
                                    if atr_ma_name else None
                else:
                    atr_ma_name = None

                data = add_ATR(data, period = atr_period, use_ema = atr_ema,
                            channel_dict = {atr_ma_name: [float(c) for c in atr_channels.split(',')]} \
                                if atr_ma_name else None
                            )

                st.write(f'##### Directional System')
                do_ADX = st.checkbox('Show ADX')
                data = add_ADX(data, period = st.number_input("ADX period", value = 13)) \
                        if do_ADX else data

        with st.expander('advanced settings'):
            l_col, m_col , r_col = st.columns(3)
            with l_col:
                st.write('#### Market Type Classification')
                mkt_class_period = int(st.number_input('peroid (match your trading time domain)', value = 66))
                mkt_class = market_classification(data, period = mkt_class_period,
                                debug = False) if mkt_class_period else None
                if mkt_class:
                    side_stock_info.write(f'market is `{mkt_class}` for the last **{mkt_class_period} bars**')
                    side_stock_info.write(f'[kaufman efficiency_ratio](https://strategyquant.com/codebase/kaufmans-efficiency-ratio-ker/) ({mkt_class_period} bars): `{round(efficiency_ratio(data, period = mkt_class_period),2)}`')
                st.write('#### Events')
                do_div = st.checkbox('show ex-dividend dates')
                if do_div:
                    data = add_div_col(df_price = data, df_div = stock_obj.dividends)
                    side_stock_info.write(
                        stock_obj.dividends[stock_obj.dividends.index > pd.Timestamp(start_date)]
                        )
                do_earnings = st.checkbox('show earning dates')
                if do_earnings and isinstance(stock_obj.calendar, pd.DataFrame):
                    data = add_event_col(df_price = data,
                                df_events = stock_obj.calendar.T.set_index('Earnings Date'),
                                event_col_name= "earnings")
                    side_stock_info.write(stock_obj.calendar.T)

                if do_MACD and ma_type:
                    st.write("#### Elder's Impulse System")
                    impulse_ema = st.selectbox('select moving average for impulse',
                                    options = [''] + get_moving_average_col(data.columns))
                    data = add_Impulse(data, ema_name = impulse_ema) if impulse_ema else data

            avg_pen_data = None
            with m_col:
                if ma_type:
                    st.write("#### Average Penetration for Entry/ SafeZone")
                    fair_col = st.selectbox('compute average penetration below',
                                    options = [''] + get_moving_average_col(data.columns))
                    avg_pen_data = add_avg_penetration(df = data, fair_col = fair_col,
                                        num_of_bars = st.number_input('period (e.g. 4-6 weeks)', value = 30), # 4-6 weeks
                                        use_ema = st.checkbox('use EMA for penetration', value = False),
                                        ignore_zero = st.checkbox('ignore days without penetration', value = True),
                                        coef = st.number_input(
                                            'SafeZone Coefficient (stops should be set at least 1x Average Penetration)',
                                            value = 1.0, step = 0.1),
                                        get_df = True, debug = True
                                    ) if fair_col else None
            with r_col:
                if do_MACD:
                    st.write('#### MACD Bullish Divergence')
                    if st.checkbox('Show Divergence'):
                        data = detect_macd_divergence(data,
                                period = st.number_input('within number of bars (should be around 3 months)', value = 66),
                                threshold = st.number_input('current low threshold (% of previous major low)', value = 0.95),
                                debug = True
                                )
                st.write(f'#### Detect Kangaroo Tails')
                tail_type = st.selectbox('Tail Type',
                                options = ['', 0, 1, -1])
                data = detect_kangaroo_tails(data,
                        atr_threshold = st.number_input('ATR Threshold', value = 2.0),
                        period = st.number_input('period', value = 22), tail_type = tail_type) \
                        if tail_type else data

        beta_events_to_plot, l_events_to_color, l_col_to_scatter = [], [], []
        show_beta_features(data = data, l_events_to_color=l_events_to_color,
            l_col_to_scatter = l_col_to_scatter, atr_period = atr_period)

        if show_df:
            with st.expander(f'raw data (last updated: {data.index[-1].strftime("%c")})'):
                st.write(data)

        if isinstance(avg_pen_data, pd.DataFrame):
            with st.expander('Buy Entry (SafeZone)'):
                avg_pen_dict = {
                    'average penetration': avg_pen_data['avg_lp'][-1],
                    'ATR': avg_pen_data['ATR'][-1],
                    'penetration stdv': avg_pen_data['std_lp'][-1],
                    'number of penetrations within period': avg_pen_data['count_lp'][-1],
                    'last': avg_pen_data['Close'][-1],
                    'expected ema T+1': avg_pen_data[fair_col][-1] + (avg_pen_data[fair_col][-1] - avg_pen_data[fair_col][-2])
                    }
                avg_pen_dict = {k:round(v,2) for k,v in avg_pen_dict.items()}
                avg_pen_dict['buy target T+1'] = avg_pen_dict['expected ema T+1'] - avg_pen_dict['average penetration']
                st.write(avg_pen_dict)
                plot_avg_pen = st.checkbox('plot buy SafeZone and show average penetration df')
                plot_target_buy = False # st.checkbox('plot target buy T+1')
                # if plot_avg_pen:
                #     st.write(avg_pen_data)

        if not(show_ATR) and 'ATR' in data.columns:
            del data['ATR']

        #TODO: fix tz issue for interval < 1d
        # see: https://stackoverflow.com/questions/16628819/convert-pandas-timezone-aware-datetimeindex-to-naive-timestamp-but-in-certain-t
        fig = plotly_ohlc_chart(
                df = data if is_intraday else data[data.index > pd.Timestamp(start_date)],
                vol_col = 'Volume',
                tup_rsi_hilo = tup_RSI_hilo,
                b_fill_channel = fill_channels if atr_ma_name else None
                ) #, show_volume_profile = do_volume_profile)
        # SafeZone
        if isinstance(avg_pen_data, pd.DataFrame):
            fig = add_Scatter(fig, df = avg_pen_data[avg_pen_data.index > pd.Timestamp(start_date)], target_col = 'buy_safezone') \
                if plot_avg_pen else fig
            if plot_target_buy:
                fig.add_hline(y = avg_pen_dict['buy target T+1'] , line_dash = 'dot', row =1, col = 1)
        # Events
        for d in ['MACD_Divergence', 'kangaroo_tails', 'ex-dividend', 'earnings'] + beta_events_to_plot:
            if d in data.columns:
                fig = add_Scatter_Event(fig, data[data.index > pd.Timestamp(start_date)],
                        target_col = d,
                        anchor_col = 'Low', textposition = 'bottom center', fontsize = 8,
                        marker_symbol = 'triangle-up', event_label = d[0])
        # Color Events
        for d in l_events_to_color:
            fig = add_color_event_ohlc(fig, data[data.index > pd.Timestamp(start_date)],
                        condition_col = d['column'], color = d['color']
                        ) if d['column'] in data.columns else fig
        # Scatter Columns
        for c in l_col_to_scatter:
            fig = add_Scatter(fig, data[data.index > pd.Timestamp(start_date)],
                    target_col = c['column'], line_color = c['color'])

        show_plotly(fig, height = chart_size,
            title = f"Price chart({interval}) for {tickers} : {stock_obj.info['longName']}")
Example #6
0
def Main():
    with st.sidebar.expander("BETA"):
        st.info(f'''
            Beta Analysis vs Benchmark Security
        ''')

    default_tickers = get_index_tickers(
                        st_asset = st.sidebar.expander('Load an Index', expanded = True)
                        )
    tickers = tickers_parser(
                st.text_input('enter stock ticker(s) [space separated]',
                    value = default_tickers)
                )
    with st.sidebar.expander('settings', expanded = False):
        today = datetime.date.today()
        end_date = st.date_input('Period End Date', value = today)
        if st.checkbox('pick start date'):
            start_date = st.date_input('Period Start Date', value = today - datetime.timedelta(days = 365))
        else:
            tenor = st.text_input('Period', value = '250b')
            start_date = (BusinessDate(end_date) - tenor).to_date()
            st.info(f'period start date: {start_date}')

        l_interval = ['1d','1m', '2m','5m','15m','30m','60m','90m','1h','5d','1wk','1mo','3mo']
        interval = st.selectbox('interval', options = l_interval)

    if tickers:
        side_config = st.sidebar.expander('charts configure', expanded = False)
        with side_config:
            show_ohlc = st.checkbox('ohlc chart', value = True)
            # b_two_col = st.checkbox('two-column view', value = True)
            chart_size = st.number_input('Chart Size', value = 500, min_value = 400, max_value = 1500)


        data_dict = get_yf_data(tickers, start_date = start_date, end_date = end_date, interval = interval)
        df_returns = data_dict['returns'].copy()

        with st.expander('view returns data'):
            st.subheader('Returns')
            st.write(df_returns)

        l_tickers = df_returns.columns.tolist()
        if len(l_tickers) != len(tickers.split(' ')):
            st.warning(f'having trouble finding the right ticker?\nCheck it out first in `DESC` :point_left:')

        l_col, r_col = st.columns(2)
        with l_col:
            benchmark_ticker = st.selectbox('benchmark security', options = tickers.split())
            beta_json = get_betas(df_returns, benchmark_col = benchmark_ticker.upper())
            # TODO: add dividend yield?
            beta_df = pd.DataFrame.from_dict(beta_json)
        with r_col:
            plot_var_options = [col for col in beta_df.columns if col not in ['ticker']]
            y_var = st.selectbox('y-axis variable', options = plot_var_options)
            x_var = st.selectbox('x-axis variable', options = plot_var_options)

        with st.expander('Betas Calcuation'):
            st.write(beta_df)

        fig = px.scatter(beta_df, x = x_var, y = y_var, color = 'ticker')
        fig.update_layout(showlegend = False)
        show_plotly(fig)