Crash detection

This commit is contained in:
Jérôme Delacotte
2025-12-13 13:53:51 +01:00
parent 94b9845619
commit c039aa29bd
17 changed files with 17174 additions and 0 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@@ -0,0 +1 @@
0.06525423728813559

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
plots/BTC/crash/loss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

3888
plots/BTC/crash/slice.html Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
# ================================
# 1. PREPARE DATA
# ================================
df = pd.read_feather("/home/jerome/Perso/freqtradeDocker/user_data/data/binance/BTC_USDC-1h.feather")
# features
df['returns'] = df['close'].pct_change()
df['atr'] = (df['high'] - df['low']).rolling(14).mean()
df['slope'] = df['close'].rolling(20).mean().diff()
df['drawdown'] = (df['close'] - df['close'].rolling(48).max()) / df['close'].rolling(48).max()
# label : crash si -12% dans les 48h
future = df['close'].shift(-48)
df['future_dd'] = (future - df['close']) / df['close']
df['crash'] = (df['future_dd'] < -0.12).astype(int)
df = df.dropna()
X = df[['returns','atr','slope','drawdown']]
y = df['crash']
# ================================
# 2. TRAIN MODEL
# ================================
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, shuffle=False)
model = RandomForestClassifier(n_estimators=200)
model.fit(X_train, y_train)
print("Accuracy:", model.score(X_test, y_test))
print("Feature importance:", model.feature_importances_)

View File

@@ -0,0 +1,163 @@
import json
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
DATA_FILE = "/home/jerome/Perso/freqtradeDocker/user_data/data/binance/BTC_USDC-4h.feather"
def compute_crash_risk_index(df):
# VOLATILITÉ
df['H-L'] = df['high'] - df['low']
df['ATR'] = df['H-L'].rolling(14).mean()
df['atr_norm'] = df['ATR'] / df['close']
# DRAWDOWN (critique)
df['rolling_max'] = df['close'].rolling(48).max()
df['drawdown'] = (df['close'] - df['rolling_max']) / df['rolling_max']
df['dd_score'] = np.clip(-df['drawdown'] / 0.10, 0, 1)
# TENDANCE (slope)
df['MA7'] = df['close'].rolling(7).mean()
df['MA14'] = df['close'].rolling(14).mean()
df['slope'] = df['MA7'] - df['MA14']
df['slope_score'] = np.clip(1 - (df['slope'] / df['close']), 0, 1)
# NÉGATIVE STREAK
df['neg_streak'] = df['close'].pct_change().apply(lambda x: min(x, 0)).rolling(24).sum()
df['neg_score'] = np.clip(-df['neg_streak'] / 0.05, 0, 1)
# COMPOSANTS COURT TERME
df['pct_change_3'] = df['close'].pct_change(3)
df['pct_change_3_smooth'] = df['pct_change_3'].rolling(6).mean()
df['crash_score'] = np.clip(1 + (df['pct_change_3_smooth'] / 0.05), 0, 1)
df['speed'] = df['close'].diff().rolling(6).mean()
df['accel'] = df['speed'].diff().rolling(6).mean()
df['STD20'] = df['close'].rolling(20).std()
df['accel_score'] = np.clip(1 + (df['accel'] / (df['STD20'] + 1e-9)), 0, 1)
# INDEX FINAL
df['crash_raw'] = (
0.35 * df['dd_score'] + # le plus important pour crash lent
0.25 * df['neg_score'] +
0.20 * df['slope_score'] +
0.10 * df['crash_score'] +
0.10 * df['accel_score']
)
# LISSAGE SIMPLE
df['crash_risk_index'] = df['crash_raw'].ewm(span=24).mean()
return df
#
# def compute_crash_risk_index(df):
# # -- volatilité
# df['H-L'] = df['high'] - df['low']
# df['ATR'] = df['H-L'].rolling(14).mean()
# df['atr_norm'] = df['ATR'] / df['close']
#
# # -- variations lissées pour éviter les spikes
# df['pct_change_3'] = df['close'].pct_change(3)
# df['pct_change_3_smooth'] = df['pct_change_3'].rolling(6).mean()
#
# # -- speed/accel : on SMOOTH sinon c'est incontrôlable
# df['speed'] = df['close'].diff().rolling(6).mean()
# df['accel'] = df['speed'].diff().rolling(6).mean()
#
# # -- Bollinger
# df['MA20'] = df['close'].rolling(20).mean()
# df['STD20'] = df['close'].rolling(20).std()
# df['BB_lower'] = df['MA20'] - 2 * df['STD20']
#
# # -------- Scores normalisés & STABLES --------
# df['crash_score'] = np.clip(1 + (df['pct_change_3_smooth'] / 0.05), 0, 1)
#
# df['accel_score'] = np.clip(
# 1 + (df['accel'] / (df['STD20'] + 1e-9)),
# 0, 1
# )
#
# df['boll_score'] = np.clip(
# (df['close'] - df['BB_lower']) / (3 * df['STD20']), # + 3σ au lieu de 2σ
# 0, 1
# )
#
# df['atr_score'] = np.clip(
# 1 - (df['atr_norm'] / 0.06), # tolérance plus large
# 0, 1
# )
#
# # -------- COMBINAISON + DOUBLE SMOOTHING --------
# df['crash_raw'] = (
# 0.30 * df['crash_score'] +
# 0.25 * df['accel_score'] +
# 0.20 * df['boll_score'] +
# 0.15 * df['atr_score']
# )
#
# # Lissage ultra important pour éviter le 0.3 → 0.9
# df['crash_risk_index'] = (
# df['crash_raw'].ewm(span=48).mean() # 2 jours en 1h
# ).rolling(24).mean() # filtre final
#
# return df
# def compute_crash_risk_index(df):
# df['H-L'] = df['high'] - df['low']
# df['ATR'] = df['H-L'].rolling(14).mean()
# df['atr_norm'] = df['ATR'] / df['close']
#
# df['pct_change_3'] = df['close'].pct_change(3)
#
# df['speed'] = df['close'].diff()
# df['accel'] = df['speed'].diff()
#
# df['MA20'] = df['close'].rolling(20).mean()
# df['STD20'] = df['close'].rolling(20).std()
# df['BB_lower'] = df['MA20'] - 2 * df['STD20']
#
# df['crash_score'] = np.clip(1 + (df['pct_change_3'] / 0.05), 0, 1)
# df['accel_score'] = np.clip(1 + (df['accel'] / df['STD20']), 0, 1)
# df['boll_score'] = np.clip((df['close'] - df['BB_lower']) / (2 * df['STD20']), 0, 1)
# df['atr_score'] = np.clip(1 - (df['atr_norm'] / 0.04), 0, 1)
#
# df['crash_raw'] = (
# 0.30 * df['crash_score'] +
# 0.25 * df['accel_score'] +
# 0.20 * df['boll_score'] +
# 0.15 * df['atr_score']
# )
#
# # Lissage ultra important pour éviter le 0.3 → 0.9
# df['crash_risk_index'] = (
# df['crash_raw'].ewm(span=4).mean() # 2 jours en 1h
# ).rolling(2).mean() # filtre final
#
# return df
# Load Freqtrade OHLCV JSON
# with open(DATA_FILE, "r") as f:
# raw = json.load(f)
df = pd.read_feather(DATA_FILE)
print(df.head())
# df = pd.DataFrame(raw)
df['date'] = pd.to_datetime(df['date'])
df = df.set_index('date')
# compute risk
df = compute_crash_risk_index(df)
# Only keep Nov. 2025
df = df["2025-10-01":"2025-12-10"]
plt.figure(figsize=(12, 6))
plt.plot(df.index, df['crash_risk_index'])
plt.title("Crash Risk Index Novembre 2025")
plt.grid(True)
plt.show()

File diff suppressed because it is too large Load Diff