Processing of dynamic inputs (from children to values)

Hi Dash community,

I have been working on some for a week and I have encountered a blocker.
My problem is the following: given two values (first level RadioItems and second level Dropdown), create fields of Inputs (therefore the set of inputs asked varies depending on the combination of RadioItems and Dropdown values selected).
I have managed to create the inputs fields however I cannot process the inputs once they have been provided (depending on the combination a graph is meant to be plotted).

Has anyone faced the same issue?

Thank you

#############################################################################

app layout

app.layout = html.Div([html.H1('Simulation of stock paths', style={'text-align': 'center', 'font-family':'verdana'}),\
                       html.H5('Generate stock paths according to different stochastic processes',\
                               style={'text-align': 'center','font-family':'verdana'}),\
                       dcc.Markdown('Select a process: ', style={'text-align': 'left', 'font-family': 'verdana'}),\
                       html.Div(\
                           dcc.RadioItems(id='process',options=[{'label': x, 'value': x} for x in\
                                                            ['black-scholes', 'heston']],\
                                      value='black-scholes',style={'text-align': 'left', 'font-family': 'verdana',\
                                                                   'display': 'inline-block'},\
                                      inputStyle={'margin': '10px'}),style={'display': 'inline-block'}),\
                       html.Br(),\
                       dcc.Dropdown(multi=False, style={'text-align': 'left', 'font-family': 'verdana'},\
                                    id='model_specification'), \
                       html.Br(),\
                       html.Div(id='model_parameters'),\
                       html.Br(), \
                       html.Div(id='my-output'),\
                       dcc.Graph(id='stock_path')])

code for my second selection level

@app.callback(
    [Output('model_specification', 'options'),\
     Output('model_specification', 'value')],\
        Input('process', 'value'))
def dropdown_options(process):
    if process == 'black-scholes':
        options_dd = {model_specification: model_specification for model_specification in ['no-jump','merton']}
    elif process == 'heston':
        options_dd = {model_specification: model_specification for model_specification in ['no-jump','bates']}
    value = options_dd[list(options_dd.keys())[0]]
    return options_dd, value

Inputs depending on the selection above

@app.callback(
    Output('model_parameters','children'),\
    [Input('process','value'),\
     Input('model_specification','value')]
)
def parameters_input(process,model_specification):
    params_ls = [dcc.Input(id='S0', placeholder='Insert S0', min=0.00, step=1, size='md',\
                           type='number',value=100,debounce=True),\
                 dcc.Input(id='r', min=0.00, step=.01, size='md', placeholder='Insert r',\
                           type='number',value=.05,debounce=True)]
    if process == 'black-scholes':
        process_param_ls = [dcc.Input(id='vol',min=0.00,step=.01,size='md',placeholder='Insert vol',type='number',\
                                       debounce=True,value=.2)]
        if model_specification == 'no-jump':
            add_param_ls = []
        elif model_specification == 'merton':
            add_param_ls = [dcc.Input(id='jump_intensity',placeholder='Insert jump_intensity',min=0.00,step=1,\
                                      size='md',type='number',value=1,debounce=True),\
                            dcc.Input(id='mu_j', min=0.00, step=.1, size='md',\
                                      placeholder='Insert mu_j',value=.4,type='number',debounce=True), \
                            dcc.Input(id='vol_j',min=0.00,step=.1,size='md',placeholder='Insert vol_j',\
                                      type='number',debounce=True,value=.2)]
    elif process == 'heston':
        process_param_ls = [dcc.Input(id='speed', placeholder='Insert k', min=0.00, step=.1, \
                                  size='md', type='number', debounce=True,value=2), \
                            dcc.Input(id='mean', min=0.00, step=.1, size='md', \
                                      placeholder='Insert mean of vol', type='number',debounce=True,value=.1), \
                            dcc.Input(id='vol_of_vol', min=0.00, step=.1, size='md',\
                                      placeholder='Insert vol of vol', \
                                      type='number', debounce=True,value=.5), \
                            dcc.Input(id='corr', min=0.00, step=.1, size='md', placeholder='Insert corr', \
                                      type='number', debounce=True,value=.7)]
        if model_specification == 'no-jump':
            add_param_ls = []
        elif model_specification == 'bates':
            add_param_ls = [dcc.Input(id='jump_intensity', placeholder='Insert jump_intensity', min=0.00, step=1, \
                                      size='40', type='number', debounce=True,value=1), \
                            dcc.Input(id='mu_j', min=0.00, step=.1, size='md', \
                                      placeholder='Insert mu_j', type='number', debounce=True,value=.5), \
                            dcc.Input(id='vol_j', min=0.00, step=.1, size='md', placeholder='Insert vol_j', \
                                      type='number', debounce=True,value=.2)]
    else:
        pass
    return params_ls+process_param_ls+add_param_ls

