Duplicate key error (st.selectbox)

Why am I getting a duplicate key error if I’m assigning a unique key to both of the select boxes?

import streamlit as st
import time
from modules.page import Page
import pandas as pd
import json


class BacktestResults(Page):
    def __init__(self):
        super().__init__()
        
        self.based_on_dict = {
            'Closed Trades': ['overview', 'closed_trades'], 
            'Net Profit': ['overview', 'net_profit'], 
            'Percent Profitable': ['overview', 'percent_profitable'], 
            'Expectancy': ['overview', 'average_profit'], 
            'Buy & Hold Return': ['performance_summary', 'buy_n_hold_return'], 
            'Max Drawdown': ['overview', 'max_drawdown'], 
            'Sharpe Ratio': ['performance_summary', 'results', 'sharpe_ratio'], 
            'Sortino Ratio': ['performance_summary', 'results', 'sortino_ratio'],
            'Calmar Ratio': ['performance_summary', 'results', 'calmar_ratio']
        }
        
    def _setup(self):
        
        self.setup(
            layout = "wide", 
            initial_sidebar_state = "expanded"
        )
                
        self._top50()
        self._best_indexes()
        
    def _top50(self):
        sort_by_combinations = st.selectbox(
            "Sort By",
            options = ['Select an option', 'Closed Trades', 'Net Profit', 'Percent Profitable', 'Expectancy', 'Profit Factor (Pain to Gain)', 'Rate of Return (Annualized)', 'Buy & Hold Return', 'Max Drawdown', 'Risk of Ruin', 'Sharpe Ratio', 'Sortino Ratio', 'Calmar Ratio'],
            on_change = self._top50,
            key = "sort_by_combinations_selectbox"
        )
      
        if sort_by_combinations != 'Select an option':
            try:
                top_combinations = self.data_analysis.get_top_combinations(
                    based_on = self.based_on_dict[sort_by_combinations], 
                    limit = 50
                )
                
                # Generate results table
                dicts = []
                for combination in top_combinations:
                    
                    d = {
                        "Combination": combination[0],
                        "Closed Trades": combination[1]["overview"]["closed_trades"],
                        "Net Profit": combination[1]["overview"]["net_profit"],
                        "Percent Profitable": combination[1]["overview"]["percent_profitable"],
                        "Expectancy": combination[1]["overview"]["average_profit"],
                        "Profit Factor (Pain to Gain)": combination[1]["overview"]["profit_factor"],
                        "Buy & Hold Return": combination[1]["performance_summary"]["results"]["buy_n_hold_return"],
                        "Max Drawdown": combination[1]["overview"]["max_drawdown"],
                        "Sharpe Ratio": combination[1]["performance_summary"]["results"]["sharpe_ratio"],
                        "Sortino Ratio": combination[1]["performance_summary"]["results"]["sortino_ratio"],
                        "Calmar Ratio": combination[1]["performance_summary"]["results"]["calmar_ratio"]
                    }
                
                    dicts.append(d)

                # Convert the list of dictionaries into a pandas DataFrame
                df = pd.DataFrame(dicts)

                st.markdown("## Top 50 Combinations")

                st.dataframe(df, use_container_width = True)
            except Exception as error:
                print(error)

    def _best_indexes(self):
        
        st.markdown(
            "---"
        )
        
        sort_by_indexes = st.selectbox(
            label = "Sort By",
            options = ('Select an option', 'Closed Trades', 'Net Profit', 'Percent Profitable', 'Expectancy', 'Profit Factor (Pain to Gain)', 'Rate of Return (Annualized)', 'Buy & Hold Return', 'Max Drawdown', 'Risk of Ruin', 'Sharpe Ratio', 'Sortino Ratio', 'Calmar Ratio'),
            key = "sort_by_indexes_selectbox"
        )
        
        if sort_by_indexes != 'Select an option':

            best_indexes = self.data_analysis.best_indexes(
                based_on = self.based_on_dict[sort_by_indexes], 
                format_message = False
            )
                        
            best_indexes_table = ""
            for key, value in best_indexes["data"].items():
                best_indexes_table += f"""
                ### {self.data_handler.variables[int(key)]['name']}
                """
                for repeated in value:
                    best_indexes_table += f"""
                    {repeated['value']} is repeated {repeated['repeated']} times with a {repeated['average']} average
                    """
            
            st.markdown(
                best_indexes_table
            )
                
        
backtest_results = BacktestResults()

backtest_results._setup()

Hi @Ivan_Schuster
It seems not all prerequisite code were shared to reproduce the error. Do you have a link to your GitHub repo of this app along with the requirements.txt file, particularly the code in modules.pages was not provided.

Best regards,
Chanin

