Reputation: 608
I am currently working on a project to enable interactive stock-valuation. This requires the user to select a stock by entering a stock ticker, and then that user should be able to tweak the stock drivers and observe the impact of these changes on the company's valuation.
In order to achieve this, one must first populate a table with stock information, and then update calculations based on user-edited variables. Ideally one would be able to update calculations within the table itself, as per the example 'Updating Columns of the Same Table' here. However, in my case, I am struggling to combine the initialization of the table with the output from stock selection, with the updating of the table's calculations upon user modification of the table's contents.
I have put together a very simple, abridged, example below. In this case, I thought it might be simpler to separate the stock data and the calculation output into two separate tables. This is something that I will want to do as well, and represents a broader use-case than having everything in one table.
I have annotated my code a little so that my logic is hopefully sufficiently clear. But a basic overview of the code is as follows:
(i) Request user input of ticker (stock1 or stock2 in this example).
(ii) Display ticker, or error message if ticker is incorrect.
(iii) Store stock data in intermediate dataframe (as per Example 1 here.)
(iv) Populate FCFf table with selected stock data for currently selected stock.
(v) Calculate present value using data from FCFf data
It is in (v) that my problem seems to be occurring: my approach involves loading the stock data from (iii into stock_df), loading the fcff data from (iv) into fcff_df, and over-writing the fcff fields in stock_df with those in fcff_df. Unfortunately, upon manually changing any of the FCFf values (e.g. that in column fcff_fy3), the valuation table does not update: instead, I get an error
TypeError: ufunc 'true_divide' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
It struck me that perhaps user input is not interpreted as having the same type as the cell, and is instead taken to be a string?
But, in any event, amy approach seems a bit unconventional, and I wondered whether there is a better solution?
Note that I would also like to be able to calculate when changes are made to a single table. For example, had I combined the valuation table and FCFf tables into one, and also added the discount rate, 'r', used in do_pv, as an editable field, how would I do this? Thank you for any help.
Example data and code:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table
import pandas as pd
from dash_table.Format import Format, Scheme, Sign, Symbol
df = pd.DataFrame(data = { 'ticker' : ['stock1', 'stock2'], 'r' : [0.1, 0.2], 'fcff_fy1' : [7902, 9409] , 'fcff_fy2' : [13912, 68969], 'fcff_fy3' : [11309, 7154], 'fcff_fy4' : [13912, 68969], 'fcff_fy5' : [76158, 84090] })
fcff_cols = ['fcff_fy1', 'fcff_fy2', 'fcff_fy3', 'fcff_fy4', 'fcff_fy5']
valuation_cols = ['Item', 'Valuation']
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.config.suppress_callback_exceptions = True
app.css.append_css({
"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"
})
app.layout = html.Div([
html.H1('Stock Valuation'),
html.H5('Choose stock from stock1, stock2'),
dcc.Input(id='stock-id', value='stock1', type='text'), #human text input of stock ticker
html.Div(id='my-tick'), # Output to identify and inform user whether ticker selection is vald
html.Hr(),
html.Div(id='stock-data', style={'display': 'none'}), #intermediate storage of stock data dataframe.
html.H5('FCFf drivers'),
dash_table.DataTable(id='fcff-table', # Free cashflow table for selected stock
columns = [{"name": i, "id": i, 'format': Format(precision=2)} for i in (['Item'] + fcff_cols)],
data = ['FCFf'] + [0 for x in fcff_cols], editable = True),
html.Hr(),
html.H5('PV'),
html.Div(dash_table.DataTable(id='valn-table', columns=[{"name": i, "id": i} for i in valuation_cols], data=[0 for x in ['Valuation']], editable = True)), # Valuation table, fed from FCff table
])
# Fill FCFf table with selected stock data
@ app.callback(
[Output('fcff-table', 'columns'),
Output('fcff-table', 'data')],
[Input('stock-data', 'children')])
def display_output(df):
stock_df = pd.read_json(df)
fcff_table = pd.DataFrame(columns = fcff_cols, data = stock_df[fcff_cols].values.round(2), index=['FCFf'])
fcff_table.reset_index(inplace=True)
fcff_table.rename(columns={'index': 'Item'}, inplace=True)
fcff_col_param = []
for col in ['Item'] + fcff_cols:
fcff_col_param.append({"name": str(col), "id": str(col)})
return [fcff_col_param, fcff_table.to_dict('records')]
# Read data in from editable FaCFf table, and use to calculate and populate valn-table
@app.callback(
[Output('valn-table', 'columns'),
Output('valn-table', 'data')],
[Input('fcff-table', 'data'),
Input('stock-data', 'children')])
def display_valn(f_data, df):
stock_df = pd.read_json(df) # Read current stock data from intermediate div in html.
fcff_df = pd.DataFrame(f_data) # Convert FCFf table data to dataframe
fcff_df = fcff_df[fcff_df.Item == 'FCFf'].drop('Item', axis=1)
fcff_df.columns = fcff_cols
stock_df[fcff_cols] = fcff_df[fcff_cols].values # overwrite fcff data with fcff data from the table, allowing users to change fcff assumptions.
stock_df = do_pv(stock_df) # do PV of cashflows using stock's discount rate, r
# construct valuation table output for display in valn-table
data = [['Present Value of forecast FCFf (m)', stock_df['pv'].values[0].round(2)], ['Discount rate (r)', stock_df['r'].values[0].round(3)]],
table = pd.DataFrame(data, columns = ['Item', 'Valuation'])
dt_col_param = [{"name": 'Item', "id": 'Item'}, {"name": 'Valuation', "id": 'Valuation'}]
return [dt_col_param, table.to_dict('records')]
# Select stock for consideration via manual entry of ticker.
@app.callback(
Output('my-tick', 'children'),
[Input('stock-id', 'value')]
)
def update_output_ticker(input_value):
if not input_value in df.ticker.unique():
return 'Incorrect ticker'
else:
return df[df.ticker == input_value].ticker
# Store selected stock data dataframe in intermediate step.
@app.callback(Output('stock-data', 'children'),
[Input('stock-id', 'value')])
def do_stock_df(selected_ticker):
stock_df = df[df.ticker == selected_ticker]
return stock_df.to_json()
def do_pv(df):
df['pv'] = 0
# pv of 5-year forecasts
for i in range(1, 5):
df.pv += df['fcff_fy' + str(i)].values[0] / ((1 + df.r.values[0]) ** i)
return df
if __name__ == '__main__':
app.run_server(debug=True)
Upvotes: 1
Views: 1941
Reputation: 1592
Try changing the do_pv
module to the below, It looks like after the table is edited the value is fetched as a string which causes this error.
def do_pv(df):
df['pv'] = 0
# pv of 5-year forecasts
for i in range(1, 5):
#its string for the edited columns
print(type(df['fcff_fy' + str(i)].values[0]))
df['pv'] += float(df['fcff_fy' + str(i)].values[0]) / float((1 + df.r.values[0]) ** i)
return df
Upvotes: 1