Final plot using inputs entered by the user

@app.callback(
   Output('stock_path', 'figure'),
   [Input('process', 'value'),
    Input('model_specification', 'value')]
)
def simulation(process,model_specification,*args):
    if (args[0] == None)|(args[1] == None):
        args[0] = 100
        args[1] = 0.05
    stock_sim = model(S0=args[0],r=args[1])
    df = stock_sim.create_path(model_specification)
    if process == 'black-scholes':
        if model_specification == 'no-jump':
            df = stock_sim.create_path(model_specification,args[2])
        elif model_specification == 'merton':
            df = stock_sim.create_path(model_specification,args[2],args[3],args[4],args[5])

    elif process == 'heston':
        stock_sim = model(process=process)
        if model_specification == 'no-jump':
            df = stock_sim.create_path(model_specification,args[2],args[3],args[4],args[5])
        elif model_specification == 'bates':
            df = stock_sim.create_path(model_specification,args[2],args[3],args[4],args[5],args[6],args[7])
    fig = px.line(df, title='Monte Carlo simulation', labels={'value': 'Prices', 'index': 'Time'})
    return fig

Could you provide a sample of your code so that the community can understand your problem better? That would be great!

@jgomes_eu Sure

1 Like

@edjanga - I haven’t looked at your code, but any mention of “dynamic inputs” likely means using Pattern-Matching Callbacks: Pattern-Matching Callbacks | Dash for Python Documentation | Plotly

1 Like

@chriddyp Thank you Chris for mentioning this.

Hello Dash community,

I have managed to make some progress on the above and I have faced a new blocker.

from dash import Dash, dcc, html, Input, Output, State, MATCH, ALL, ALLSMALLER
import pandas as pd
from stocks_simulation import model
import plotly.express as px
import pdb

app = Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Div([html.H1('Simulation of stock paths', style={'text-align': 'center', 'font-family':'verdana'}),\
                       html.H5('Generate stock paths according to different stochastic processes',\
                               style={'text-align': 'center','font-family':'verdana'}),\
                       dcc.Markdown('Select a process: ', style={'text-align': 'left', 'font-family': 'verdana'}),\
                       html.Div(\
                           dcc.RadioItems(id='process',options=[{'label': x, 'value': x} for x in\
                                                            ['black-scholes', 'heston']],\
                                      value='black-scholes',style={'text-align': 'left', 'font-family': 'verdana',\
                                                                   'display': 'inline-block'},\
                                      inputStyle={'margin': '10'}),style={'display': 'inline-block'}),\
                       dcc.Dropdown(multi=False, style={'text-align': 'left', 'font-family': 'verdana'},\
                                    id='model_specification'), \
                       html.Br(),\
                       html.Div(id='main_container')])

@app.callback(
    [Output('model_specification', 'options'),\
     Output('model_specification', 'value')],\
        Input('process', 'value'))
def dropdown_options(process):
    if process == 'black-scholes':
        options_dd = {model_specification: model_specification for model_specification in ['no-jump','merton']}
    elif process == 'heston':
        options_dd = {model_specification: model_specification for model_specification in ['no-jump','bates']}
    value = options_dd[list(options_dd.keys())[0]]
    return options_dd, value

