Project structure:

o1_made_2/
├── data/
│   ├── raw_data.csv
│   ├── test_data.csv
│   └── train_data.csv
├── models/
│   ├── best_model.pth
│   └── scaler.gz
├── data_loader.py
├── data_loader_v2.py
├── model.py
├── test.py
└── train.py


Project files:

data_loader.py
```py
не нужно
```


data_loader_v2.py
```py

# data_loader.py

import requests
import pandas as pd
import time
from datetime import datetime
from tqdm import tqdm
from concurrent.futures import ThreadPoolExecutor, as_completed
import math
from threading import Lock

def get_data(symbol, start_time, end_time, interval, pbar, lock):
    url = "https://api.binance.com/api/v3/klines"
    limit = 1000
    all_data = []
    while start_time < end_time:
        params = {
            'symbol': symbol,
            'interval': interval,
            'startTime': start_time,
            'endTime': end_time,
            'limit': limit
        }
        response = requests.get(url, params=params)
        data = response.json()
        if not data:
            break
        all_data.extend(data)
        start_time = data[-1][6] + 1  # Обновляем время начала
        with lock:
            pbar.update(1)
        time.sleep(0.001)  # Небольшая задержка, чтобы не перегружать API
    return all_data


def add_indicators(df):
    # Сортируем данные по времени
    df = df.sort_values('Open Time')

    # Простая скользящая средняя (SMA)
    df['SMA_10'] = df['Close'].rolling(window=10).mean()
    df['SMA_30'] = df['Close'].rolling(window=30).mean()

    # Экспоненциальная скользящая средняя (EMA)
    df['EMA_10'] = df['Close'].ewm(span=10, adjust=False).mean()
    df['EMA_30'] = df['Close'].ewm(span=30, adjust=False).mean()

    # Индекс относительной силы (RSI)
    delta = df['Close'].diff()
    up = delta.clip(lower=0)
    down = -delta.clip(upper=0)
    ema_up = up.ewm(com=13, adjust=False).mean()
    ema_down = down.ewm(com=13, adjust=False).mean()
    rs = ema_up / ema_down
    df['RSI'] = 100 - (100 / (1 + rs))

    return df


def collect_data(start_date_str, end_date_str, symbols):
    # Устанавливаем интервал в 5 минут
    interval = '5m'

    # Преобразуем строки дат в объекты datetime
    start_date = datetime.strptime(start_date_str, '%Y-%m-%d')
    end_date = datetime.strptime(end_date_str, '%Y-%m-%d')

    # Начальное и конечное время в формате миллисекунд
    start_time = int(start_date.timestamp() * 1000)
    end_time = int(end_date.timestamp() * 1000)

    all_symbol_data = []

    print("Сбор данных для валют:", symbols)

    # Создаем общий lock для синхронизации прогрессбаров
    lock = Lock()

    # Создаем словарь для хранения прогрессбаров
    progress_bars = {}

    # Вычисляем общее количество шагов для каждого символа
    total_steps = {}
    interval_ms = 5 * 60 * 1000  # Интервал 5 минут в миллисекундах

    for idx, symbol in enumerate(symbols):
        total_candles = (end_time - start_time) / interval_ms
        steps = math.ceil(total_candles / 1000)  # Так как мы запрашиваем по 1000 записей за раз
        total_steps[symbol] = steps
        progress_bars[symbol] = tqdm(total=steps, desc=f'Сбор {symbol}', position=idx, leave=True)

    # Используем ThreadPoolExecutor для параллельного сбора данных
    with ThreadPoolExecutor(max_workers=5) as executor:
        futures = {}
        for symbol in symbols:
            pbar = progress_bars[symbol]
            future = executor.submit(get_data, symbol, start_time, end_time, interval, pbar, lock)
            futures[future] = symbol

        for future in as_completed(futures):
            symbol = futures[future]
            data = future.result()
            if not data:
                print(f"Нет данных для валюты: {symbol}")
            else:
                # Преобразуем данные в DataFrame
                columns = ['Open Time', 'Open', 'High', 'Low', 'Close', 'Volume',
                           'Close Time', 'Quote Asset Volume', 'Number of Trades',
                           'Taker Buy Base Asset Volume', 'Taker Buy Quote Asset Volume', 'Ignore']
                df = pd.DataFrame(data, columns=columns)
                df['Symbol'] = symbol

                # Конвертируем типы данных
                df['Open Time'] = pd.to_datetime(df['Open Time'], unit='ms')
                df['Close Time'] = pd.to_datetime(df['Close Time'], unit='ms')
                numeric_columns = ['Open', 'High', 'Low', 'Close', 'Volume']
                df[numeric_columns] = df[numeric_columns].astype(float)

                # Добавляем индикаторы
                df = add_indicators(df)

                # Удаляем строки с NaN (после вычисления индикаторов)
                df.dropna(inplace=True)
                df.reset_index(drop=True, inplace=True)

                # Добавляем данные в общий список
                all_symbol_data.append(df)

                # Закрываем прогрессбар для текущего символа
                progress_bars[symbol].close()

                time.sleep(0.001)  # Небольшая задержка

    # Объединяем все данные в один DataFrame
    if all_symbol_data:
        full_df = pd.concat(all_symbol_data, ignore_index=True)

        # Сортируем по валюте и времени
        full_df.sort_values(['Symbol', 'Open Time'], inplace=True)

        # Сохраняем данные с индикаторами в CSV
        full_df.to_csv('data/raw_data.csv', index=False)
        print("Данные успешно сохранены в data/raw_data.csv")
    else:
        print("Не удалось собрать данные для указанных валют.")


def preprocess_data():
    df = pd.read_csv('data/raw_data.csv')

    # Выбираем нужные столбцы для модели
    features = ['Open', 'High', 'Low', 'Close', 'Volume', 'SMA_10', 'SMA_30', 'EMA_10', 'EMA_30', 'RSI']

    # Разбиваем данные на обучающую и тестовую выборки, учитывая валюты
    grouped = df.groupby('Symbol')

    train_dfs = []
    test_dfs = []

    for symbol, group in grouped:
        group = group.reset_index(drop=True)
        train_size = int(len(group) * 0.8)
        train_df = group.iloc[:train_size]
        test_df = group.iloc[train_size:]

        train_dfs.append(train_df)
        test_dfs.append(test_df)

    train_df = pd.concat(train_dfs, ignore_index=True)
    test_df = pd.concat(test_dfs, ignore_index=True)

    # Нормализация данных
    from sklearn.preprocessing import StandardScaler
    scaler = StandardScaler()
    scaled_train_features = scaler.fit_transform(train_df[features])
    scaled_test_features = scaler.transform(test_df[features])

    # Сохраняем скейлер
    import joblib
    joblib.dump(scaler, 'models/scaler.gz')

    # Создаём DataFrames с нормализованными данными
    scaled_train_df = pd.DataFrame(scaled_train_features, columns=features)
    scaled_train_df['Symbol'] = train_df['Symbol'].values
    scaled_test_df = pd.DataFrame(scaled_test_features, columns=features)
    scaled_test_df['Symbol'] = test_df['Symbol'].values

    # Сохраняем данные
    scaled_train_df.to_csv('data/train_data.csv', index=False)
    scaled_test_df.to_csv('data/test_data.csv', index=False)
    print("Данные успешно разбиты на train_data.csv и test_data.csv")


if __name__ == "__main__":
    start_date_str = '2021-09-16'  # Задайте дату начала в формате YYYY-MM-DD
    end_date_str = '2023-09-17'  # Задайте дату окончания в формате YYYY-MM-DD
    symbols = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT']  # Укажите требуемые валютные пары
    print(f"Сбор данных с {start_date_str} по {end_date_str} для валют: {symbols}")
    collect_data(start_date_str, end_date_str, symbols)
    print("Предобработка данных")
    preprocess_data()

```


model.py
```py

import torch
import torch.nn as nn

class BitcoinPricePredictor(nn.Module):
    def __init__(self, input_size, hidden_size=128, num_layers=2, dropout=0.2):
        super(BitcoinPricePredictor, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers

        # LSTM слои с Dropout
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout)

        # Dropout слой
        self.dropout = nn.Dropout(dropout)

        # Полносвязный слой
        self.fc = nn.Linear(hidden_size, 1)

    def forward(self, x):
        # Инициализируем скрытые состояния
        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
        c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)

        # Пропускаем через LSTM
        out, _ = self.lstm(x, (h0, c0))

        # Берём выход последнего временного шага
        out = out[:, -1, :]

        # Применяем Dropout
        out = self.dropout(out)

        # Полносвязный слой
        out = self.fc(out)

        return out

```


test.py
```py

# test.py

import torch
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from model import BitcoinPricePredictor
import joblib

# Определение датасета для тестирования
class BitcoinTestDataset:
    def __init__(self, data, sequence_length=200):
        self.sequence_length = sequence_length
        self.data = data.reset_index(drop=True)
        self.symbol_groups = self.data.groupby('Symbol')

        self.sequences = []
        for symbol, group in self.symbol_groups:
            group = group.reset_index(drop=True)
            for i in range(len(group) - sequence_length - 1):
                seq = group.iloc[i:i + sequence_length][group.columns.difference(['Symbol'])].values
                target = group.iloc[i + sequence_length]['Close']
                self.sequences.append((symbol, seq, target))

    def get_random_sequence(self):
        idx = np.random.randint(0, len(self.sequences))
        symbol, seq, target = self.sequences[idx]
        return symbol, seq, target

def test():
    # Загружаем данные
    test_df = pd.read_csv('data/test_data.csv')

    # Загружаем скейлер
    scaler = joblib.load('models/scaler.gz')

    # Переносим данные в исходный масштаб для визуализации
    test_df_scaled = pd.DataFrame(scaler.inverse_transform(test_df.drop(columns=['Symbol'])), columns=test_df.columns.drop('Symbol'))
    test_df_scaled['Symbol'] = test_df['Symbol'].values

    # Инициализируем модель
    input_size = test_df.shape[1] - 1  # Исключаем 'Symbol'
    model = BitcoinPricePredictor(input_size=input_size, hidden_size=128, dropout=0.2)
    model.load_state_dict(torch.load('models/best_model.pth'))
    model.eval()

    # Переносим модель на устройство (CPU или GPU)
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model.to(device)

    # Создаём датасет
    test_dataset = BitcoinTestDataset(test_df, sequence_length=200)

    # Получаем случайную последовательность
    symbol, seq, real_value = test_dataset.get_random_sequence()

    # Подготовка данных для модели
    seq_tensor = torch.tensor(seq, dtype=torch.float32).unsqueeze(0).to(device)

    # Предсказание
    with torch.no_grad():
        predicted_value = model(seq_tensor).cpu().item()

    # Обратное преобразование предсказанного значения
    close_index = test_df.columns.get_loc('Close')
    mean = scaler.mean_[close_index]
    std = np.sqrt(scaler.var_[close_index])

    predicted_value = predicted_value * std + mean
    real_value = real_value * std + mean

    # Подготовка данных для визуализации
    seq_denorm = seq[:, close_index] * std + mean

    print(f"Валюта: {symbol}")
    print(f"Предсказанное значение: {predicted_value:.2f} USD")
    print(f"Реальное значение: {real_value:.2f} USD")

    # Визуализация
    time_steps = np.arange(len(seq_denorm) + 1)

    # Построение графика
    plt.plot(time_steps[:-1], seq_denorm, color='blue', label='Входная последовательность')
    plt.plot(time_steps[-2:], [seq_denorm[-1], predicted_value], color='orange', marker='o', label='Предсказанный шаг')
    plt.plot(time_steps[-2:], [seq_denorm[-1], real_value], color='green', marker='o', label='Реальный шаг')

    plt.legend()
    plt.xlabel('Временные шаги')
    plt.ylabel(f'Цена {symbol} (USD)')
    plt.title('Предсказание цены')

    plt.show()

if __name__ == "__main__":
    test()

```


train.py
```py

# train.py

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import pandas as pd
from model import BitcoinPricePredictor

# Определение датасета
class BitcoinDataset(Dataset):
    def __init__(self, data, sequence_length=200):
        self.sequence_length = sequence_length
        self.data = data.reset_index(drop=True)
        self.symbol_groups = self.data.groupby('Symbol')

        self.sequences = []
        for symbol, group in self.symbol_groups:
            group = group.reset_index(drop=True)
            for i in range(len(group) - sequence_length):
                seq = group.iloc[i:i + sequence_length][group.columns.difference(['Symbol'])].values
                target = group.iloc[i + sequence_length]['Close']
                self.sequences.append((seq, target))

    def __len__(self):
        return len(self.sequences)

    def __getitem__(self, idx):
        seq, target = self.sequences[idx]
        return torch.tensor(seq, dtype=torch.float32), torch.tensor(target, dtype=torch.float32)

def train():
    # Загружаем данные
    train_df = pd.read_csv('data/train_data.csv')

    # Параметры
    sequence_length = 200
    batch_size = 64
    num_epochs = 8#50
    learning_rate = 0.001
    input_size = train_df.shape[1] - 1  # Исключаем столбец 'Symbol'

    # Создаём датасеты и загрузчики
    train_dataset = BitcoinDataset(train_df, sequence_length)
    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)

    # Данные для валидации
    val_df = pd.read_csv('data/test_data.csv')
    val_dataset = BitcoinDataset(val_df, sequence_length)
    val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)

    # Инициализируем модель, функцию потерь и оптимизатор
    model = BitcoinPricePredictor(input_size=input_size, hidden_size=128, dropout=0.2)
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

    # Переносим модель на устройство (CPU или GPU)
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    model.to(device)

    # Обучение модели с валидацией
    best_val_loss = float('inf')
    for epoch in range(num_epochs):
        model.train()
        epoch_loss = 0
        for sequences, targets in train_loader:
            sequences = sequences.to(device)
            targets = targets.to(device)

            # Прямой проход
            outputs = model(sequences)
            loss = criterion(outputs.squeeze(), targets)

            # Обратный проход и оптимизация
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            epoch_loss += loss.item()
        avg_loss = epoch_loss / len(train_loader)

        # Валидация
        model.eval()
        val_loss = 0
        with torch.no_grad():
            for sequences, targets in val_loader:
                sequences = sequences.to(device)
                targets = targets.to(device)
                outputs = model(sequences)
                loss = criterion(outputs.squeeze(), targets)
                val_loss += loss.item()
        avg_val_loss = val_loss / len(val_loader)

        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.6f}, Val Loss: {avg_val_loss:.6f}')

        # Сохраняем лучшую модель
        if avg_val_loss < best_val_loss:
            best_val_loss = avg_val_loss
            torch.save(model.state_dict(), 'models/best_model.pth')

    print("Лучшие веса модели сохранены в models/best_model.pth")

if __name__ == "__main__":
    train()

```


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