For loop with callback

Hi,

I am using Card to have a deck of financial asset that is composed of a candlestick graph and the change on the last 24h.
I am able to do a comprehension list for building the layout of the card for each row. However I am not able to build the callback function in a way i just write one callback to populate the card because only the symbol of the financial asset changes not the graph themselve.

the callback is as follow but it is not working with the TypeError: update_graph() missing 1 required positional argument: ‘symbol’.:

for symbol in symbols:
    @app.callback(
        [Output('indicator-graph-{}'.format(symbol), 'figure')],
        [Input('update', 'n_intervals')]
    )
    def update_graph(timer, symbol):
        # dff_rv = dff.iloc[::-1]
        day_start = candle_hourly[symbol][candle_hourly[symbol]['date'] == candle_hourly[symbol]['date'].min()]['close'].values[0]
        day_end = candle_hourly[symbol][candle_hourly[symbol]['date'] == candle_hourly[symbol]['date'].max()]['close'].values[0]
        # print(day_start)
        # print(day_end)

        fig = go.Figure(go.Indicator(
            mode="delta",
            value=day_end,
            delta={'reference': day_start, 'relative': True, 'valueformat': '.2%'}))
        fig.update_traces(delta_font={'size': 12})
        fig.update_layout(height=30, width=70)

        if day_end >= day_start:
            fig.update_traces(delta_increasing_color='green')
        elif day_end < day_start:
            fig.update_traces(delta_decreasing_color='red')
        return fig

What can I change to make it work? Is there a better way to write my callback?
Read about dynamics callback but not sure how it would work.

Welcome to Dash!

Your function has two arguments (timer, symbol), but you’ve only bound one UI input to the function (Input('update', 'n_intervals').

So to start, remove symbol from update_graph. You’ll want symbol passed into the function “implicitly” via the local scope / closure. This can be tricky, unexpected things with how these work in with Python, see e.g. function - Python, loops and closures - Stack Overflow. It may work by just removing symbol but you might have better luck refactoring to create a “function factory”.

Something like this:

def create_update_graph_function(symbol):
    def update_graph(timer):
         # the function you have above
         day_start =... 
         # this function can have `symbol` in it...
         print(symbol)
         # etc etc
     return update_graph # yup, returning a function!

for symbol in symbols:
     update_graph_function = create_update_graph_function(symbol)
     app.callback(Output(...), Input(...))(update_graph_function)

This can also be simplified as just:

for symbol in symbols:
     app.callback(Output(...), Input(...))(create_update_graph_function(symbol))

create_update_graph(symbol) returns a function which is then wrapped by the callback.

I’m using an alternative syntax for callbacks that makes it easier to pass in “dynamically” created functions. With callbacks, this:

@callback(...)
def foo():
   pass

is the same as:

callback(...)(foo)

Or in the example in the example above, app.callback(Output(...), Input(...))(create_update_graph(symbol))

Welcome to the world of closures and decorators!

1 Like

(Also my ... aren’t anything special or specific to Python - it’s just a short hand for “fill in the blanks” :slightly_smiling_face: )

@chriddyp Thank you. It works. Learnt something new. It’s great.
Need to keep practicing this part now :slight_smile: