Frictrade

This commit is contained in:
Jérôme Delacotte
2025-12-05 23:27:40 +01:00
parent 3efc95e415
commit ae98d11559
26 changed files with 15887 additions and 38 deletions

View File

@@ -456,7 +456,7 @@ class FrictradeLearning(IStrategy):
# Add all ta features
pair = metadata['pair']
short_pair = self.getShortName(pair)
self.path = f"user_data/plots/{short_pair}/" + ("valide/" if not self.dp.runmode.value in ('backtest') else '')
self.path = f"user_data/strategies/plots/{short_pair}/" + ("valide/" if not self.dp.runmode.value in ('backtest') else '')
heikinashi = qtpylib.heikinashi(dataframe)
dataframe['haopen'] = heikinashi['open']
@@ -490,6 +490,16 @@ class FrictradeLearning(IStrategy):
& (dataframe["sma5_sqrt"] > 5)
)
dataframe["sma12_sqrt"] = (
np.sqrt(np.abs(dataframe["sma12"] - dataframe["sma12"].shift(1)))
+ np.sqrt(np.abs(dataframe["sma12"].shift(3) - dataframe["sma12"].shift(1)))
)
dataframe["sma12_inv"] = (
(dataframe["sma12"].shift(2) >= dataframe["sma12"].shift(1))
& (dataframe["sma12"].shift(1) <= dataframe["sma12"])
& (dataframe["sma12_sqrt"] > 5)
)
dataframe["percent"] = dataframe['mid'].pct_change()
dataframe["percent3"] = dataframe['mid'].pct_change(3).rolling(3).mean()
dataframe["percent12"] = dataframe['mid'].pct_change(12).rolling(12).mean()
@@ -723,9 +733,9 @@ class FrictradeLearning(IStrategy):
self.trainModel(dataframe, metadata)
short_pair = self.getShortName(pair)
path=f"user_data/plots/{short_pair}/"
# path=f"user_data/strategies/plots/{short_pair}/"
self.model = joblib.load(f"{path}/{short_pair}_rf_model.pkl")
self.model = joblib.load(f"{self.path}/{short_pair}_rf_model.pkl")
# Préparer les features pour la prédiction
features = dataframe[self.model_indicators].fillna(0)
@@ -824,7 +834,12 @@ class FrictradeLearning(IStrategy):
# Buy = prediction > threshold
dataframe["buy"] = 0
dataframe.loc[dataframe["ml_prob"] > 0.5, ['enter_long', 'enter_tag']] = (1, f"future")
dataframe.loc[
(dataframe["ml_prob"].shift(3) < 0.2)
& (dataframe['low'].shift(3) < dataframe['min180'].shift(3))
& (dataframe['min180'].shift(3) == dataframe['min180']),
['enter_long', 'enter_tag']
] = (1, f"future")
dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.003, np.nan)
return dataframe
@@ -1060,9 +1075,10 @@ class FrictradeLearning(IStrategy):
# FIN ########################## ALGO ATH
condition = last_candle['hapercent'] > 0 and last_candle['sma24_deriv1'] > 0 \
and last_candle['close'] < self.pairs[pair]['first_buy'] \
and last_candle['ml_prob'] > 0.65
condition = last_candle['percent'] > 0 and last_candle['sma24_deriv1'] > 0 \
and last_candle['close'] < self.pairs[pair]['first_buy']
# and last_candle['ml_prob'] > 0.65
limit_buy = 40
# or (last_candle['close'] <= last_candle['min180'] and hours > 3)
if (decline >= dca_threshold) and condition:
@@ -1206,12 +1222,12 @@ class FrictradeLearning(IStrategy):
position = (mid - min_) / (max_ - min_)
zone = int(position * 3) # 0 à 2
if zone == 0:
current_trailing_stop_positive = self.trailing_stop_positive
current_trailing_stop_positive_offset = self.trailing_stop_positive_offset * 2
if minutes > 1440:
current_trailing_only_offset_is_reached = False
current_trailing_stop_positive_offset = self.trailing_stop_positive_offset
# if zone == 0:
# current_trailing_stop_positive = self.trailing_stop_positive
# current_trailing_stop_positive_offset = self.trailing_stop_positive_offset * 2
# if minutes > 1440:
# current_trailing_only_offset_is_reached = False
# current_trailing_stop_positive_offset = self.trailing_stop_positive_offset
# if zone == 1:
# ----- 5) Calcul du trailing stop dynamique -----
@@ -1236,7 +1252,7 @@ class FrictradeLearning(IStrategy):
stake=0
)
if last_candle['ml_prob'] > 0.65:
if last_candle['sma12'] > last_candle['sma24']:
return None
# if last_candle['sma24_deriv1'] > 0 : #and minutes < 180 and baisse < 30: # and last_candle['sma5_deriv1'] > -0.15:
# if (minutes < 180):
@@ -1371,7 +1387,7 @@ class FrictradeLearning(IStrategy):
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option("display.width", 200)
path=f"user_data/plots/{pair}/"
path=self.path #f"user_data/plots/{pair}/"
os.makedirs(path, exist_ok=True)
# # Étape 1 : sélectionner numériques
@@ -1452,7 +1468,7 @@ class FrictradeLearning(IStrategy):
plt.yticks(rotation=0)
# --- Sauvegarde ---
output_path = f"{path}/Matrice_de_correlation_temperature.png"
output_path = f"{self.path}/Matrice_de_correlation_temperature.png"
plt.savefig(output_path, bbox_inches="tight", dpi=150)
plt.close(fig)
@@ -1594,7 +1610,7 @@ class FrictradeLearning(IStrategy):
)
# Sauvegarde du graphique sur disque
output_path = f"{path}/shap_waterfall.png"
output_path = f"{self.path}/shap_waterfall.png"
plt.savefig(output_path, dpi=200, bbox_inches='tight')
plt.close() # ferme la figure proprement
@@ -1625,13 +1641,13 @@ class FrictradeLearning(IStrategy):
# Graphs
fig = plot_optimization_history(study)
fig.write_html(f"{path}/optimization_history.html")
fig.write_html(f"{self.path}/optimization_history.html")
fig = plot_param_importances(study)
fig.write_html(f"{path}/param_importances.html")
fig.write_html(f"{self.path}/param_importances.html")
fig = plot_slice(study)
fig.write_html(f"{path}/slice.html")
fig.write_html(f"{self.path}/slice.html")
fig = plot_parallel_coordinate(study)
fig.write_html(f"{path}/parallel_coordinates.html")
fig.write_html(f"{self.path}/parallel_coordinates.html")
# 2⃣ Sélection des features AVANT calibration
@@ -1677,14 +1693,14 @@ class FrictradeLearning(IStrategy):
feat_imp.plot(kind='bar', figsize=(12, 6))
plt.title("Feature importances")
# plt.show()
plt.savefig(f"{path}/Feature importances.png", bbox_inches='tight')
plt.savefig(f"{self.path}/Feature importances.png", bbox_inches='tight')
result = permutation_importance(self.train_model, X_valid, y_valid, scoring='f1', n_repeats=10, random_state=42)
perm_imp = pd.Series(result.importances_mean, index=X_valid.columns).sort_values(ascending=False)
perm_imp.plot(kind='bar', figsize=(12, 6))
plt.title("Permutation feature importance")
# plt.show()
plt.savefig(f"{path}/Permutation feature importance.png", bbox_inches='tight')
plt.savefig(f"{self.path}/Permutation feature importance.png", bbox_inches='tight')
# Shap
explainer = shap.TreeExplainer(self.train_model)
@@ -1695,7 +1711,7 @@ class FrictradeLearning(IStrategy):
# Force plot pour une observation
force_plot = shap.force_plot(explainer.expected_value, shap_values[0, :], X_valid.iloc[0, :])
shap.save_html(f"{path}/shap_force_plot.html", force_plot)
shap.save_html(f"{self.path}/shap_force_plot.html", force_plot)
fig, ax = plt.subplots(figsize=(24, 48))
PartialDependenceDisplay.from_estimator(
@@ -1705,7 +1721,7 @@ class FrictradeLearning(IStrategy):
kind="average",
ax=ax
)
fig.savefig(f"{path}/PartialDependenceDisplay.png", bbox_inches="tight")
fig.savefig(f"{self.path}/PartialDependenceDisplay.png", bbox_inches="tight")
plt.close(fig)
best_f1 = 0
@@ -1726,7 +1742,7 @@ class FrictradeLearning(IStrategy):
print(f"Accuracy: {acc:.3f}")
# 7⃣ Sauvegarde du modèle
joblib.dump(self.train_model, f"{path}/{pair}_rf_model.pkl")
joblib.dump(self.train_model, f"{self.path}/{pair}_rf_model.pkl")
print(f"✅ Modèle sauvegardé sous {pair}_rf_model.pkl")

