here is my code which is work fine on csv files with under 30mb size(operation done in 5 min for each file ), but for bigger size csv files like 40mb ,its took so long ,like 2-3 hours 

is it possible to improve it without changing functionality?:
import pandas as pd
import os
import numpy as np
from datetime import timedelta

# The path where your CSV files are stored
daily_data_path = r"E:\01_calculate_talib\day_spot/errored - Copy"
hourly_data_path = r"H:\1h_spot"
four_data_path = r"E:\01_calculate_talib\4h_spot"
week_data_path = r"E:\01_calculate_talib\week_spot"
month_data_path = r"E:\01_calculate_talib\month_spot"

valid_extras_directory = r"H:\trade\crypto data\day\to merge corrected(replace dot values)"

def find_first_matching_1h(filename):
    for root, _, files in os.walk(hourly_data_path):
        for file in files:
            if file.split("_")[-2] == filename:
                return os.path.join(root, file)

    return None


def find_first_matching_4h(filename):
    for root, _, files in os.walk(four_data_path):
        for file in files:
            if file.split("_")[-2] == filename:
                return os.path.join(root, file)

    return None


def find_first_matching_week(filename):
    for root, _, files in os.walk(week_data_path):
        for file in files:
            if file.split("_")[-2] == filename:
                return os.path.join(root, file)

    return None


def find_first_matching_month(filename):
    for root, _, files in os.walk(month_data_path):
        for file in files:
            if file.split("_")[-2] == filename:
                return os.path.join(root, file)

    return None


# Iterate through each file in the csv_folder_path
def add_all_data(daily_df, hourly_df, four_df, week_df, month_df):
    # Generate the hourly column names dynamically based on available columns minus 'Date'
    hourly_column_names = [f"c{i}_h_{col}" for i in range(1, 25) for col in hourly_df.columns if col != 'Date']
    four_column_names = [f"c{i}_4h_{col}" for i in range(1, 7) for col in four_df.columns if col != 'Date']
    week_column_names = [f"c{i}_w_{col}" for i in range(1, 2) for col in week_df.columns if col != 'Date']
    month_column_names = [f"c{i}_m_{col}" for i in range(1, 2) for col in month_df.columns if col != 'Date']

    # Combined DataFrame with adjusted columns
    combined_columns = list(
        daily_df.columns) + hourly_column_names + four_column_names + week_column_names + month_column_names
    combined_df = pd.DataFrame(columns=combined_columns)

    # Function to generate hourly data for a given day
    def get_hourly_data_for_day(day):
        filtered_hourly_df = hourly_df[hourly_df['Date'] == day]
        hourly_data = []
        for _, row in filtered_hourly_df.iterrows():
            for col in filtered_hourly_df.columns:
                if col != 'Date':
                    hourly_data.append(row[col])
        while len(hourly_data) < len(hourly_column_names):
            hourly_data.append(None)  # Append None for missing data
        return hourly_data

    def get_four_data_for_day(day):
        filtered_four_df = four_df[four_df['Date'] == day]
        four_data = []
        for _, row in filtered_four_df.iterrows():
            for col in filtered_four_df.columns:
                if col != 'Date':
                    four_data.append(row[col])
        while len(four_data) < len(four_column_names):
            four_data.append(None)  # Append None for missing data
        return four_data

    def get_week_data_for_day(day):

        week_start = week_df['Date'] - pd.to_timedelta(6, unit='d')
        filtered_week_df = week_df[(week_start <= day) & (week_df['Date'] >= day)]
        week_data = []
        for _, row in filtered_week_df.iterrows():
            for col in filtered_week_df.columns:
                if col != 'Date':
                    week_data.append(row[col])
        while len(week_data) < len(week_column_names):
            week_data.append(None)  # Append None for missing data
        return week_data

    def get_month_data_for_day(day):
        month_start = month_df['Date'].values.astype('datetime64[M]')
        filtered_month_df = month_df[(month_start <= day) & (month_df['Date'] >= day)]
        month_data = []
        for _, row in filtered_month_df.iterrows():
            for col in filtered_month_df.columns:
                if col != 'Date':
                    month_data.append(row[col])
        while len(month_data) < len(month_column_names):
            month_data.append(None)  # Append None for missing data
        return month_data

    for _, daily_row in daily_df.iterrows():
        daily_data = daily_row.tolist()
        day = daily_row['Date'].date()
        hourly_data = get_hourly_data_for_day(day)
        four_data = get_four_data_for_day(day)
        week_data = get_week_data_for_day(day)
        month_data = get_month_data_for_day(day)
        combined_row = daily_data + hourly_data + four_data + week_data + month_data
        combined_df = pd.concat([combined_df, pd.DataFrame([combined_row], columns=combined_columns)],
                                ignore_index=True)

    return combined_df


