diff --git a/pairs.txt b/pairs.txt new file mode 100644 index 0000000..dd9e748 --- /dev/null +++ b/pairs.txt @@ -0,0 +1,6 @@ +BTC_USDT +ETH_USDT +DOGE_USDT +XRP_USDT +SOL_USDT + diff --git a/tools/create_params_tree.sh b/tools/create_params_tree.sh new file mode 100755 index 0000000..6c9b6b8 --- /dev/null +++ b/tools/create_params_tree.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Usage: create_params_tree.sh [PAIR2] ... + +if [ $# -lt 1 ]; then + echo "Usage: $0 [PAIR2] ..." >&2 + exit 1 +fi + +for PAIR in "$@"; do + DIR="params/$PAIR" + mkdir -p "$DIR" + echo "Created: $DIR" + for REGIME in bull bear range; do + mkdir -p "$DIR/$REGIME" + echo " -> $DIR/$REGIME" + done +done diff --git a/tools/detect_regime.py b/tools/detect_regime.py new file mode 100755 index 0000000..966a9d3 --- /dev/null +++ b/tools/detect_regime.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +""" +Detect bull/bear/range regimes from historical CSV data (SMA50/SMA200 crossovers) +Usage: python3 detect_regime.py +CSV must contain at least columns: timestamp, close +""" + +# python3 user_data/strategies/tools/detect_regime.py BTC /home/jerome/Perso/freqtradeDocker/user_data/data/binance/BTC_USDT-1d.feather + +import sys +import pandas as pd +import ta +import talib.abstract as talib + +if len(sys.argv) < 3: + print("Usage: detect_regime.py ") + sys.exit(1) + +pair = sys.argv[1] +file = sys.argv[2] + +# lecture du fichier feather +df = pd.read_feather(file) +# ne garder que timestamp et close pour detect_regime.py +df[['date', 'close']].rename(columns={'date':'timestamp'}).to_csv( + f"user_data/data/{pair}-usdt-1d.csv", index=False +) + +df = pd.read_csv(f"user_data/data/{pair}-usdt-1d.csv") + +if 'close' not in df.columns or 'timestamp' not in df.columns: + print("CSV must contain 'timestamp' and 'close' columns") + sys.exit(1) + +# --- paramètres --- +SMOOTH_WIN = 5 # EMA pour lisser la pente +NUM_TOP = 3 # nombre de segments à garder par classe + +# --- charger les données --- +df['timestamp'] = pd.to_datetime(df['timestamp'], errors='coerce') + +# --- calcul SMA14 --- +df['sma14'] = ta.trend.sma_indicator(df['close'], 14) + +# --- pente brute --- +df['slope'] = df['sma14'].diff() + +# --- lissage EMA --- +df['slope_smooth'] = df['slope'].ewm(span=SMOOTH_WIN, adjust=False).mean() + +# --- normalisation relative --- +df['slope_norm'] = df['slope_smooth'] / df['close'] +df['slope_norm'].fillna(0, inplace=True) + +# --- classification dynamique via quantiles --- +q = df['slope_norm'].quantile([0.07, 0.21, 0.35, 0.65, 0.79, 0.93]).values +q1, q2, q3, q4, q5, q6 = q + +def classify(v): + if v <= q1: + return 'BR3' + elif v <= q2: + return 'BR2' + elif v <= q3: + return 'BR1' + elif v <= q4: + return 'RG' + elif v <= q5: + return 'BU1' + elif v <= q6: + return 'BU2' + else: + return 'BU3' + +df['trend_class'] = df['slope_norm'].apply(classify) + +# --- boucle pour détecter les segments --- +segments = [] +trend = None +start_idx = None + +for i in range(len(df)): + new_trend = df['trend_class'].iloc[i] + + if trend is None: + trend = new_trend + start_idx = i + elif new_trend != trend: + start_ts = df['timestamp'].iloc[start_idx] + end_ts = df['timestamp'].iloc[i] + segments.append((trend, start_ts, end_ts)) + trend = new_trend + start_idx = i + +# fermer le dernier segment +if trend is not None and start_idx is not None: + start_ts = df['timestamp'].iloc[start_idx] + end_ts = df['timestamp'].iloc[len(df)-1] + segments.append((trend, start_ts, end_ts)) + +# --- extraire les 5 plus longs segments par classe --- +top_segments_by_class = {} +for cls in ['BR3','BR2','BR1','RG','BU1','BU2','BU3']: + cls_segments = [(t,s,e) for t,s,e in segments if t==cls] + # calcul de la durée + cls_segments = [(t,s,e,(e-s).total_seconds()) for t,s,e in cls_segments] + cls_segments.sort(key=lambda x: x[3], reverse=True) + top_segments_by_class[cls] = [(t,s,e) for t,s,e,d in cls_segments[:NUM_TOP]] + +# --- affichage --- +for cls, segs in top_segments_by_class.items(): + print(f"--- {cls} ---") + for t,s,e in segs: + print(f"{t} {s} {e}") diff --git a/tools/run_hyperopt_batch.sh b/tools/run_hyperopt_batch.sh new file mode 100755 index 0000000..df631d6 --- /dev/null +++ b/tools/run_hyperopt_batch.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Usage: run_hyperopt_batch.sh +# contains one pair per line, e.g., BTC-USDT\nETH-USDT +# +# This script will read each pair, detect regimes (using detect_regime.py), +# and run freqtrade hyperopt for each detected regime, saving user_data/strategies/params in user_data/strategies/params/PAIR/REGIME/ + +# Lanement par docker exec -i freqtrade user_data/strategies/tools/run_hyperopt_batch.sh user_data/strategies/pairs.txt +# user_data/strategies/tools/run_hyperopt_batch.sh user_data/strategies/pairs.txt + +STRATEGIE="Zeus_8_3_2_B_4_2" + +if [ $# -ne 1 ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +PAIR_LIST_FILE="$1" + +if [ ! -f "$PAIR_LIST_FILE" ]; then + echo "File not found: $PAIR_LIST_FILE" >&2 + exit 1 +fi +echo "ici $PAIR_LIST_FILE" + +while read -r PAIR; do + echo $PAIR + [ -z "$PAIR" ] && continue + echo "\nProcessing pair: $PAIR" + + DATA_FEATHER="user_data/data/binance/$(echo $PAIR | tr '[:upper:]' '[:upper:]')-1d.feather" + if [ ! -f "$DATA_FEATHER" ]; then + echo "Data file not found for $PAIR: $DATA_FEATHER" >&2 + continue + fi + echo "Work with data file $PAIR: $DATA_FEATHER" >&2 + + # Detect regimes using Python script + REGIMES=$(python3 user_data/strategies/tools/detect_regime.py "$PAIR" "$DATA_FEATHER") # | awk '{print tolower($1) " " $2 " " $4}') + + while read -r REGIME START st END et; do + echo "Work with dates $REGIME : $START - $END" >&2 + + if [ -z "$START" ] || [ -z "$END" ]; then + echo "Skipping $PAIR $REGIME : cannot determine timerange" >&2 + continue + fi + + if [[ "$START" < "2024-09-01" ]]; then + echo "TOO OLD $START" + continue + fi + + TIMERANGE="${START//-/}-${END//-/}" + echo "Running hyperopt for $PAIR $REGIME with timerange $TIMERANGE" + + OUTPUT_JSON="user_data/strategies/params/$PAIR/$REGIME/$START-$END-hyperopt_result.json" + OUTPUT_TXT="user_data/strategies/params/$PAIR/$REGIME/$START-$END-hyperopt_result.txt" + mkdir -p "user_data/strategies/params/$PAIR/$REGIME" + + converted="${PAIR/_//}" + +# COLUMNS=200 LINES=40 script -q -c " + + freqtrade hyperopt --strategy $STRATEGIE --config user_data/config.json --hyperopt-loss OnlyProfitHyperOptLoss --timerange $TIMERANGE --timeframe 5m --spaces sell buy protection --pair $converted -e 80 -j7 + + echo "Saved hyperopt output to $OUTPUT_JSON" + + cp user_data/strategies/$STRATEGIE.json $OUTPUT_JSON + + # COLUMNS=120 LINES=40 script -q -c " +# docker exec -i freqtrade + freqtrade hyperopt-list --profitable --no-details > $OUTPUT_TXT + + sleep 1 + + done <<< "$REGIMES" + +done < "$PAIR_LIST_FILE" \ No newline at end of file diff --git a/tools/save_params.sh b/tools/save_params.sh new file mode 100755 index 0000000..69b64fc --- /dev/null +++ b/tools/save_params.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Usage: save_params.sh + +set -e + +if [ $# -ne 4 ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +PAIR="$1" +REGIME="$2" +SRC="$3" +DESTNAME="$4" + +mkdir -p "params/${PAIR}/${REGIME}" + +DEST="params/${PAIR}/${REGIME}/${DESTNAME}.json" +cp "$SRC" "$DEST" +echo "Saved: $DEST" \ No newline at end of file