@app.callback(
    Output('main_container','children'), \
    [Input('process','value'), \
     Input('model_specification', 'value')],\
    State('main_container','children')
)
def generate_paths(process,model_specification,div_children):
    div_children = []
    params_ls = [dcc.Input(id={'type':'input','index':0}, placeholder='Insert S0', min=0.00, step=1, size='md', \
                           type='number', value=0, debounce=True),\
                 dcc.Input(id={'type':'input','index':1}, min=0.00, step=.01, size='md', placeholder='Insert r', \
                           type='number', value=0, debounce=True)]
    if process == 'black-scholes':
        process_param_ls = [dcc.Input(id={'type':'input','index':2}, min=0.00, step=.01, size='md',\
                                      placeholder='Insert vol',type='number', debounce=True, value=0)]

        if model_specification == 'merton':
            model_specification_param_ls = [dcc.Input(id={'type':'input','index':3},\
                                                      placeholder='Insert jump_intensity',
                                                      min=0.00, step=1, size='40', type='number', debounce=True, \
                                                      value=0), \
                                            dcc.Input(id={'type':'input','index':4}, min=0.00, step=.1, size='md', \
                                                      placeholder='Insert mu_j', type='number',\
                                                      debounce=True, value=0), \
                                            dcc.Input(id={'type':'input','index':5}, min=0.00, step=.1, size='md', \
                                                      placeholder='Insert vol_j', \
                                                      type='number', debounce=True, value=0)]
        else:
            model_specification_param_ls = []
    elif process == 'heston':
        process_param_ls = [dcc.Input(id={'type':'input','index':2}, placeholder='Insert k', min=0.00, step=.1, \
                                      size='md', type='number', debounce=True, value=0), \
                            dcc.Input(id={'type':'input','index':3}, min=0.00, step=.1, size='md', \
                                      placeholder='Insert mean of vol', type='number', debounce=True, value=0), \
                            dcc.Input(id={'type':'input','index':4}, min=0.00, step=.1, size='md', \
                                      placeholder='Insert vol of vol', \
                                      type='number', debounce=True, value=0), \
                            dcc.Input(id={'type':'input','index':5}, min=0.00, step=.1,\
                                      size='md', placeholder='Insert corr', type='number', debounce=True, value=0)]

        if model_specification == 'bates':
            model_specification_param_ls = [dcc.Input(id={'type':'input','index':6}, \
                                                      placeholder='Insert jump_intensity',
                                                      min=0.00, step=1, size='40', type='number', debounce=True, \
                                                      value=0), \
                                            dcc.Input(id={'type':'input','index':7}, min=0.00, step=.1, size='md', \
                                                      placeholder='Insert mu_j', type='number', \
                                                      debounce=True, value=0), \
                                            dcc.Input(id={'type':'input','index':8}, min=0.00, step=.1, size='md', \
                                                      placeholder='Insert vol_j', \
                                                      type='number', debounce=True, value=0)]
        else:
            model_specification_param_ls = []
    div_children.append(html.Div(params_ls+process_param_ls+model_specification_param_ls))
    ####################################################################################################################
    ## Display figure
    ####################################################################################################################
    graph_comp = dcc.Graph(id={'type':'simulation', 'index':0},figure={})
    div_children.append(graph_comp)
    return div_children

@app.callback(
    Output(component_id={'type':'simulation', 'index':MATCH}, component_property='figure'),
    [Input('process','value'), \
     Input('model_specification', 'value'),\
     Input(component_id={'type':'input', 'index':MATCH}, component_property='value')]
)
def update_graph(process,model_specification,*args):
    print(args[0])
    print(args[1])
    stock_sim = model(S0=args[0], r=args[1])
    df = stock_sim.create_path(model_specification)
    if process == 'black-scholes':
        if model_specification == 'no-jump':
            df = stock_sim.create_path(model_specification, args[2])
        elif model_specification == 'merton':
            df = stock_sim.create_path(model_specification, args[2], args[3], args[4], args[5])
    elif process == 'heston':
        stock_sim = model(process=process)
        if model_specification == 'no-jump':
            df = stock_sim.create_path(model_specification, args[2], args[3], args[4], args[5])
        elif model_specification == 'bates':
            df = stock_sim.create_path(model_specification, args[2], args[3], args[4], args[5], args[6], args[7])
    fig = px.line(df, title='Monte Carlo simulation', labels={'value': 'Prices', 'index': 'Time'})
    return fig

if __name__ == '__main__':
    app.run_server(debug=True)

For some reasons, my args tuple is out of range despite being ([0,0,0]) when my update_graph function is called for the first time.

Has anyone faced this before?

Thanks

Hi,

The problem is that you are using the MATCH pattern, and according to the documentation:

Like ALL , MATCH will fire the callback when any of the component’s properties change. However, instead of passing all of the values into the callback, MATCH will pass just a single value into the callback. Instead of updating a single output, it will update the dynamic output that is “matched” with.

As you are passing a single value, then args becomes out of range when you update just one of the inputs. In the first time you had a length-3 value because your “initial layout” (with process BS) has three inputs defined with value=0 and the 3 inputs were updated at the same time.

Note also that MATCH is meant to match Input and Output in a one-to-one relation. Here you have many-to-one (just one graph) and probably you should use the standard id to your dcc.Graph component (or at least hardcode the index to 0).

In summary, this is probably how the decorator should look like:

@app.callback(
    Output("simulation", 'figure'),
    [Input('process','value'), \
     Input('model_specification', 'value'),\
     Input(component_id={'type':'input', 'index':ALL}, component_property='value')]
)

Hope this helps! :slight_smile:

4 Likes

@jlfsjunior Thank you - It has worked like a charm.

1 Like