def add_y_with_next_2d(combined_df):
    # Precisely identify columns
    high_columns = [col for col in combined_df.columns if col.startswith('c') and col.endswith('_h_High')]
    low_columns = [col for col in combined_df.columns if col.startswith('c') and col.endswith('_h_Low')]

    # Initial placeholders for new columns
    combined_df['y_High_2d'] = np.nan
    combined_df['y_Low_2d'] = np.nan
    combined_df['y_Priority_2d'] = np.nan

    # Iterating through DataFrame rows except the last two
    for index in range(len(combined_df) - 2):
        next_two_high_values = combined_df.loc[index + 1:index + 2, high_columns].to_numpy().flatten()
        next_two_low_values = combined_df.loc[index + 1:index + 2, low_columns].to_numpy().flatten()

        # Find max and min values for y_High and y_Low
        y_High_value = np.max(next_two_high_values)
        y_Low_value = np.min(next_two_low_values)

        # Assign y_High and y_Low to the current row
        combined_df.at[index, 'y_High_2d'] = y_High_value
        combined_df.at[index, 'y_Low_2d'] = y_Low_value

        # Determine the positions (row, column index) of the max and min values
        highest_pos = np.argmax(next_two_high_values)
        lowest_pos = np.argmin(next_two_low_values)

        highest_row = highest_pos // len(high_columns)
        lowest_row = lowest_pos // len(low_columns)

        # Calculate y_Priority based on the rules provided
        if lowest_row is None:
            print(f'---------------------------------------------- None at {index}')
            combined_df.at[index, 'y_Priority_2d'] =0
        elif highest_row is None:
            print(f'---------------------------------------------- None at {index}')
            combined_df.at[index, 'y_Priority_2d'] = 0
        elif highest_row < lowest_row:
            combined_df.at[index, 'y_Priority_2d'] = 1
        elif highest_row >= lowest_row:
            combined_df.at[index, 'y_Priority_2d'] = 0
        else:  # High and Low are in the same row in respect to the next two rows
            highest_col_index = highest_pos % len(high_columns)
            lowest_col_index = lowest_pos % len(low_columns)

            if highest_col_index < lowest_col_index:
                combined_df.at[index, 'y_Priority_2d'] = 1
            else:
                combined_df.at[index, 'y_Priority_2d'] = 0

    return combined_df

def add_y_with_next_3d(combined_df):
    # Precisely identify columns
    high_columns = [col for col in combined_df.columns if col.startswith('c') and col.endswith('_h_High')]
    low_columns = [col for col in combined_df.columns if col.startswith('c') and col.endswith('_h_Low')]

    # Initial placeholders for new columns
    combined_df['y_High_3d'] = np.nan
    combined_df['y_Low_3d'] = np.nan
    combined_df['y_Priority_3d'] = np.nan

    # Iterating through DataFrame rows except the last two
    for index in range(len(combined_df) - 3):
        next_3_high_values = combined_df.loc[index + 1:index + 3, high_columns].to_numpy().flatten()
        next_3_low_values = combined_df.loc[index + 1:index + 3, low_columns].to_numpy().flatten()

        # Find max and min values for y_High and y_Low
        y_High_value = np.max(next_3_high_values)
        y_Low_value = np.min(next_3_low_values)

        # Assign y_High and y_Low to the current row
        combined_df.at[index, 'y_High_3d'] = y_High_value
        combined_df.at[index, 'y_Low_3d'] = y_Low_value

        # Determine the positions (row, column index) of the max and min values
        highest_pos = np.argmax(next_3_high_values)
        lowest_pos = np.argmin(next_3_low_values)

        highest_row = highest_pos // len(high_columns)
        lowest_row = lowest_pos // len(low_columns)

        # Calculate y_Priority based on the rules provided
        if lowest_row is None:
            print(f'---------------------------------------------- None at {index}')
            combined_df.at[index, 'y_Priority_3d'] =0
        elif highest_row is None:
            print(f'---------------------------------------------- None at {index}')
            combined_df.at[index, 'y_Priority_3d'] = 0
        elif highest_row < lowest_row:
            combined_df.at[index, 'y_Priority_3d'] = 1
        elif highest_row >= lowest_row:
            combined_df.at[index, 'y_Priority_3d'] = 0
        else:  # High and Low are in the same row in respect to the next two rows
            highest_col_index = highest_pos % len(high_columns)
            lowest_col_index = lowest_pos % len(low_columns)

            if highest_col_index < lowest_col_index:
                combined_df.at[index, 'y_Priority_3d'] = 1
            else:
                combined_df.at[index, 'y_Priority_3d'] = 0

    return combined_df

def add_y_with_next_5d(combined_df):
    # Precisely identify columns
    high_columns = [col for col in combined_df.columns if col.startswith('c') and col.endswith('_h_High')]
    low_columns = [col for col in combined_df.columns if col.startswith('c') and col.endswith('_h_Low')]

    # Initial placeholders for new columns
    combined_df['y_High_5d'] = np.nan
    combined_df['y_Low_5d'] = np.nan
    combined_df['y_Priority_5d'] = np.nan

    # Iterating through DataFrame rows except the last two
    for index in range(len(combined_df) - 5):
        next_5_high_values = combined_df.loc[index + 1:index + 5, high_columns].to_numpy().flatten()
        next_5_low_values = combined_df.loc[index + 1:index + 5, low_columns].to_numpy().flatten()

        # Find max and min values for y_High and y_Low
        y_High_value = np.max(next_5_high_values)
        y_Low_value = np.min(next_5_low_values)

        # Assign y_High and y_Low to the current row
        combined_df.at[index, 'y_High_5d'] = y_High_value
        combined_df.at[index, 'y_Low_5d'] = y_Low_value

        # Determine the positions (row, column index) of the max and min values
        highest_pos = np.argmax(next_5_high_values)
        lowest_pos = np.argmin(next_5_low_values)

        highest_row = highest_pos // len(high_columns)
        lowest_row = lowest_pos // len(low_columns)

        # Calculate y_Priority based on the rules provided
        if lowest_row is None:
            print(f'---------------------------------------------- None at {index}')
            combined_df.at[index, 'y_Priority_5d'] = 0
        elif highest_row is None:
            print(f'---------------------------------------------- None at {index}')
            combined_df.at[index, 'y_Priority_5d'] = 0
        if highest_row < lowest_row:
            combined_df.at[index, 'y_Priority_5d'] = 1
        elif highest_row >= lowest_row:
            combined_df.at[index, 'y_Priority_5d'] = 0
        else:  # High and Low are in the same row in respect to the next two rows
            highest_col_index = highest_pos % len(high_columns)
            lowest_col_index = lowest_pos % len(low_columns)

            if highest_col_index < lowest_col_index:
                combined_df.at[index, 'y_Priority_5d'] = 1
            else:
                combined_df.at[index, 'y_Priority_5d'] = 0

    return combined_df

def merge_valid_files(combined_df):
    global df_merged
    first = True

    for valid_extra in os.listdir(valid_extras_directory):
        if valid_extra.endswith(".csv"):
            valid_extra_path = os.path.join(valid_extras_directory, valid_extra)
            extra_data = pd.read_csv(valid_extra_path)
            extra_data['Date'] = pd.to_datetime(extra_data['Date'], format="ISO8601", utc=True)
            if first:
                df_merged = combined_df.merge(extra_data, how='left', on='Date')
                first = False
            else:
                df_merged = df_merged.merge(extra_data, how='left', on='Date')

    return df_merged


for daily_csv_file in os.listdir(daily_data_path):

    try:

        daily_file_path = os.path.join(daily_data_path, daily_csv_file)
        hourly_file_path = find_first_matching_1h(daily_csv_file.split('_')[-2])
        four_file_path = find_first_matching_4h(daily_csv_file.split('_')[-2])
        week_file_path = find_first_matching_week(daily_csv_file.split('_')[-2])
        month_file_path = find_first_matching_month(daily_csv_file.split('_')[-2])
        print(f'processig {daily_csv_file}'
              f' with {hourly_file_path}'
              f' with {four_file_path}'
              f' with {week_file_path}'
              f' with {month_file_path}')
        # Load the daily and hourly data from CSV files
        daily_df = pd.read_csv(daily_file_path)
        hourly_df = pd.read_csv(hourly_file_path)
        four_df = pd.read_csv(four_file_path)
        week_df = pd.read_csv(week_file_path)
        month_df = pd.read_csv(month_file_path)

        daily_df['Date'] = pd.to_datetime(daily_df['Date'], format="ISO8601", utc=True)
        hourly_df['Date'] = pd.to_datetime(hourly_df['Date'], format="ISO8601", utc=True)
        four_df['Date'] = pd.to_datetime(four_df['Date'], format="ISO8601", utc=True)
        week_df['Date'] = pd.to_datetime(week_df['Date'], format="ISO8601", utc=True)
        month_df['Date'] = pd.to_datetime(month_df['Date'], format="ISO8601", utc=True)

        # Strip time part from hourly_df 'Date' for alignment
        hourly_df['Date'] = hourly_df['Date'].dt.date
        four_df['Date'] = four_df['Date'].dt.date
        week_df['Date'] = week_df['Date'].dt.date
        month_df['Date'] = month_df['Date'].dt.date

        combined_df = add_all_data(daily_df, hourly_df, four_df, week_df, month_df)

        columns_to_remove = combined_df.filter(like='_Date').columns
        combined_df.drop(columns=columns_to_remove, inplace=True)

        columns_to_remove = combined_df.filter(like='_Symbol').columns
        combined_df.drop(columns=columns_to_remove, inplace=True)

        high_cols = [f'c{i}_h_High' for i in range(1, 25)]
        low_cols = [f'c{i}_h_Low' for i in range(1, 25)]

        # Shift the DataFrame by one row
        df_shifted = combined_df.shift(-1)

        # Calculate y_High and y_Low
        combined_df['y_High_1d'] = df_shifted[high_cols].max(axis=1)
        combined_df['y_Low_1d'] = df_shifted[low_cols].min(axis=1)


        # Calculate y_Priority
        def calculate_priority(row):
            high_index = np.argmax(row[high_cols].values)
            low_index = np.argmin(row[low_cols].values)
            return int(high_index < low_index)


        combined_df['y_Priority_1d'] = df_shifted.apply(calculate_priority, axis=1)

        combined_df = add_y_with_next_2d(combined_df)
        combined_df = add_y_with_next_3d(combined_df)
        combined_df = add_y_with_next_5d(combined_df)

        combined_df = combined_df.iloc[2:-6]

        combined_df = merge_valid_files(combined_df)
        # Save the combined DataFrame
        combined_df.to_csv(daily_file_path, index=False)
        print('Combined CSV has been saved.')

    except Exception as e:
        print(
            f"----------------------------------> An error occurred while processing {daily_file_path}: {e}")