|
|
|
|
|
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 pandas_ta as ta |
|
|
import joblib |
|
|
import matplotlib.pyplot as plt |
|
|
import ccxt |
|
|
from datetime import datetime, timedelta, timezone |
|
|
from tensorflow.keras import regularizers |
|
|
|
|
|
|
|
|
|
|
|
SYMBOL = 'BTC/USDT' |
|
|
TIMEFRAME = '1h' |
|
|
|
|
|
|
|
|
DAYS_OF_DATA_TO_FETCH = 365 * 2 |
|
|
LIMIT_PER_FETCH = 1000 |
|
|
|
|
|
|
|
|
WINDOW_SIZE = 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BASE_FEATURE_COLS = [ |
|
|
'close_div_atr', |
|
|
'volume_div_atr', |
|
|
|
|
|
'rsi_14', |
|
|
|
|
|
'atr', |
|
|
'bbp' ] |
|
|
|
|
|
|
|
|
NUM_FEATURES = len(BASE_FEATURE_COLS) |
|
|
|
|
|
|
|
|
|
|
|
PREDICTION_HORIZON = 5 |
|
|
PRICE_CHANGE_THRESHOLD = 0.005 |
|
|
|
|
|
|
|
|
LSTM_UNITS = [32,16] |
|
|
DENSE_UNITS = 16 |
|
|
DROPOUT_RATE = 0.1 |
|
|
LEARNING_RATE = 0.001 |
|
|
|
|
|
|
|
|
BATCH_SIZE = 64 |
|
|
EPOCHS = 100 |
|
|
|
|
|
|
|
|
MODEL_SAVE_DIR = "app/model" |
|
|
MODEL_NAME = "model.h5" |
|
|
PRICE_VOL_SCALER_NAME = "price_volume_scaler.joblib" |
|
|
INDICATOR_SCALER_NAME = "indicator_scaler.joblib" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_ohlcv_data_ccxt(symbol, timeframe, days_to_fetch, limit_per_call=1000): |
|
|
"""Busca dados OHLCV de uma exchange usando ccxt.""" |
|
|
exchange = ccxt.binance() |
|
|
print(f"Buscando dados para {symbol} na {exchange.id} com timeframe {timeframe}...") |
|
|
|
|
|
|
|
|
all_ohlcv = [] |
|
|
|
|
|
|
|
|
since_dt = datetime.now(timezone.utc) - timedelta(days=days_to_fetch) |
|
|
since = exchange.parse8601(since_dt.isoformat()) |
|
|
|
|
|
while True: |
|
|
try: |
|
|
print(f"Buscando {limit_per_call} candles desde {exchange.iso8601(since)}...") |
|
|
ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since, limit_per_call) |
|
|
if not ohlcv: break |
|
|
all_ohlcv.extend(ohlcv) |
|
|
last_timestamp_in_batch = ohlcv[-1][0] |
|
|
since = last_timestamp_in_batch + exchange.rateLimit |
|
|
print(f"Coletados {len(ohlcv)} candles. Último: {exchange.iso8601(last_timestamp_in_batch)}. Total: {len(all_ohlcv)}") |
|
|
if len(ohlcv) < limit_per_call: break |
|
|
|
|
|
if len(all_ohlcv) >= (days_to_fetch * 24) and last_timestamp_in_batch > exchange.parse8601(datetime.now(timezone.utc).isoformat()): |
|
|
break |
|
|
|
|
|
all_ohlcv.extend(ohlcv) |
|
|
last_timestamp_in_batch = ohlcv[-1][0] |
|
|
|
|
|
|
|
|
since = last_timestamp_in_batch + exchange.rateLimit |
|
|
|
|
|
print(f"Coletados {len(ohlcv)} candles. Último timestamp: {exchange.iso8601(last_timestamp_in_batch)}. Total: {len(all_ohlcv)}") |
|
|
|
|
|
|
|
|
if len(ohlcv) < limit_per_call: |
|
|
break |
|
|
if len(all_ohlcv) >= (days_to_fetch * (24 if timeframe.endswith('h') else 1440 if timeframe.endswith('m') else 1)): |
|
|
if all_ohlcv[-1][0] > exchange.parse8601(datetime.utcnow().isoformat()): |
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
except ccxt.NetworkError as e: |
|
|
print(f"Erro de rede CCXT: {e}. Tentando novamente em 5s...") |
|
|
|
|
|
except ccxt.ExchangeError as e: |
|
|
print(f"Erro da Exchange CCXT: {e}. Parando busca.") |
|
|
break |
|
|
except Exception as e: |
|
|
print(f"Erro inesperado: {e}. Parando busca.") |
|
|
break |
|
|
|
|
|
if not all_ohlcv: |
|
|
raise ValueError("Nenhum dado OHLCV foi coletado. Verifique o símbolo, timeframe ou a exchange.") |
|
|
|
|
|
df = pd.DataFrame(all_ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) |
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms') |
|
|
df.set_index('timestamp', inplace=True) |
|
|
print(f"Total de {len(df)} candles OHLCV coletados para {symbol}.") |
|
|
return df |
|
|
|
|
|
|
|
|
def calculate_targets(df, horizon=PREDICTION_HORIZON, threshold=PRICE_CHANGE_THRESHOLD): |
|
|
""" |
|
|
Cria o alvo da predição. |
|
|
Retorna 1 se o preço futuro (horizonte) subir mais que o threshold, 0 caso contrário. |
|
|
""" |
|
|
|
|
|
|
|
|
df['next_close'] = df['close'].shift(-1) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
df['next_open'] = df['open'].shift(-1) |
|
|
df['target_raw'] = df['next_close'] - df['next_open'] |
|
|
df['target'] = (df['target_raw'] > 0).astype(int) |
|
|
|
|
|
|
|
|
df.dropna(subset=['next_close', 'next_open', 'target'], inplace=True) |
|
|
return df |
|
|
|
|
|
|
|
|
""" df['future_price'] = df['close'].shift(-horizon) |
|
|
df['price_change_pct'] = (df['future_price'] - df['close']) / df['close'] |
|
|
df['target'] = (df['price_change_pct'] > threshold).astype(int) |
|
|
df.dropna(inplace=True) # Remove future_price é NaN |
|
|
return df """ |
|
|
|
|
|
|
|
|
def create_sequences(data, target_col, window_size, feature_cols): |
|
|
"""Cria sequências de dados e seus alvos correspondentes.""" |
|
|
X, y = [], [] |
|
|
|
|
|
data_values = data[feature_cols].values |
|
|
target_values = data[target_col].values |
|
|
|
|
|
for i in range(len(data_values) - window_size): |
|
|
X.append(data_values[i:(i + window_size)]) |
|
|
y.append(target_values[i + window_size -1]) |
|
|
|
|
|
|
|
|
return np.array(X), np.array(y) |
|
|
|
|
|
|
|
|
def build_lstm_model(input_shape, lstm_units, dense_units, dropout_rate, initial_learning_rate): |
|
|
"""Constrói o modelo LSTM com regularização L2.""" |
|
|
|
|
|
|
|
|
model = tf.keras.Sequential() |
|
|
|
|
|
L2_REG = 0.0001 |
|
|
|
|
|
|
|
|
for i, units in enumerate(lstm_units): |
|
|
return_sequences = True if i < len(lstm_units) - 1 else False |
|
|
if i == 0: |
|
|
model.add(tf.keras.layers.LSTM(units, |
|
|
return_sequences=return_sequences, |
|
|
input_shape=input_shape, |
|
|
kernel_regularizer=regularizers.l2(L2_REG) |
|
|
)) |
|
|
else: |
|
|
model.add(tf.keras.layers.LSTM(units, |
|
|
return_sequences=return_sequences, |
|
|
kernel_regularizer=regularizers.l2(L2_REG) |
|
|
|
|
|
)) |
|
|
model.add(tf.keras.layers.Dropout(dropout_rate)) |
|
|
|
|
|
|
|
|
model.add(tf.keras.layers.Dense(dense_units, |
|
|
activation='relu', |
|
|
kernel_regularizer=regularizers.l2(L2_REG) |
|
|
)) |
|
|
model.add(tf.keras.layers.Dropout(dropout_rate)) |
|
|
|
|
|
|
|
|
model.add(tf.keras.layers.Dense(1, activation='sigmoid')) |
|
|
|
|
|
optimizer = tf.keras.optimizers.Adam(learning_rate=initial_learning_rate ,amsgrad=True, clipvalue=1.0) |
|
|
model.compile(optimizer=optimizer, loss='binary_crossentropy', metrics=['accuracy']) |
|
|
model.summary() |
|
|
return model |
|
|
|
|
|
|
|
|
def main(): |
|
|
print("Iniciando script de treinamento da RNN...") |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
ohlcv_df = 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}") |
|
|
print("Verifique se você tem um arquivo 'data.csv' no mesmo diretório para fallback.") |
|
|
|
|
|
try: |
|
|
ohlcv_df = pd.read_csv("data.csv", index_col='timestamp', parse_dates=True) |
|
|
print("Dados carregados de data.csv") |
|
|
except FileNotFoundError: |
|
|
print("Erro: data.csv não encontrado. Forneça dados para treinamento.") |
|
|
return |
|
|
|
|
|
if ohlcv_df.empty: |
|
|
print("DataFrame de dados está vazio. Encerrando.") |
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(f"Shape original do ohlcv_df: {ohlcv_df.shape}") |
|
|
if not ohlcv_df.index.is_unique: |
|
|
print("AVISO: Timestamps duplicados encontrados no índice. Removendo duplicatas (mantendo a primeira ocorrência)...") |
|
|
|
|
|
ohlcv_df = ohlcv_df[~ohlcv_df.index.duplicated(keep='first')] |
|
|
|
|
|
|
|
|
|
|
|
print(f"Shape do ohlcv_df após remover duplicatas: {ohlcv_df.shape}") |
|
|
|
|
|
|
|
|
ohlcv_df.sort_index(inplace=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(f"Shape original do ohlcv_df: {ohlcv_df.shape}") |
|
|
|
|
|
|
|
|
if isinstance(ohlcv_df.index, pd.DatetimeIndex): |
|
|
ohlcv_df.reset_index(inplace=True) |
|
|
|
|
|
|
|
|
|
|
|
if 'index' in ohlcv_df.columns and pd.api.types.is_datetime64_any_dtype(ohlcv_df['index']): |
|
|
ohlcv_df.rename(columns={'index': 'ts_temp'}, inplace=True) |
|
|
timestamp_col_name = 'ts_temp' |
|
|
elif 'timestamp' in ohlcv_df.columns and pd.api.types.is_datetime64_any_dtype(ohlcv_df['timestamp']): |
|
|
|
|
|
ohlcv_df.rename(columns={'timestamp': 'ts_temp'}, inplace=True) |
|
|
timestamp_col_name = 'ts_temp' |
|
|
else: |
|
|
print("ERRO: Não foi possível identificar a coluna de timestamp após reset_index.") |
|
|
return |
|
|
|
|
|
|
|
|
initial_rows = len(ohlcv_df) |
|
|
ohlcv_df.drop_duplicates(subset=[timestamp_col_name], keep='first', inplace=True) |
|
|
rows_removed = initial_rows - len(ohlcv_df) |
|
|
if rows_removed > 0: |
|
|
print(f"AVISO: Removidas {rows_removed} linhas com timestamps duplicados (coluna '{timestamp_col_name}').") |
|
|
|
|
|
|
|
|
ohlcv_df.set_index(timestamp_col_name, inplace=True) |
|
|
ohlcv_df.index.name = 'timestamp' |
|
|
|
|
|
|
|
|
ohlcv_df.sort_index(inplace=True) |
|
|
|
|
|
|
|
|
if not ohlcv_df.index.is_unique: |
|
|
print("ERRO CRÍTICO: O índice ainda não é único após a limpeza. Algo está errado.") |
|
|
|
|
|
return |
|
|
else: |
|
|
print(f"Índice agora é único. Shape do ohlcv_df após limpeza: {ohlcv_df.shape}") |
|
|
|
|
|
|
|
|
|
|
|
print("Calculando indicadores técnicos...") |
|
|
if ta: |
|
|
ohlcv_df.ta.sma(length=10, close='close', append=True, col_names=('sma_10',)) |
|
|
ohlcv_df.ta.rsi(length=14, close='close', append=True, col_names=('rsi_14',)) |
|
|
|
|
|
ohlcv_df.ta.macd(close='close', append=True, col_names=('macd', 'macdh', 'macds')) |
|
|
ohlcv_df.ta.atr(length=14, append=True, col_names=('atr',)) |
|
|
ohlcv_df.ta.bbands(length=20, close='close', append=True, col_names=('bbl', 'bbm', 'bbu', 'bbb', 'bbp')) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ohlcv_df['close_div_atr'] = ohlcv_df['close'] / (ohlcv_df['atr'] + 1e-7) |
|
|
ohlcv_df['volume_div_atr'] = ohlcv_df['volume'] / (ohlcv_df['atr'] + 1e-7) |
|
|
|
|
|
ohlcv_df['sma_10_div_atr'] = ohlcv_df['sma_10'] / (ohlcv_df['atr'] + 1e-7) |
|
|
ohlcv_df['macd_div_atr'] = ohlcv_df['macd'] / (ohlcv_df['atr'] + 1e-7) |
|
|
|
|
|
|
|
|
ohlcv_df.dropna(inplace=True) |
|
|
|
|
|
|
|
|
print("Criando coluna alvo para predição...") |
|
|
ohlcv_df_with_target = calculate_targets(ohlcv_df.copy(), PREDICTION_HORIZON, PRICE_CHANGE_THRESHOLD) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
api_price_vol_cols = ['close_div_atr', 'volume_div_atr'] |
|
|
if all(col in ohlcv_df_with_target.columns for col in api_price_vol_cols): |
|
|
price_volume_scaler_to_save = MinMaxScaler() |
|
|
price_volume_scaler_to_save.fit(ohlcv_df_with_target[api_price_vol_cols]) |
|
|
joblib.dump(price_volume_scaler_to_save, os.path.join(MODEL_SAVE_DIR, PRICE_VOL_SCALER_NAME)) |
|
|
print(f"Scaler de Preço/Volume (API: {api_price_vol_cols}) salvo em: {os.path.join(MODEL_SAVE_DIR, PRICE_VOL_SCALER_NAME)}") |
|
|
else: |
|
|
print(f"ERRO: Colunas para salvar price_volume_scaler ({api_price_vol_cols}) não encontradas em ohlcv_df_with_target.") |
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BASE_FEATURE_COLS = ['close_div_atr', 'volume_div_atr', 'sma_10_div_atr', 'rsi_14', 'macd_div_atr', 'atr', 'bbp'] |
|
|
indicator_cols_for_api_scaler = ['sma_10_div_atr', 'rsi_14', 'macd_div_atr', 'atr', 'bbp'] |
|
|
|
|
|
|
|
|
if indicator_cols_for_api_scaler and all(col in ohlcv_df_with_target.columns for col in indicator_cols_for_api_scaler): |
|
|
indicator_data_to_fit_api_scaler = ohlcv_df_with_target[indicator_cols_for_api_scaler].copy() |
|
|
indicator_data_to_fit_api_scaler.dropna(subset=indicator_cols_for_api_scaler, inplace=True) |
|
|
|
|
|
if not indicator_data_to_fit_api_scaler.empty: |
|
|
indicator_scaler_to_save = MinMaxScaler() |
|
|
indicator_scaler_to_save.fit(indicator_data_to_fit_api_scaler) |
|
|
joblib.dump(indicator_scaler_to_save, os.path.join(MODEL_SAVE_DIR, INDICATOR_SCALER_NAME)) |
|
|
print(f"Scaler de Indicadores (API: {indicator_cols_for_api_scaler}) salvo em: {os.path.join(MODEL_SAVE_DIR, INDICATOR_SCALER_NAME)}") |
|
|
else: |
|
|
print(f"ERRO: Não foi possível treinar/salvar o scaler de indicadores (API: {indicator_cols_for_api_scaler}) devido a dados vazios.") |
|
|
return |
|
|
elif not indicator_cols_for_api_scaler: |
|
|
print("Nenhuma coluna de indicador definida para salvar o indicator_scaler para API.") |
|
|
else: |
|
|
missing_cols = [col for col in indicator_cols_for_api_scaler if col not in ohlcv_df_with_target.columns] |
|
|
print(f"ERRO: Colunas para salvar indicator_scaler ({indicator_cols_for_api_scaler}) não encontradas ou lista vazia. Ausentes: {missing_cols}") |
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print("Escalonando features para criação de sequências (usando as colunas de BASE_FEATURE_COLS)...") |
|
|
|
|
|
|
|
|
if not all(col in ohlcv_df_with_target.columns for col in BASE_FEATURE_COLS): |
|
|
missing_final_base_cols = [col for col in BASE_FEATURE_COLS if col not in ohlcv_df_with_target.columns] |
|
|
print(f"ERRO: Colunas finais em BASE_FEATURE_COLS ausentes em ohlcv_df_with_target: {missing_final_base_cols}") |
|
|
print(f"Colunas disponíveis: {ohlcv_df_with_target.columns.tolist()}") |
|
|
return |
|
|
|
|
|
features_to_scale_df = ohlcv_df_with_target[BASE_FEATURE_COLS].copy() |
|
|
|
|
|
|
|
|
general_training_scaler = MinMaxScaler() |
|
|
scaled_features_values = general_training_scaler.fit_transform(features_to_scale_df) |
|
|
|
|
|
scaled_features_df = pd.DataFrame(scaled_features_values, |
|
|
columns=[f"{col}_scaled" for col in BASE_FEATURE_COLS], |
|
|
index=features_to_scale_df.index) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ohlcv_df_with_target.empty: |
|
|
print("DataFrame vazio após cálculo do alvo. Verifique os parâmetros de horizonte/threshold. Encerrando.") |
|
|
return |
|
|
|
|
|
print(f"Distribuição do Alvo:\n{ohlcv_df_with_target['target'].value_counts(normalize=True)}") |
|
|
pv_cols_for_scaling = ['close_div_atr', 'volume_div_atr'] |
|
|
if all(col in ohlcv_df_with_target.columns for col in pv_cols_for_scaling): |
|
|
price_volume_scaler = MinMaxScaler() |
|
|
price_volume_data_original = ohlcv_df_with_target[pv_cols_for_scaling].copy() |
|
|
price_volume_scaler.fit(price_volume_data_original) |
|
|
joblib.dump(price_volume_scaler, os.path.join(MODEL_SAVE_DIR, PRICE_VOL_SCALER_NAME)) |
|
|
print(f"Scaler de Preço/Volume salvo em: {os.path.join(MODEL_SAVE_DIR, PRICE_VOL_SCALER_NAME)}") |
|
|
else: |
|
|
print(f"Aviso: Colunas para price_volume_scaler não encontradas: {pv_cols_for_scaling}") |
|
|
|
|
|
|
|
|
indicator_cols_for_scaling = [col for col in BASE_FEATURE_COLS if col not in ['close_div_atr', 'volume_div_atr']] |
|
|
|
|
|
if indicator_cols_for_scaling and all(col in ohlcv_df_with_target.columns for col in indicator_cols_for_scaling): |
|
|
indicator_data_original = ohlcv_df_with_target[indicator_cols_for_scaling].copy() |
|
|
indicator_data_original.dropna(inplace=True) |
|
|
|
|
|
if not indicator_data_original.empty: |
|
|
indicator_scaler_instance = MinMaxScaler() |
|
|
indicator_scaler_instance.fit(indicator_data_original) |
|
|
joblib.dump(indicator_scaler_instance, os.path.join(MODEL_SAVE_DIR, INDICATOR_SCALER_NAME)) |
|
|
print(f"Scaler de Indicadores ({indicator_cols_for_scaling}) salvo em: {os.path.join(MODEL_SAVE_DIR, INDICATOR_SCALER_NAME)}") |
|
|
else: |
|
|
print(f"Não foi possível treinar/salvar o scaler de indicadores ({indicator_cols_for_scaling}) devido a dados vazios após dropna.") |
|
|
elif not indicator_cols_for_scaling: |
|
|
print("Nenhuma coluna de indicador definida para o indicator_scaler.") |
|
|
else: |
|
|
print(f"Aviso: Algumas colunas para indicator_scaler não encontradas: {indicator_cols_for_scaling}") |
|
|
|
|
|
|
|
|
missing_base_cols = [col for col in BASE_FEATURE_COLS if col not in ohlcv_df.columns] |
|
|
if missing_base_cols: |
|
|
print(f"Erro: Colunas base para features ausentes após cálculo de indicadores: {missing_base_cols}") |
|
|
print(f"Colunas disponíveis: {ohlcv_df.columns.tolist()}") |
|
|
print(f"Verifique BASE_FEATURE_COLS e a saída dos indicadores.") |
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
print("Escalonando features...") |
|
|
|
|
|
features_to_scale_df = ohlcv_df_with_target[BASE_FEATURE_COLS].copy() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scaler = MinMaxScaler() |
|
|
scaled_features_values = scaler.fit_transform(features_to_scale_df) |
|
|
|
|
|
|
|
|
scaled_features_df = pd.DataFrame(scaled_features_values, |
|
|
columns=[f"{col}_scaled" for col in BASE_FEATURE_COLS], |
|
|
index=features_to_scale_df.index) |
|
|
|
|
|
|
|
|
|
|
|
data_for_sequences = scaled_features_df.join(ohlcv_df_with_target[['target']]) |
|
|
data_for_sequences.dropna(inplace=True) |
|
|
|
|
|
if data_for_sequences.empty: |
|
|
print("DataFrame vazio após escalonamento e join com target. Encerrando.") |
|
|
return |
|
|
|
|
|
|
|
|
sequence_feature_cols = [f"{col}_scaled" for col in BASE_FEATURE_COLS] |
|
|
|
|
|
|
|
|
print("Criando sequências de dados...") |
|
|
X, y = create_sequences(data_for_sequences, 'target', WINDOW_SIZE, sequence_feature_cols) |
|
|
|
|
|
if X.shape[0] == 0: |
|
|
print("Nenhuma sequência foi criada. Verifique o tamanho dos dados e WINDOW_SIZE. Encerrando.") |
|
|
return |
|
|
print(f"Shape de X (sequências de entrada): {X.shape}") |
|
|
print(f"Shape de y (alvos): {y.shape}") |
|
|
|
|
|
|
|
|
print("Dividindo dados em treino e teste...") |
|
|
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) |
|
|
print(f"Tamanho do conjunto de treino: {X_train.shape[0]}, Teste: {X_test.shape[0]}") |
|
|
|
|
|
|
|
|
print("Construindo modelo LSTM...") |
|
|
|
|
|
model = build_lstm_model((WINDOW_SIZE, NUM_FEATURES), LSTM_UNITS, DENSE_UNITS, DROPOUT_RATE, LEARNING_RATE) |
|
|
|
|
|
print("Iniciando treinamento do modelo...") |
|
|
|
|
|
reduce_lr_callback = tf.keras.callbacks.ReduceLROnPlateau( |
|
|
monitor='val_loss', |
|
|
factor=0.2, |
|
|
patience=7, |
|
|
min_lr=1e-7, |
|
|
verbose=1 |
|
|
) |
|
|
early_stopping_callback = tf.keras.callbacks.EarlyStopping( |
|
|
monitor='val_loss', |
|
|
patience=25, |
|
|
restore_best_weights=True |
|
|
) |
|
|
callbacks = [early_stopping_callback, reduce_lr_callback] |
|
|
|
|
|
from sklearn.utils.class_weight import compute_class_weight |
|
|
unique_classes_in_train = np.unique(y_train) |
|
|
class_weights_values = compute_class_weight('balanced', classes=unique_classes_in_train, y=y_train) |
|
|
class_weights = {cls: weight for cls, weight in zip(unique_classes_in_train, class_weights_values)} |
|
|
print(f"Pesos de Classe para Treinamento: {class_weights}") |
|
|
|
|
|
history = model.fit(X_train, y_train, |
|
|
epochs=EPOCHS, |
|
|
batch_size=BATCH_SIZE, |
|
|
validation_data=(X_test, y_test), |
|
|
callbacks=callbacks, |
|
|
class_weight=class_weights, |
|
|
verbose=1) |
|
|
|
|
|
|
|
|
print("Avaliando modelo no conjunto de teste...") |
|
|
loss, accuracy = model.evaluate(X_test, y_test, verbose=0) |
|
|
print(f"Perda no Teste: {loss:.4f}") |
|
|
print(f"Acurácia no Teste: {accuracy:.4f}") |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print("Salvando modelo e scalers...") |
|
|
os.makedirs(MODEL_SAVE_DIR, exist_ok=True) |
|
|
|
|
|
model_path = os.path.join(MODEL_SAVE_DIR, MODEL_NAME) |
|
|
model.save(model_path) |
|
|
print(f"Modelo salvo em: {model_path}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pv_cols_for_scaling = ['volume_div_atr', 'volume_div_atr'] |
|
|
pv_data_to_fit_scaler = ohlcv_df_with_target[pv_cols_for_scaling].copy() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
price_volume_scaler = MinMaxScaler() |
|
|
price_volume_data_original = ohlcv_df_with_target[['close_div_atr', 'volume_div_atr']].copy() |
|
|
price_volume_scaler.fit(price_volume_data_original) |
|
|
joblib.dump(price_volume_scaler, os.path.join(MODEL_SAVE_DIR, PRICE_VOL_SCALER_NAME)) |
|
|
print(f"Scaler de Preço/Volume salvo em: {os.path.join(MODEL_SAVE_DIR, PRICE_VOL_SCALER_NAME)}") |
|
|
|
|
|
|
|
|
indicator_cols_for_scaling = ['sma_10', 'rsi_14'] |
|
|
indicator_data_original = ohlcv_df_with_target[indicator_cols_for_scaling].copy() |
|
|
indicator_data_original.dropna(inplace=True) |
|
|
if not indicator_data_original.empty: |
|
|
indicator_scaler_instance = MinMaxScaler() |
|
|
indicator_scaler_instance.fit(indicator_data_original) |
|
|
joblib.dump(indicator_scaler_instance, os.path.join(MODEL_SAVE_DIR, INDICATOR_SCALER_NAME)) |
|
|
print(f"Scaler de Indicadores salvo em: {os.path.join(MODEL_SAVE_DIR, INDICATOR_SCALER_NAME)}") |
|
|
else: |
|
|
print("Não foi possível treinar/salvar o scaler de indicadores devido a dados vazios.") |
|
|
|
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
print("Script de treinamento concluído.") |
|
|
|
|
|
|
|
|
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) |
|
|
print(classification_report(y_test, y_pred_classes, target_names=['No Rise (0)', 'Rise (1)'], zero_division=0)) |
|
|
print(confusion_matrix(y_test, y_pred_classes)) |
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
main() |