25
Genetic.json Normal file
View File

@@ -0,0 +1,25 @@
{
"strategy_name": "Genetic",
"params": {
"roi": {
"0": 0.21029,
"11": 0.05876,
"57": 0.02191,
"281": 0
},
"stoploss": {
"stoploss": -1.0
},
"max_open_trades": {
"max_open_trades": 80
},
"trailing": {
"trailing_stop": true,
"trailing_stop_positive": 0.028,
"trailing_stop_positive_offset": 0.105,
"trailing_only_offset_is_reached": true
}
},
"ft_stratparam_v": 1,
"export_time": "2025-12-04 23:58:24.045587+00:00"
}

View File

@@ -160,7 +160,7 @@ class Genetic(IStrategy):
}
# Stoploss:
stoploss = -0.07693
stoploss = -1
# Optimal ticker interval for the strategy
ticker_interval = '2h'
@@ -359,4 +359,5 @@ class Genetic(IStrategy):
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
return dataframe
return dataframe

View File

@@ -11,8 +11,13 @@
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
from datetime import timedelta, datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open,
IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute)
from pandas import DataFrame
# --------------------------------
from datetime import timezone, timedelta
# Add your lib to import here
# import talib.abstract as ta
@@ -24,6 +29,38 @@ import numpy as np
class GodStra(IStrategy):
position_adjustment_enable = True
columns_logged = False
pairs = {
pair: {
"first_buy": 0,
"last_buy": 0.0,
"last_min": 999999999999999.5,
"last_max": 0,
"trade_info": {},
"max_touch": 0.0,
"last_sell": 0.0,
'count_of_buys': 0,
'current_profit': 0,
'expected_profit': 0,
'previous_profit': 0,
"last_candle": {},
"last_count_of_buys": 0,
'base_stake_amount': 0,
'stop_buy': False,
'last_date': 0,
'stop': False,
'max_profit': 0,
'first_amount': 0,
'total_amount': 0,
'has_gain': 0,
'force_sell': False,
'force_buy': False
}
for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC",
"BTC/USDT", "ETH/USDT", "DOGE/USDT", "XRP/USDT", "SOL/USDT"]
}
# 5/66: 9 trades. 8/0/1 Wins/Draws/Losses. Avg profit 21.83%. Median profit 35.52%. Total profit 1060.11476586 USDT ( 196.50Σ%). Avg duration 3440.0 min. Objective: -7.06960
# +--------+---------+----------+------------------+--------------+-------------------------------+----------------+-------------+
# | Best | Epoch | Trades | Win Draw Loss | Avg profit | Profit | Avg duration | Objective |
@@ -67,7 +104,7 @@ class GodStra(IStrategy):
trailing_stop_positive_offset = 0.2684
trailing_only_offset_is_reached = True
# Buy hypers
timeframe = '12h'
timeframe = '1m'
print('Add {\n\t"method": "AgeFilter",\n\t"min_days_listed": 30\n},\n to your pairlists in config (Under StaticPairList)')
def dna_size(self, dct: dict):
@@ -169,3 +206,150 @@ class GodStra(IStrategy):
'sell'] = 1
return dataframe
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, min_stake: float,
max_stake: float, **kwargs):
# ne rien faire si ordre deja en cours
print('ici')
if trade.has_open_orders:
# self.printLog("skip open orders")
return None
if (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake:
return 0
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
before_last_candle = dataframe.iloc[-2].squeeze()
# prépare les données
current_time = current_time.astimezone(timezone.utc)
open_date = trade.open_date.astimezone(timezone.utc)
dispo = round(self.wallets.get_available_stake_amount())
hours_since_first_buy = (current_time - trade.open_date_utc).seconds / 3600.0
days_since_first_buy = (current_time - trade.open_date_utc).days
hours = (current_time - trade.date_last_filled_utc).total_seconds() / 3600.0
count_of_buys = trade.nr_of_successful_entries
current_time_utc = current_time.astimezone(timezone.utc)
open_date = trade.open_date.astimezone(timezone.utc)
days_since_open = (current_time_utc - open_date).days
pair = trade.pair
profit = trade.calc_profit(current_rate) #round(current_profit * trade.stake_amount, 1)
last_lost = self.getLastLost(last_candle, pair)
pct_first = 0
total_counts = sum(
pair_data['count_of_buys'] for pair_data in self.pairs.values() if not self.getShortName(pair) == 'BTC')
if self.pairs[pair]['first_buy']:
pct_first = self.getPctFirstBuy(pair, last_candle)
lim = 0.3
if (len(dataframe) < 1):
# self.printLog("skip dataframe")
return None
# Dernier prix d'achat réel (pas le prix moyen)
last_fill_price = self.pairs[trade.pair]['last_buy'] #trade.open_rate # remplacé juste après ↓
# if len(trade.orders) > 0:
# # On cherche le dernier BUY exécuté
# buy_orders = [o for o in trade.orders if o.is_buy and o.status == "closed"]
# if buy_orders:
# last_fill_price = buy_orders[-1].price
# baisse relative
dca_threshold = 0.0025 * count_of_buys
decline = (last_fill_price - current_rate) / last_fill_price
increase = - decline
# if decline >= self.dca_threshold:
# # Exemple : on achète 50% du montant du dernier trade
# last_amount = buy_orders[-1].amount if buy_orders else 0
# stake_amount = last_amount * current_rate * 0.5
# return stake_amount
condition = last_candle['percent'] > 0 #and last_candle['sma24_deriv1'] > 0
limit_buy = 40
# or (last_candle['close'] <= last_candle['min180'] and hours > 3)
if (decline >= dca_threshold) and condition:
print('decline')
try:
if self.pairs[pair]['has_gain'] and profit > 0:
self.pairs[pair]['force_sell'] = True
self.pairs[pair]['previous_profit'] = profit
return None
max_amount = self.config.get('stake_amount') * 2.5
stake_amount = min(min(max_amount, self.wallets.get_available_stake_amount()),
self.adjust_stake_amount(pair, last_candle))
# print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}")
if stake_amount > 0:
self.pairs[pair]['previous_profit'] = profit
trade_type = "Loss " + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '')
self.pairs[trade.pair]['count_of_buys'] += 1
self.pairs[pair]['total_amount'] += stake_amount
# self.log_trade(
# last_candle=last_candle,
# date=current_time,
# action="🟧 Loss -",
# dispo=dispo,
# pair=trade.pair,
# rate=current_rate,
# trade_type=trade_type,
# profit=round(profit, 1),
# buys=trade.nr_of_successful_entries + 1,
# stake=round(stake_amount, 2)
# )
self.pairs[trade.pair]['last_buy'] = current_rate
self.pairs[trade.pair]['max_touch'] = last_candle['close']
self.pairs[trade.pair]['last_candle'] = last_candle
# df = pd.DataFrame.from_dict(self.pairs, orient='index')
# colonnes_a_exclure = ['last_candle', 'stop',
# 'trade_info', 'last_date', 'expected_profit', 'last_count_of_buys', 'base_stake_amount', 'stop_buy']
# df_filtered = df[df['count_of_buys'] > 0].drop(columns=colonnes_a_exclure)
# # df_filtered = df_filtered["first_buy", "last_max", "max_touch", "last_sell","last_buy", 'count_of_buys', 'current_profit']
#
# self.printLog(df_filtered)
return stake_amount
return None
except Exception as exception:
self.printLog(exception)
return None
if current_profit > dca_threshold and (increase >= dca_threshold and self.wallets.get_available_stake_amount() > 0):
print('increase')
try:
self.pairs[pair]['previous_profit'] = profit
stake_amount = max(20, min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle)))
if stake_amount > 0:
self.pairs[pair]['has_gain'] += 1
trade_type = 'Gain +' + (last_candle['enter_tag'] if last_candle['enter_long'] == 1 else '')
self.pairs[trade.pair]['count_of_buys'] += 1
self.pairs[pair]['total_amount'] += stake_amount
# self.log_trade(
# last_candle=last_candle,
# date=current_time,
# action="🟡 Gain +",
# dispo=dispo,
# pair=trade.pair,
# rate=current_rate,
# trade_type='Gain',
# profit=round(profit, 1),
# buys=trade.nr_of_successful_entries + 1,
# stake=round(stake_amount, 2)
# )
self.pairs[trade.pair]['last_buy'] = current_rate
self.pairs[trade.pair]['max_touch'] = last_candle['close']
self.pairs[trade.pair]['last_candle'] = last_candle
return stake_amount
return None
except Exception as exception:
self.printLog(exception)
return None
return None

