Машинное обучение в трейдинге: теория, модели, практика и алготорговля - страница 3600

[Удален]
Aleksey Nikolayev #:
Имхо, ещё должно хотеться сделать нормальный форум по МО в трейдинге))

Хочется сделать, но не хочется делать )

[Удален]

Если не делать сильно большой форвард, то работает как конфета. Потом переобучать, с учетом новых данных.

Признаки любые, но лучше поближе к ценам. Много не надо, 10 достаточно.  Желательно нормализовать для кластеризации, если сильно отличаются по значениям.

Разметку желательно делать плотную, то есть для каждого изменения признаков должна быть метка. Но можно и на разреженном датасете. Метки должны соответствовать прибыльным сделкам в большинстве своем. Иначе будет обратный эффект.
Maxim Dmitrievsky #:
Видимо никто не читает, один флуд в теме.

Вроде я уже писал тогда, что у меня есть нечто такое же...

# Настройка источника данных
#dir_cb = "C:\\FX\\MT5_02\\MQL5\\Files\\00_Standart_50_Test"
dir_cb = "E:\\FX\\MT5_CB\\MQL5\\Files\\iDelta_D1_TP_4_SL_4_Random\\CB_Bi_Setup"

arr_viborka = ["train.csv", "test.csv", "exam.csv"]
strok_total_data = 1000
stolb_total_data = 100
Load_Strok = strok_total_data
Load_Stolb = stolb_total_data
# Задание параметров кластеризации
arr_Tree = [3,3,3] # Звдвём структуру дерева - указывается число кластеров/листьев на каждом уровне/ярусе
n_level = len(arr_Tree)
arr_filter = []
arr_centroid_arhiv = []

scaler = None #Переменная для нормализации
Variant_Norm=3 #Вариант нормализации
iteration=0;# Переменная для индексации перебора

# Задание параметров обработки данных
Use_Convert_CSV = True # Если необходимо открыть CSV и пробразовать и сохранить потом в формат Use_Convert_CSV
Use_Load_CSV = True # Загружать данные из файла или использовать случайно сгенерированные - для отладки кода   
Not_Bi_Data = False # Если выборка не бинарная, то True
Use_Creat_New_Viborka = True # Создать новую выборку через фильтрацию по стат. данным кластеризации
Variant_Filter_Klaster = 2 # Вариант условий фильтрации

# Проверяем наличие директории
directory = dir_cb+'\\Rez_Tree_K-Means'
if not os.path.exists(directory):# Если директории нет, создаем ее
    os.makedirs(directory)
    print(f"Директория {directory} была создана.")
else:
    print(f"Директория {directory} уже существует.")

if Use_Load_CSV == False:# Генерация случайных данных
    np.random.seed(100)
    arr_data_load = np.random.randint(0, 2, (strok_total_data, stolb_total_data))
    arr_Target = np.random.randint(2, size=Load_Strok)

if Use_Convert_CSV == True:
    f_Load_Data(dir_cb+'\\Setup','train',False,True,Not_Bi_Data)#Открыть в CSV и преобразовать в feather
    f_Load_Data(dir_cb+'\\Setup','test',False,True,Not_Bi_Data)#Открыть в CSV и преобразовать в feather
    f_Load_Data(dir_cb+'\\Setup','exam',False,True,Not_Bi_Data)#Открыть в CSV и преобразовать в feather

# Создаем пустой DataFrame для хранения статистики
#columns = ['random_state', 'cluster',
#           'Precision_train','Recall_train',#Итерация 0
#           'Precision_test','Recall_test',  #Итерация 1
#           'Precision_exam','Recall_exam']  #Итерация 2
columns = ['random_state', 'cluster',
           'Precision_train','Precision_test','Precision_exam',
           'Recall_train','Recall_test', 'Recall_exam',
           'Balans_train','Balans_test', 'Balans_exam'
           ]  
table_stat = pd.DataFrame(columns=columns)
#Svod_Statistics

arr_Name_Pred=[]

arr_Set_Viborka=['train','test','exam']
n_Set = len(arr_Set_Viborka)
for Set in range(n_Set):   
    if Use_Load_CSV == True:
        arr_data_load,arr_Target,arr_Info=f_Load_Data(dir_cb+'\\Setup',arr_Set_Viborka[Set],True,False,Not_Bi_Data)#Открыть файл в формате feather
        arr_Name_Pred=arr_data_load.columns.tolist()
        if Not_Bi_Data == True: #Если выборка не бинарная, то проведём нормализацию
            arr_data_load,scaler=f_Normalization(arr_data_load, Variant_Norm, Set, scaler)
            if Variant_Norm==0:
                arr_data_load = arr_data_load.values # Преобразуем полученные данные из Pandas в массив NumPy

        else:
            arr_data_load = arr_data_load.values # Преобразуем полученные данные из Pandas в массив NumPy

        #if Set == 0:#Нормализая - для не бинарной выборки
        #    scaler.fit(arr_data_load)
        #arr_data_load = scaler.transform(arr_data_load)

        #!!!arr_data_load = arr_data_load.values # Преобразуем полученные данные из Pandas в массив NumPy
        Load_Strok, Load_Stolb = arr_data_load.shape
    if Set == 0:
        # Вызовим функцию для кластеризации
        arr_filter=clusterize_and_filter(arr_data_load, arr_Tree, Load_Stolb, Load_Strok, n_level)
        arr_filter = arr_filter.reshape(-1, n_level)#Преобразуем в двухмерный массив

        # Сохранение результата кластеризации на каждом уровне дерева в файл CSV
        csv_data = pd.DataFrame(arr_filter)
        csv_data.to_csv(dir_cb + "\\Rez_Tree_K-Means\\f_Klaster_" + "train" + ".csv", index=False, header=False,sep=';', decimal='.')

        # Сохранение всех центройдов в файл CSV
        csv_data = pd.DataFrame(arr_centroid_arhiv)
        csv_data.to_csv(dir_cb + "\\Rez_Tree_K-Means\\f_Centroid_Arhiv.csv", index=False, header=False,sep=';', decimal='.')

        if Use_Load_CSV == False:#Только для тестов без чтения выборки!
            arr_data_load = arr_data_load.reshape(Load_Stolb, -1)# Преобразуем одномерный массив в двумерный массив, обратите внимание на то, что мы используем метод reshape()
            arr_data_load = pd.DataFrame(arr_data_load)# Преобразуем массив в DataFrame

    # Применяем модель на входных данных
    arr_Otvet = np.full(Load_Strok, -1)   
    for s in range(Load_Strok):
        arr_Get_Data = arr_data_load[s]#Копируем всю строку двухмерного массива
        Get_Klaster_N = f_Klaster_Load(arr_Tree, arr_Get_Data, Load_Stolb,arr_centroid_arhiv)
        arr_Otvet[s]=Get_Klaster_N

    # Сохранение результата применения модели в файл CSV
    csv_data = pd.DataFrame(arr_Otvet)
    csv_data.to_csv(dir_cb + "\\Rez_Tree_K-Means\\f_Klaster_Load_" + arr_Set_Viborka[Set] + ".csv", index=False, header=False,sep=';', decimal='.')

    # Добавление сохраненной информации к таблицам
    #arr_Info = pd.concat([arr_Info, csv_data], axis=1)
    # Находим уникальные значения и их количество
    #unique_values = np.unique(arr_Otvet)
    #num_unique_values = len(unique_values)
        
    # Подсчитываем статистику откликов и смещения процента целевой "1"
    df_Statistics=f_Statistics(arr_Otvet,arr_Target)
    #print("df_Statistics=",df_Statistics)
    # Сохранение результата в файл CSV
    csv_data = pd.DataFrame(df_Statistics)
    csv_data.to_csv(dir_cb + "\\Rez_Tree_K-Means\\f_Klaster_Statistics_" + arr_Set_Viborka[Set] + ".csv", index=False, header=False,sep=';', decimal='.')
    
    if Set == 0:
        # Получаем все уникальные значения из столбца 'Номер кластера'
        unique_clusters = df_Statistics['Номер кластера'].unique()
        # Создаем новый DataFrame
        new_data = pd.DataFrame({
            'random_state': [iteration]* len(unique_clusters),  # Заполняем первый столбец константой
            'cluster': unique_clusters  # Заполняем второй столбец уникальными значениями
        })
        table_stat = pd.concat([table_stat, new_data], ignore_index=True)# Используем pd.concat() для объединения DataFrame
    # Добавляем статистику для данных train в DataFrame
    matching_rows = table_stat[table_stat['random_state'] == iteration].index#Находим строки, где значение в столбце "random_state" равно переменной iteration        
    for index in matching_rows:# Для каждой найденной строки в table_stat
        second_column_value = table_stat.loc[index, 'cluster']  
        matching_row = df_Statistics[df_Statistics['Номер кластера'] == second_column_value]# Находим строку во второй таблице, где значение в столбце "Номер кластера" равно значению из столбца "cluster" в первой таблице
        if not matching_row.empty: # Если соответствующая строка найдена, копируем значения из второй таблицы в соответствующие столбцы первой таблицы
            table_stat.loc[index, 'Precision_'+arr_Set_Viborka[Set]] = matching_row.iloc[0]['Процент значений 1']
            table_stat.loc[index, 'Recall_'+arr_Set_Viborka[Set]] = matching_row.iloc[0]['Процент строк в кластере']                
    # Добавляем значение баланса для каждего кластера в DataFrame
    max_value = np.max(arr_Otvet)
    arr_Klaster_Balans = np.full(max_value+1, 0.0)
    print("arr_Info=",arr_Info)
    for i in range(arr_Info.shape[0]):
        Target_P=arr_Info['Target_P'][i]
        if Target_P>0:
            arr_Klaster_Balans[arr_Otvet[i]]+=arr_Info['Target_100_Buy'][i]
        else:
            arr_Klaster_Balans[arr_Otvet[i]]+=arr_Info['Target_100_Sell'][i]                        
    print('arr_Klaster_Balans=',arr_Klaster_Balans)
    for index in table_stat.index:#Перебор по строкам таблицы table_stat
        cluster_value = table_stat.loc[index, 'cluster']
        if cluster_value < len(arr_Klaster_Balans):#Если значение кластера меньше размера массива, то выполняем код
            table_stat.loc[index, 'Balans_'+arr_Set_Viborka[Set]] = arr_Klaster_Balans[cluster_value]
        else:
            # Обработка случаев, когда индекс выходит за границы массива
            pass  # Здесь можно добавить какую-то логику или значения по умолчанию для таких случаев

    if Set == 2:
        # Применяем lambda-функцию для преобразования столбцов в тип "float"
        table_stat = table_stat.apply(lambda col: pd.to_numeric(col, errors='coerce'))# Выводим информацию о типах данных
        # Сохранение результата в файл CSV
        csv_data = pd.DataFrame(table_stat)
        csv_data.to_csv(dir_cb + "\\Rez_Tree_K-Means\\f_Klaster_Svod_Statistics.csv", index=False, header=True,sep=';', decimal=',')

    if Use_Creat_New_Viborka==True:
        #Создадим отфильтрованную выборку
        arr_Data_New=f_Creat_Viborka(arr_data_load, arr_Name_Pred, arr_Info,table_stat,arr_Otvet,Load_Strok, Load_Stolb,Variant_Filter_Klaster)
        # Применяем lambda-функцию для преобразования столбцов в тип "float"
        #arr_Data_New = arr_Data_New.apply(lambda col: pd.to_numeric(col, errors='coerce'))# Выводим информацию о типах данных
        #arr_Data_New = arr_Data_New.apply(lambda col: pd.to_numeric(col, errors='coerce') if col.name != 'Time' else col)
        # Сохранение результата в файл CSV
        csv_data = pd.DataFrame(arr_Data_New)
        csv_data.to_csv(dir_cb + "\\Rez_Tree_K-Means\\"+arr_viborka[Set], index=False, header=True,sep=';', decimal='.')


    del csv_data
    if Use_Load_CSV == True:
        del arr_data_load
        del arr_Target
Maxim Dmitrievsky #:
Чем больше subset_size

За что отвечает этот параметр?

Maxim Dmitrievsky #:
Параметры можно подбирать.

Так когда перебираешь, то обязательно найдётся хороший результат - надо же, что бы всегда был хороший с любыми параметрами :)

Maxim Dmitrievsky #:
Потом переобучать, с учетом новых данных.

Как понять, что уже не работает - извечный вопрос.

Maxim Dmitrievsky #:
Много не надо, 10 достаточно.

В планах было сделать вариант выбора случайных признаков из моего ассортимента - пока не сделал - вообще почти месяц питон не трогал.

Aleksey Nikolayev #:

У меня там была задача найти не средний, а средний-максимальный спред - внутри минутки брался максимальный спред, а потом они усреднялись для каждой минуты дня.

В подсвеченном месте подставлял средневзвешенный по времени спред минуты. Всплески в 15:15, 15:30 и 17:00.

[Удален]
Aleksey Vyazmikin #:

Вроде я уже писал тогда, что у меня есть нечто такое же...

Мой код исправляет метки, ваш код что делает? 

Сейчас опять начнем ходить кругами, потому что ищем аналогии там, где их нет :)
[Удален]
Aleksey Vyazmikin #:

Как понять, что уже не работает - извечный вопрос.

Понять очень просто - перестает работать :)

Идею подхода уже описывал по ссылкам. Датасет группируется на похожие кластеры (паттерны, если угодно) по признакам. И метки для заданного (subset_size) кол-во кластеров исправляются, то есть все метки становятся 1 или 0, в зависимости от того, чего больше в кластере. Это убирает неоднозначность для финальной модели, она перестает переобучаться на шум и делать лишние сплиты.

В сортированном списке кластеров, по "смещению вероятности", в самом начале стоят самые смещенные. Они исправляются в первую очередь, чтобы стать полностью однозначными для последующего обучения модели. Остальные, которые в хвосте и вероятность в которых близка к 0.5, никак не трогаются и продолжают вносить шум в модель.

Варьируя кол-во кластеров и subset_size, ищется баланс между хорошими кластерами и плохими, который удовлетворяет юзера.

Работа функции прозрачна в том смысле, что дает проверенно ожидаемый результат: чем больше кластеров исправляется, тем модель более устойчива, но менее "красива" и наоборот. Поэтому добавлена доп. настройка, для регулировки.

В итоге, такая маленькая ф-я делает практически всю работу по поиску устойчивых паттернов в данных и улучшению модели. Если устойчивых паттернов нет, всегда будет предсказуемый (плохой) результат даже на трейне, тогда как без этой функции модель бы переобучилась и показала грааль на трейне.

Начинает работать статистика, зависящая от кол-ва данных. Чем больше трейн/тест, тем больше доверия. Без этой ф-ии такой критерий бесмысленный, потому что деревья растут при увеличении длины выборки (всегда переобучаются).
Maxim Dmitrievsky #:

Мой код исправляет метки, ваш код что делает? 

Ваш код убирает строки из выборки, которые относятся к кластерам, имеющих среднее значение (как Вы пишите 0,5) единиц.

Мой код делает то ж самое, если кратко:

1. Открываем выборку train

2. Делаем кластеризацию по принципу дерева, т.е. последовательно углубляясь в каждый кластер.

3. Оцениваем метрики каждого кластера на всех выборках.

4. Отбираем кластеры по выборке train, где есть смещение вероятности встретить целевую "1" больше 5%.

5. Формируем новую выборку по отобранным кластерам.

Но, я не пробовал так много кластеров использовать...

Особой стабильности после этого нет, улучшения есть, если хорошо разметка прошла рандомной кластеризацией, но без подглядывания в новые данные это не гарантированно.

Maxim Dmitrievsky #:
И метки для заданного (subset_size) кол-во кластеров исправляются, то есть все метки становятся 1 или 0, в зависимости от того, чего больше в кластере. Это убирает неоднозначность для финальной модели, она перестает переобучаться на шум и делать лишние сплиты.

Вот этого я не делал. Но, по сути это бинаризация уже получается. Опять же, если вероятности сохраняться на новых данных - эффект будет, а если нет, то трындец.

Схожий эффект я получаю через квантование по сути.
[Удален]
Aleksey Vyazmikin #:

Ваш код убирает строки из выборки, которые относятся к кластерам, имеющих среднее значение (как Вы пишите 0,5) единиц.

Мой код этого не делает и это видно в коде. Понеслось принятие желаемого за действительное.
[Удален]
Aleksey Vyazmikin #:

Вот этого я не делал. Но, по сути это бинаризация уже получается. Опять же, если вероятности сохраняться на новых данных - эффект будет, а если нет, то трындец.

Схожий эффект я получаю через квантование по сути.
Опять додумки. Чувствую себя предсказателем.