Download scripts/train_rnn_model.py from amos-fernandes/DeepFin: direct link, hf CLI and curl.
- Browser
- Download file 17.7 kB
-
https://huggingface.co/amos-fernandes/DeepFin/resolve/main/scripts/train_rnn_model.py
- Command line
-
hf download hf://amos-fernandes/DeepFin/scripts/train_rnn_model.py
-
curl -L -o train_rnn_model.py https://huggingface.co/amos-fernandes/DeepFin/resolve/main/scripts/train_rnn_model.py
17.7 kB
| # train_rnn_model.py | |
| import os | |
| import numpy as np | |
| import pandas as pd | |
| import tensorflow as tf | |
| from sklearn.model_selection import train_test_split | |
| from sklearn.preprocessing import MinMaxScaler | |
| import joblib | |
| import matplotlib.pyplot as plt | |
| from datetime import timezone # Adicionado para datetime.now(timezone.utc) | |
| # Importar configurações e módulos | |
| from config import ( | |
| NUM_FEATURES, SYMBOL, TIMEFRAME, DAYS_OF_DATA_TO_FETCH, LIMIT_PER_FETCH, | |
| WINDOW_SIZE, BASE_FEATURE_COLS, # NUM_FEATURES é derivado no model_builder | |
| PREDICTION_HORIZON, PRICE_CHANGE_THRESHOLD, | |
| EPOCHS, BATCH_SIZE, # LSTM_UNITS, DENSE_UNITS, etc., são usados em model_builder | |
| MODEL_SAVE_DIR, MODEL_NAME, | |
| PRICE_VOL_SCALER_NAME, INDICATOR_SCALER_NAME, PRICE_VOL_SCALER_NAME, INDICATOR_SCALER_NAME, # Nomes dos arquivos | |
| EXPECTED_SCALED_FEATURES_FOR_MODEL, EXPECTED_FEATURES_ORDER # Usaremos esta para as colunas de entrada das sequências | |
| ) | |
| # Note: LEARNING_RATE é usado em model_builder, não precisa importar aqui diretamente se não for para os callbacks. | |
| from data_handler import fetch_ohlcv_data_ccxt, calculate_technical_indicators, calculate_targets, create_sequences | |
| from model_builder import build_lstm_model | |
| # Para métricas de classificação | |
| from sklearn.metrics import classification_report, confusion_matrix | |
| from sklearn.utils.class_weight import compute_class_weight | |
| def main(): | |
| print("Iniciando script de treinamento da RNN...") | |
| os.makedirs(MODEL_SAVE_DIR, exist_ok=True) | |
| # --- 1. Obter e Pré-processar Dados --- | |
| try: | |
| ohlcv_df_raw = fetch_ohlcv_data_ccxt(SYMBOL, TIMEFRAME, DAYS_OF_DATA_TO_FETCH, LIMIT_PER_FETCH) | |
| except Exception as e: | |
| print(f"Falha ao buscar dados com CCXT: {e}") | |
| # ... (lógica de fallback para CSV como antes) ... | |
| return | |
| if ohlcv_df_raw.empty: print("DataFrame de dados raw está vazio."); return | |
| ohlcv_df_with_ta = calculate_technical_indicators(ohlcv_df_raw) | |
| if ohlcv_df_with_ta.empty: print("DataFrame vazio após cálculo de indicadores."); return | |
| ohlcv_df_final_features = calculate_targets(ohlcv_df_with_ta, PREDICTION_HORIZON, PRICE_CHANGE_THRESHOLD) | |
| if ohlcv_df_final_features.empty: print("DataFrame vazio após cálculo de alvos."); return | |
| # --- 2. Salvar Scalers para API (Fitados nas Colunas Base) --- | |
| api_price_vol_atr_cols = [ # Features que já estão normalizadas ou são valores de preço/vol normalizados | |
| 'open_div_atr', 'high_div_atr', 'low_div_atr', 'close_div_atr', | |
| 'volume_div_atr', 'body_size_norm_atr' | |
| ] | |
| # Garante que só pegamos o que está em BASE_FEATURE_COLS | |
| api_price_vol_cols = [col for col in api_price_vol_atr_cols if col in BASE_FEATURE_COLS] | |
| api_indicator_cols = [ # Features que são indicadores brutos ou taxas | |
| 'log_return_1', 'rsi_14', 'atr', 'bbp', 'cci_37', 'mfi_37', | |
| 'body_vs_avg_body', 'macd', 'sma_10_div_atr' | |
| ] | |
| # Garante que só pegamos o que está em BASE_FEATURE_COLS e não está no outro grupo | |
| api_indicator_cols = [col for col in api_indicator_cols if col in BASE_FEATURE_COLS and col not in api_price_vol_cols] | |
| print("Preparando e salvando scalers para a API...") | |
| # Garantir que todas as colunas de BASE_FEATURE_COLS existem ANTES de tentar fitar os scalers | |
| missing_base_for_api_scalers = [col for col in BASE_FEATURE_COLS if col not in ohlcv_df_final_features.columns] | |
| if missing_base_for_api_scalers: | |
| print(f"ERRO: Colunas de BASE_FEATURE_COLS ({missing_base_for_api_scalers}) não encontradas para fitar scalers da API.") | |
| print(f"Disponíveis: {ohlcv_df_final_features.columns.tolist()}") | |
| return | |
| api_price_vol_cols = [col for col in BASE_FEATURE_COLS if 'close_div_atr' in col or 'volume_div_atr' in col] | |
| api_indicator_cols = [col for col in BASE_FEATURE_COLS if col not in api_price_vol_cols] | |
| if api_price_vol_cols: | |
| price_volume_scaler_api = MinMaxScaler() | |
| price_volume_scaler_api.fit(ohlcv_df_final_features[api_price_vol_cols]) | |
| joblib.dump(price_volume_scaler_api, os.path.join(MODEL_SAVE_DIR, PRICE_VOL_SCALER_NAME)) | |
| print(f"Scaler de Preço/Volume (API: {api_price_vol_cols}) salvo.") | |
| else: | |
| print("Aviso: Nenhuma coluna de preço/volume definida para o scaler da API.") | |
| if api_indicator_cols: | |
| indicator_scaler_api = MinMaxScaler() | |
| indicator_scaler_api.fit(ohlcv_df_final_features[api_indicator_cols]) | |
| joblib.dump(indicator_scaler_api, os.path.join(MODEL_SAVE_DIR, INDICATOR_SCALER_NAME)) | |
| print(f"Scaler de Indicadores (API: {api_indicator_cols}) salvo.") | |
| else: | |
| print("Aviso: Nenhuma coluna de indicador definida para o scaler da API.") | |
| # --- 3. Escalonar TODAS as Features para Treinamento do Modelo Atual --- | |
| # Este scaler é apenas para o processo de treinamento aqui. | |
| # Ele é treinado em TODAS as BASE_FEATURE_COLS juntas. | |
| print(f"Escalonando features para treinamento (colunas: {BASE_FEATURE_COLS})...") | |
| training_features_df = ohlcv_df_final_features[BASE_FEATURE_COLS].copy() | |
| general_training_scaler = MinMaxScaler() | |
| scaled_values_for_training = general_training_scaler.fit_transform(training_features_df) | |
| # DataFrame com colunas escaladas, ex: 'close_div_atr_scaled', 'rsi_14_scaled' | |
| # Usa EXPECTED_SCALED_FEATURES_FOR_MODEL do config.py | |
| df_scaled_for_sequences = pd.DataFrame( | |
| scaled_values_for_training, | |
| columns=EXPECTED_SCALED_FEATURES_FOR_MODEL, | |
| index=training_features_df.index | |
| ) | |
| # Juntar o target de volta | |
| df_for_sequences = df_scaled_for_sequences.join(ohlcv_df_final_features[['target']]) | |
| df_for_sequences.dropna(inplace=True) # Se o join criar NaNs (improvável) | |
| if df_for_sequences.empty: print("DataFrame para sequências vazio após escalonamento/join."); return | |
| # --- 4. Criar Sequências --- | |
| # As colunas para as sequências são as EXPECTED_SCALED_FEATURES_FOR_MODEL | |
| X, y = create_sequences(df_for_sequences, "target", WINDOW_SIZE, EXPECTED_SCALED_FEATURES_FOR_MODEL) | |
| if X.shape[0] == 0: print("Nenhuma sequência criada."); return | |
| print("Preparando, fitando e salvando scalers...") | |
| os.makedirs(MODEL_SAVE_DIR, exist_ok=True) | |
| # Garantir que todas as colunas de BASE_FEATURE_COLS existem em ohlcv_df_final_features | |
| missing_base_cols = [col for col in BASE_FEATURE_COLS if col not in ohlcv_df_final_features.columns] | |
| if missing_base_cols: | |
| print(f"ERRO FATAL: Colunas de BASE_FEATURE_COLS ({missing_base_cols}) não encontradas em ohlcv_df_final_features.") | |
| return | |
| # Definir quais colunas de BASE_FEATURE_COLS vão para cada scaler | |
| # Estas são as colunas que representam valores normalizados pelo ATR (preço, volume, corpo) | |
| price_vol_atr_norm_cols = [ | |
| 'open_div_atr', 'high_div_atr', 'low_div_atr', 'close_div_atr', | |
| 'volume_div_atr', 'body_size_norm_atr' | |
| ] | |
| # Remover colunas que podem não existir se você não as adicionou a BASE_FEATURE_COLS | |
| price_vol_atr_norm_cols = [col for col in price_vol_atr_norm_cols if col in BASE_FEATURE_COLS] | |
| # As colunas restantes de BASE_FEATURE_COLS são os "outros indicadores" | |
| other_indicator_cols = [col for col in BASE_FEATURE_COLS if col not in price_vol_atr_norm_cols] | |
| # DataFrames para fitar os scalers | |
| df_for_pv_scaler = ohlcv_df_final_features[price_vol_atr_norm_cols].copy() | |
| df_for_ind_scaler = ohlcv_df_final_features[other_indicator_cols].copy() | |
| # Fitar e Salvar o Price/Volume (ATR Normalized) Scaler | |
| if not df_for_pv_scaler.empty: | |
| pv_atr_scaler = MinMaxScaler() | |
| pv_atr_scaler.fit(df_for_pv_scaler) | |
| joblib.dump(pv_atr_scaler, os.path.join(MODEL_SAVE_DIR, PRICE_VOL_SCALER_NAME)) | |
| print(f"Scaler de Preço/Volume (ATR Norm) (API: {price_vol_atr_norm_cols}) salvo.") | |
| # Transformar os dados para o treinamento com este scaler | |
| scaled_pv_data = pv_atr_scaler.transform(df_for_pv_scaler) | |
| else: | |
| print(f"AVISO: Sem dados para o scaler de Preço/Volume (ATR Norm) ({price_vol_atr_norm_cols}).") | |
| scaled_pv_data = pd.DataFrame() # DataFrame vazio para evitar erro de concatenação | |
| # Fitar e Salvar o Other Indicators Scaler | |
| if not df_for_ind_scaler.empty: | |
| other_ind_scaler = MinMaxScaler() | |
| other_ind_scaler.fit(df_for_ind_scaler) | |
| joblib.dump(other_ind_scaler, os.path.join(MODEL_SAVE_DIR, INDICATOR_SCALER_NAME)) | |
| print(f"Scaler de Outros Indicadores (API: {other_indicator_cols}) salvo.") | |
| # Transformar os dados para o treinamento com este scaler | |
| scaled_other_ind_data = other_ind_scaler.transform(df_for_ind_scaler) | |
| else: | |
| print(f"AVISO: Sem dados para o scaler de Outros Indicadores ({other_indicator_cols}).") | |
| scaled_other_ind_data = pd.DataFrame() | |
| # --- Montar o DataFrame com TODAS as features escaladas para o treinamento --- | |
| # As colunas devem estar na ORDEM DE EXPECTED_SCALED_FEATURES_FOR_MODEL | |
| # (que é derivada da ordem de BASE_FEATURE_COLS) | |
| df_scaled_for_sequences = pd.DataFrame(index=ohlcv_df_final_features.index) | |
| # Adicionar colunas escaladas de preço/volume | |
| if not df_for_pv_scaler.empty: | |
| for i, col_name in enumerate(price_vol_atr_norm_cols): | |
| df_scaled_for_sequences[f"{col_name}_scaled"] = scaled_pv_data[:, i] | |
| # Adicionar colunas escaladas de outros indicadores | |
| if not df_for_ind_scaler.empty: | |
| for i, col_name in enumerate(other_indicator_cols): | |
| df_scaled_for_sequences[f"{col_name}_scaled"] = scaled_other_ind_data[:, i] | |
| # Verificar se todas as colunas escaladas esperadas foram criadas | |
| # `EXPECTED_SCALED_FEATURES_FOR_MODEL` vem do config.py | |
| missing_scaled_cols = [col for col in EXPECTED_SCALED_FEATURES_FOR_MODEL if col not in df_scaled_for_sequences.columns] | |
| if missing_scaled_cols: | |
| print(f"ERRO FATAL: Colunas escaladas esperadas ({missing_scaled_cols}) não foram criadas para as sequências.") | |
| print(f"Colunas escaladas disponíveis: {df_scaled_for_sequences.columns.tolist()}") | |
| return | |
| # Reordenar colunas para garantir a ordem de EXPECTED_SCALED_FEATURES_FOR_MODEL | |
| df_scaled_for_sequences = df_scaled_for_sequences[EXPECTED_SCALED_FEATURES_FOR_MODEL] | |
| # Juntar o target de volta | |
| df_for_sequences = df_scaled_for_sequences.join(ohlcv_df_final_features[['target']]) | |
| df_for_sequences.dropna(inplace=True) | |
| if df_for_sequences.empty: print("DataFrame para sequências vazio após escalonamento/join."); return | |
| # --- 5. Dividir Dados --- | |
| X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) | |
| print(f"Treino: {X_train.shape[0]} amostras, Teste: {X_test.shape[0]} amostras.") | |
| X, y = create_sequences(df_for_sequences, "target", WINDOW_SIZE, EXPECTED_SCALED_FEATURES_FOR_MODEL) | |
| if X.shape[0] == 0: print("Nenhuma sequência criada."); return | |
| # --- 6. Dividir Dados --- | |
| X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) | |
| print(f"Treino: {X_train.shape[0]} amostras, Teste: {X_test.shape[0]} amostras.") | |
| # --- 7. Construir e Treinar Modelo --- | |
| print("Construindo modelo LSTM...") | |
| # NUM_FEATURES é importado de config.py e deve ser len(BASE_FEATURE_COLS) | |
| # EXPECTED_SCALED_FEATURES_FOR_MODEL também é de config.py e é [f"{col}_scaled" for col in BASE_FEATURE_COLS] | |
| # O número de colunas em X_train (X_train.shape[2]) DEVE ser igual a NUM_FEATURES. | |
| # E NUM_FEATURES DEVE ser igual a len(EXPECTED_SCALED_FEATURES_FOR_MODEL). | |
| actual_num_features = NUM_FEATURES # Se estiverem consistentes | |
| model_input_shape = (WINDOW_SIZE, actual_num_features) | |
| model = build_lstm_model(model_input_shape) # model_builder.py usa LEARNING_RATE do config | |
| print("Iniciando treinamento do modelo...") | |
| reduce_lr_cb = tf.keras.callbacks.ReduceLROnPlateau( | |
| monitor='val_loss', | |
| factor=0.5, # Reduz pela metade, um pouco menos agressivo que 0.2 | |
| patience=7, | |
| min_lr=1e-7, # Pode ser 1e-6 também | |
| verbose=1 | |
| ) | |
| early_stopping_cb = tf.keras.callbacks.EarlyStopping( | |
| monitor='val_loss', | |
| patience=25, # Mantém sua paciência maior | |
| restore_best_weights=True # MUITO IMPORTANTE | |
| ) | |
| callbacks_list = [early_stopping_cb, reduce_lr_cb] | |
| class_weights_map = { | |
| 0: 0.4, # Diminui o peso da classe majoritária | |
| 1: 4.0 # Aumenta BASTANTE o peso da classe minoritária (Rise) | |
| } | |
| unique_classes, counts = np.unique(y_train, return_counts=True) | |
| print(f"Distribuição das classes no treino: {dict(zip(unique_classes, counts))}") | |
| class_weights_vals = compute_class_weight('balanced', classes=unique_classes, y=y_train) | |
| class_weights_map = {cls: weight for cls, weight in zip(unique_classes, class_weights_vals)} | |
| class_weights_map = {0: 0.5, 1: 3.5} | |
| print(f"Pesos de Classe: {class_weights_map}") | |
| """ class_weights_map = { | |
| 0: 0.4, # Diminui o peso da classe majoritária | |
| 1: 4.0 # Aumenta BASTANTE o peso da classe minoritária (Rise) | |
| } """ | |
| history = model.fit(X_train, y_train, epochs=EPOCHS, batch_size=BATCH_SIZE, | |
| validation_split=0.1, # Usa 10% do X_train/y_train para validação | |
| callbacks=callbacks_list, class_weight=class_weights_map, verbose=1) | |
| # --- 8. Avaliar o Modelo TREINADO --- | |
| # O modelo 'model' aqui é o treinado, com os melhores pesos restaurados pelo EarlyStopping | |
| print("Avaliando modelo treinado no conjunto de teste...") | |
| loss, accuracy_keras = model.evaluate(X_test, y_test, verbose=0) | |
| print(f"Perda no Teste (Keras): {loss:.4f}") | |
| print(f"Acurácia no Teste (Keras, thr=0.5): {accuracy_keras:.4f}") | |
| # Após model.evaluate() | |
| from sklearn.metrics import classification_report, confusion_matrix | |
| y_pred_probs = model.predict(X_test) | |
| y_pred_classes = (y_pred_probs > 0.65).astype(int) # 0.5, 0.6, 0.65, 0.7, 0.75 Valores de referencia par Thresholud | |
| print("\nRelatório de Classificação no Conjunto de Teste:") | |
| print(classification_report(y_test, y_pred_classes, target_names=['No Rise (0)', 'Rise (1)'])) | |
| print("\nMatriz de Confusão no Conjunto de Teste:") | |
| cm = confusion_matrix(y_test, y_pred_classes) | |
| print(cm) | |
| import seaborn as sns # Para um plot mais bonito da matriz | |
| plt.figure(figsize=(6,5)) | |
| sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=['No Rise', 'Rise'], yticklabels=['No Rise', 'Rise']) | |
| plt.xlabel('Predito') | |
| plt.ylabel('Verdadeiro') | |
| plt.title('Matriz de Confusão') | |
| plt.savefig(os.path.join(MODEL_SAVE_DIR, "confusion_matrix.png")) | |
| # --- 9. Salvar Modelo e Scalers --- | |
| print("Salvando modelo e scalers...") | |
| os.makedirs(MODEL_SAVE_DIR, exist_ok=True) # Cria o diretório se não existir | |
| model_path = os.path.join(MODEL_SAVE_DIR, MODEL_NAME) | |
| model.save(model_path) | |
| print(f"Modelo salvo em: {model_path}") | |
| # --- 9. Salvar o Modelo TREINADO --- | |
| # Não reconstrua o modelo aqui! Salve o que foi treinado. | |
| model_save_path = os.path.join(MODEL_SAVE_DIR, MODEL_NAME) | |
| model.save(model_save_path) | |
| print(f"Modelo TREINADO salvo em: {model_save_path}") | |
| # --- 10. Plotar Histórico de Treinamento (Opcional) --- | |
| plt.figure(figsize=(12, 6)) | |
| plt.subplot(1, 2, 1) | |
| plt.plot(history.history['accuracy'], label='Acurácia Treino') | |
| plt.plot(history.history['val_accuracy'], label='Acurácia Validação') | |
| plt.title('Acurácia do Modelo') | |
| plt.xlabel('Época') | |
| plt.ylabel('Acurácia') | |
| plt.legend() | |
| plt.subplot(1, 2, 2) | |
| plt.plot(history.history['loss'], label='Perda Treino') | |
| plt.plot(history.history['val_loss'], label='Perda Validação') | |
| plt.title('Perda do Modelo') | |
| plt.xlabel('Época') | |
| plt.ylabel('Perda') | |
| plt.legend() | |
| # Salvar o gráfico | |
| plot_path = os.path.join(MODEL_SAVE_DIR, "training_history.png") | |
| plt.savefig(plot_path) | |
| print(f"Gráfico do histórico de treinamento salvo em: {plot_path}") | |
| plt.show() # Descomente se quiser ver o gráfico imediatamente | |
| # --- Imprimindo rodade de Tresh | |
| print("\nAnálise com diferentes thresholds de predição no conjunto de teste:") | |
| y_pred_probs = model.predict(X_test) | |
| thresholds_to_test = [0.50, 0.55, 0.60, 0.65, 0.70, 0.75] | |
| for thresh in thresholds_to_test: | |
| print(f"\n--- Resultados com Threshold: {thresh:.2f} ---") | |
| y_pred_classes = (y_pred_probs > thresh).astype(int) | |
| # Use target_names se suas classes forem 0 e 1. Se y_test tiver outros valores, ajuste. | |
| # Supondo que 0 é 'No Rise' e 1 é 'Rise' | |
| print(classification_report(y_test, y_pred_classes, target_names=['No Rise (0)', 'Rise (1)'], zero_division=0)) | |
| print("Matriz de Confusão:") | |
| print(confusion_matrix(y_test, y_pred_classes)) | |
| print("Script de treinamento concluído.") | |
| if __name__ == "__main__": | |
| main() | |
| # Configurar GPU (Opcional, se tiver TensorFlow com suporte a GPU) | |
| print("Iniciando configuração de GPU") | |
| gpus = tf.config.experimental.list_physical_devices('GPU') | |
| if gpus: | |
| try: | |
| for gpu in gpus: | |
| tf.config.experimental.set_memory_growth(gpu, True) | |
| logical_gpus = tf.config.experimental.list_logical_devices('GPU') | |
| print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs") | |
| except RuntimeError as e: | |
| print(e) | |