View File

@@ -14,7 +14,10 @@ import pandas as pd # noqa
from pandas import DataFrame
from skopt.space import Categorical, Dimension, Integer, Real # noqa
from freqtrade.optimize.hyperopt_interface import IHyperOpt
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open,
IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute)
# from freqtrade.optimize.hyperopt_interface import IHyperOpt
# --------------------------------
# Add your lib to import here
@@ -52,7 +55,7 @@ GodGenes = ["open", "high", "low", "close", "volume", "volume_adi", "volume_obv"
"others_dr", "others_dlr", "others_cr"]
class GodStraHo(IHyperOpt):
class GodStraHo(IStrategy):
@staticmethod
def indicator_space() -> List[Dimension]:

53
GodStraJD.json Normal file
View File

@@ -0,0 +1,53 @@
{
"strategy_name": "GodStraJD",
"params": {
"roi": {
"0": 0.598,
"644": 0.166,
"3269": 0.115,
"7289": 0
},
"max_open_trades": {
"max_open_trades": 80
},
"buy": {
"buy_crossed_indicator0": "WMA-100",
"buy_crossed_indicator1": "KAMA-110",
"buy_crossed_indicator2": "CDLKICKING-55",
"buy_indicator0": "MACD-0-50",
"buy_indicator1": "SAREXT-15",
"buy_indicator2": "DEMA-12",
"buy_operator0": "<R",
"buy_operator1": "C",
"buy_operator2": "D",
"buy_real_num0": 0.7,
"buy_real_num1": 0.7,
"buy_real_num2": 0.7
},
"sell": {
"sell_crossed_indicator0": "CDLDARKCLOUDCOVER-15",
"sell_crossed_indicator1": "CDLHANGINGMAN-6",
"sell_crossed_indicator2": "CDLMATHOLD-55",
"sell_indicator0": "MEDPRICE-15",
"sell_indicator1": "T3-100",
"sell_indicator2": "CDLDOJISTAR-12",
"sell_operator0": "=R",
"sell_operator1": "DT",
"sell_operator2": "CA",
"sell_real_num0": 0.7,
"sell_real_num1": 0.7,
"sell_real_num2": 0.6
},
"stoploss": {
"stoploss": -0.122
},
"trailing": {
"trailing_stop": true,
"trailing_stop_positive": 0.058,
"trailing_stop_positive_offset": 0.097,
"trailing_only_offset_is_reached": true
}
},
"ft_stratparam_v": 1,
"export_time": "2025-12-04 23:29:46.366351+00:00"
}

View File

@@ -470,7 +470,7 @@ class GodStraJD(IStrategy):
# Stoploss:
stoploss = -0.128
# Buy hypers
timeframe = '4h'
timeframe = '1m'
# Trailing stoploss
trailing_stop = True

View File

@@ -474,7 +474,7 @@ class GodStraJD3_1(IStrategy):
# Stoploss:
stoploss = -1
# Buy hypers
timeframe = '4h'
timeframe = '1m'
# Trailing stoploss
trailing_stop = True

View File

@@ -668,8 +668,7 @@ class GodStraJD3_4(IStrategy):
# # Use default stake amount.
# return proposed_stake
def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
current_profit: float, **kwargs):
def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
previous_last_candle = dataframe.iloc[-2].squeeze()
@@ -971,8 +970,6 @@ class GodStraJD3_4(IStrategy):
if conditions:
dataframe.loc[
(reduce(lambda x, y: x & y, conditions)
# & (dataframe['percent_4h'] > 0)
& (dataframe['percent3_4h'] <= 0)
),
'buy']=1
# print(len(dataframe.keys()))

View File

@@ -86,7 +86,7 @@ class HammerReversalStrategy(IStrategy):
}
stoploss = -1
timeframe = '1h'
timeframe = '1m'
position_adjustment_enable = True
columns_logged = False
max_entry_position_adjustment = 20

BIN
plots/BTC/BTC_rf_model.pkl Normal file

Binary file not shown.

BIN
plots/BTC/Courbe ROC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 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: 58 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

3888
plots/BTC/slice.html Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB