import pandas as pd import json import os import numpy as np from datetime import datetime import matplotlib.pyplot as plt from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, confusion_matrix class HougaardAnalyzer: def __init__(self, file_path): self.file_path = file_path self.df = None self.results = {} def load_data(self): """Loads Freqtrade/Bybit JSON format and converts to DataFrame.""" print(f"Loading data from {self.file_path}...") with open(self.file_path, 'r') as f: data = json.load(f) # Format: [timestamp, open, high, low, close, volume] cols = ['date', 'open', 'high', 'low', 'close', 'volume'] self.df = pd.DataFrame(data, columns=cols) # Convert timestamp (ms) to datetime self.df['date'] = pd.to_datetime(self.df['date'], unit='ms', utc=True) self.df.set_index('date', inplace=True) print(f"Loaded {len(self.df)} candles.") def engineer_features(self): """Creates Hougaard-style features.""" print("Engineering features...") df = self.df # 1. Time-based features df['hour'] = df.index.hour df['day_of_week'] = df.index.dayofweek # 2. Overnight Range (00:00 - 08:00 UTC) # We group by day and calculate High-Low for the 0-8h window df['date_only'] = df.index.date overnight = df.between_time('00:00', '08:00').groupby('date_only').agg({ 'high': 'max', 'low': 'min', 'open': 'first' }).rename(columns={'high': 'on_high', 'low': 'on_low', 'open': 'on_open'}) overnight['on_range_pct'] = (overnight['on_high'] - overnight['on_low']) / overnight['on_open'] # Map back to main DF df = df.join(overnight, on='date_only') # 3. Distance from Overnight High/Low at 08:00 df['dist_from_on_high'] = (df['close'] - df['on_high']) / df['on_high'] df['dist_from_on_low'] = (df['close'] - df['on_low']) / df['on_low'] # 4. Volatility (ATR-like) df['body_size'] = abs(df['close'] - df['open']) / df['open'] df['wick_size'] = (df['high'] - np.maximum(df['open'], df['close'])) / df['open'] self.df = df.dropna() def label_data(self, target_pct=0.01, stop_pct=0.005, horizon_candles=48): """ Labels a 'Long' setup at 08:00 UTC. 1: Hits target before stop 0: Hits stop before target or expires """ print(f"Labeling data (Target: {target_pct*100}%, Stop: {stop_pct*100}%)...") # We only look at the 08:00 candle (London Open) setups = self.df[self.df.index.hour == 8].copy() labels = [] for idx, row in setups.iterrows(): entry_price = row['close'] target_price = entry_price * (1 + target_pct) stop_price = entry_price * (1 - stop_pct) # Look ahead future_data = self.df.loc[idx:].iloc[1:horizon_candles] success = 0 for f_idx, f_row in future_data.iterrows(): if f_row['high'] >= target_price: success = 1 break if f_row['low'] <= stop_price: success = 0 break labels.append(success) setups['label'] = labels return setups def run_analysis(self): self.load_data() self.engineer_features() setups = self.label_data() # Features for the model features = ['hour', 'day_of_week', 'on_range_pct', 'dist_from_on_high', 'dist_from_on_low', 'body_size', 'wick_size'] X = setups[features] y = setups['label'] if len(y.unique()) < 2: print("Error: Not enough variance in labels. Adjust target/stop.") return X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) model = RandomForestClassifier(n_estimators=100, random_state=42) model.fit(X_train, y_train) # Importance importances = pd.Series(model.feature_importances_, index=features).sort_values(ascending=False) print("\n--- Feature Importance (Hougaard Insights) ---") print(importances) # Accuracy y_pred = model.predict(X_test) print("\n--- Model Performance ---") print(classification_report(y_test, y_pred)) if __name__ == "__main__": # Test with BTC 5m data data_path = "/Volumes/Alpha SSD/Coding/freqtrade/user_data/data/bybit/BTC_USDT-5m-futures.json" analyzer = HougaardAnalyzer(data_path) analyzer.run_analysis()