1 Like
import json
import time
import threading
import pandas as pd
import streamlit as st
from pathlib import Path

from st_pages import show_pages
from st_pages import Page as StPage

from modules.data_handler import DataHandler
from modules.data_analysis import DataAnalysis
from streamlit.runtime.scriptrunner import add_script_run_ctx

class Page:
    def __init__(self):
        
        self.data_handler = DataHandler()
        self.data_analysis = DataAnalysis(
            database_path = self.data_handler.const_data["Database"]["Path"]
        )
        
        self.data_handler.run_thread(target = self.data_handler.const_b_listener, interval = 1)

        self.param_options = [var['name'] for var in self.data_handler.variables]
        self.metric_options = ['Select an option', 'Net Profit', 'Closed Trades', 'Percent Profitable', 'Expectancy', 'Profit Factor', 'Buy & Hold Return', 'Max Drawdown', 'Sharpe Ratio', 'Sortino Ratio', 'Calmar Ratio']

        self.based_on = {
            'Closed Trades': ['overview', 'closed_trades'], 
            'Net Profit': ['overview', 'net_profit'], 
            'Percent Profitable': ['overview', 'percent_profitable'], 
            'Profit Factor': ['overview', 'profit_factor'],
            'Expectancy': ['overview', 'average_profit'], 
            'Buy & Hold Return': ['performance_summary', 'results', 'buy_n_hold_return'], 
            'Max Drawdown': ['overview', 'max_drawdown'], 
            'Sharpe Ratio': ['performance_summary', 'results', 'sharpe_ratio'], 
            'Sortino Ratio': ['performance_summary', 'results', 'sortino_ratio'],
            'Calmar Ratio': ['performance_summary', 'results', 'calmar_ratio'],
            #'Score': ['performance_summary', 'score']   
        }

    def separator(self, n):
        for _ in range(0, n + 1):
            st.text("\n")

    def setup(self, layout, initial_sidebar_state):
        """
        Args:
            layout                : str : wide - centered
            initial_sidebar_state : str : expanded - collapsed
        """
        # Page config
        st.set_page_config(
            page_title = self.data_handler.const_data['PageSetup']['Title'], 
            page_icon = self.data_handler.const_data['PageSetup']['Icon'], 
            layout = layout, 
            initial_sidebar_state = "collapsed" #initial_sidebar_state
        )

        hide_streamlit_style = """
                    <style>
                    #MainMenu {visibility: hidden;}
                    footer {visibility: hidden;}
                    </style>
        """
        st.markdown(hide_streamlit_style, unsafe_allow_html = True) 
        
        show_pages(
            [
                StPage(
                    path = f"{self.data_handler.root_path}/Home.py",
                    name = "Home",
                    icon = "🏠" # 🏠 πŸšͺ πŸŒ…
                ), 
                StPage(
                    path = f"{self.data_handler.root_path}/pages/backtest_results.py",
                    name = "Backtest Results",
                    icon = "πŸ“Š" # πŸ“ˆ πŸ’Ή πŸ“Š
                ),
                StPage(
                    path = f"{self.data_handler.root_path}/pages/top_combination.py",
                    name = "Top Combination",
                    icon = "πŸ†" # πŸ† πŸ₯‡ πŸ”
                ),
                StPage(
                    path = f"{self.data_handler.root_path}/pages/optimization_analysis.py",
                    name = "Optimization Analysis",
                    icon = "πŸ”" # πŸ” πŸ”¬ πŸ“‰
                ),
                StPage(
                    path = f"{self.data_handler.root_path}/pages/trade_analysis.py",
                    name = "Trade Analysis",
                    icon = "πŸ’°" # πŸ’Έ πŸ’° πŸ’΅
                ),
                StPage(
                    path = f"{self.data_handler.root_path}/pages/help_support.py",
                    name = "Help & Support",
                    icon = "πŸ†˜" # πŸ†˜ πŸ’‘ 🀝
                )
            ]
        )
                
        if self.data_handler.const_b_checked and not self.data_handler.const_b_received:  
            st.markdown(
                """
                <style>
                    .css-1rs6os.edgvbvh3 {
                        display: none;
                    }
                </style>
                """, 
                unsafe_allow_html = True
            )
            with st.spinner("Waiting for backtest to start"):
                while not self.data_handler.const_b_received:
                    time.sleep(1)
        else:
            st.markdown(
                """
                <style>
                    .css-1rs6os.edgvbvh3 {
                        display: inline-block;
                    }
                </style>
                """, 
                unsafe_allow_html = True
            )

Can you post the error you are getting?

Because you are creating each widget twice. First in the setup() call, then again in the callback when the widget changes.

oh right lmao tysm!!!

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.