FrictradeLearning.py Stoploss auto et gestion steps de mises adjust
@@ -9,22 +9,27 @@
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": false,
|
||||
"trailing_stop_positive": 0.15,
|
||||
"trailing_stop_positive": 0.2,
|
||||
"trailing_stop_positive_offset": 1,
|
||||
"trailing_only_offset_is_reached": true
|
||||
},
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 80
|
||||
},
|
||||
"buy": {
|
||||
"hours_force": 11
|
||||
"max_open_trades": 20
|
||||
},
|
||||
"protection": {
|
||||
"allow_decrease_rate": 0.7,
|
||||
"first_adjust_param": 0.01,
|
||||
"max_steps": 35
|
||||
"allow_decrease_rate": 0.4,
|
||||
"first_adjust_param": 0.005,
|
||||
"max_steps": 45
|
||||
},
|
||||
"buy": {
|
||||
"hours_force": 44,
|
||||
"indic_1h_force_buy": "sma5_deriv1_1h"
|
||||
},
|
||||
"sell": {
|
||||
"offset_max": 18,
|
||||
"offset_min": 17
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2025-12-09 07:22:51.929255+00:00"
|
||||
"export_time": "2025-12-14 18:44:03.713386+00:00"
|
||||
}
|
||||
@@ -5,77 +5,57 @@
|
||||
# IMPORTANT: INSTALL TA BEFOUR RUN(pip install ta)
|
||||
# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces buy sell roi --strategy Zeus
|
||||
# --- Do not remove these libs ---
|
||||
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)
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import os
|
||||
import json
|
||||
import csv
|
||||
from pandas import DataFrame
|
||||
from typing import Optional, Union, Tuple
|
||||
import math
|
||||
import inspect
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
# --------------------------------
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from typing import Optional
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
# Machine Learning
|
||||
import joblib
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import mpmath as mp
|
||||
import numpy as np
|
||||
import optuna
|
||||
import pandas as pd
|
||||
import seaborn as sns
|
||||
import shap
|
||||
# Add your lib to import here test git
|
||||
import ta
|
||||
import talib.abstract as talib
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from datetime import timezone, timedelta
|
||||
import mpmath as mp
|
||||
|
||||
# Machine Learning
|
||||
from sklearn.ensemble import RandomForestClassifier,RandomForestRegressor
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
|
||||
from sklearn.metrics import accuracy_score
|
||||
import joblib
|
||||
import matplotlib.pyplot as plt
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.strategy import (CategoricalParameter, DecimalParameter, IntParameter, IStrategy, merge_informative_pair)
|
||||
from optuna.visualization import plot_optimization_history
|
||||
from optuna.visualization import plot_parallel_coordinate
|
||||
from optuna.visualization import plot_param_importances
|
||||
from optuna.visualization import plot_slice
|
||||
from pandas import DataFrame
|
||||
from sklearn.calibration import CalibratedClassifierCV
|
||||
from sklearn.feature_selection import SelectFromModel
|
||||
from sklearn.feature_selection import VarianceThreshold
|
||||
from sklearn.inspection import PartialDependenceDisplay
|
||||
from sklearn.inspection import permutation_importance
|
||||
from sklearn.linear_model import LogisticRegression
|
||||
from sklearn.metrics import brier_score_loss, roc_auc_score
|
||||
from sklearn.metrics import (
|
||||
classification_report,
|
||||
confusion_matrix,
|
||||
accuracy_score,
|
||||
roc_auc_score,
|
||||
roc_curve,
|
||||
precision_score, recall_score, precision_recall_curve,
|
||||
f1_score
|
||||
precision_score, recall_score
|
||||
)
|
||||
from sklearn.tree import export_text
|
||||
import inspect
|
||||
from sklearn.feature_selection import mutual_info_classif
|
||||
from sklearn.inspection import permutation_importance
|
||||
from lightgbm import LGBMClassifier
|
||||
from sklearn.calibration import CalibratedClassifierCV
|
||||
from sklearn.feature_selection import SelectFromModel
|
||||
from tabulate import tabulate
|
||||
from sklearn.model_selection import GridSearchCV
|
||||
from sklearn.feature_selection import VarianceThreshold
|
||||
import seaborn as sns
|
||||
from xgboost import XGBClassifier
|
||||
import optuna
|
||||
from optuna.visualization import plot_optimization_history
|
||||
from optuna.visualization import plot_slice
|
||||
from optuna.visualization import plot_param_importances
|
||||
from optuna.visualization import plot_parallel_coordinate
|
||||
import shap
|
||||
from sklearn.inspection import PartialDependenceDisplay
|
||||
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.metrics import f1_score
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.pipeline import Pipeline
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.tree import export_text
|
||||
from xgboost import XGBClassifier
|
||||
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.linear_model import LogisticRegression
|
||||
from sklearn.calibration import CalibratedClassifierCV
|
||||
from sklearn.metrics import brier_score_loss, roc_auc_score
|
||||
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.pipeline import Pipeline
|
||||
# --------------------------------
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -91,7 +71,7 @@ RESET = "\033[0m"
|
||||
|
||||
|
||||
class FrictradeLearning(IStrategy):
|
||||
startup_candle_count = 180
|
||||
startup_candle_count = 200
|
||||
train_model = None
|
||||
model_indicators = []
|
||||
DEFAULT_PARAMS = {
|
||||
@@ -106,13 +86,21 @@ class FrictradeLearning(IStrategy):
|
||||
"stoploss": -0.10,
|
||||
"minimal_roi": {"0": 0.10}
|
||||
}
|
||||
indicators = {'sma24_deriv1', 'sma60_deriv1', 'sma5_deriv1_1h', 'sma12_deriv1_1h', 'sma24_deriv1_1h',
|
||||
'sma60_deriv1_1h'}
|
||||
indic_1h_force_buy = CategoricalParameter(indicators, default="sma60_deriv1", space='buy')
|
||||
|
||||
allow_decrease_rate = DecimalParameter(0.1, 0.8, decimals=1, default=0.4, space='protection')
|
||||
first_adjust_param = DecimalParameter(0.001, 0.01, decimals=3, default=0.003, space='protection')
|
||||
allow_decrease_rate = DecimalParameter(0.1, 0.8, decimals=1, default=0.4, space='protection', optimize=False,
|
||||
load=False)
|
||||
first_adjust_param = DecimalParameter(0.001, 0.01, decimals=3, default=0.005, space='protection', optimize=False,
|
||||
load=False)
|
||||
max_steps = IntParameter(10, 50, default=40, space='protection', optimize=True, load=True)
|
||||
|
||||
hours_force = IntParameter(1, 48, default=24, space='buy', optimize=True, load=True)
|
||||
|
||||
offset_min = IntParameter(1, 48, default=24, space='sell', optimize=True, load=True)
|
||||
offset_max = IntParameter(1, 48, default=24, space='sell', optimize=True, load=True)
|
||||
|
||||
# ROI table:
|
||||
minimal_roi = {
|
||||
"0": 10
|
||||
@@ -157,11 +145,13 @@ class FrictradeLearning(IStrategy):
|
||||
'stop': False,
|
||||
'max_profit': 0,
|
||||
'first_amount': 0,
|
||||
'first_price': 0,
|
||||
'total_amount': 0,
|
||||
'has_gain': 0,
|
||||
'force_sell': False,
|
||||
'force_buy': False,
|
||||
'last_ath': 0,
|
||||
'mises': {},
|
||||
'dca_thresholds': {}
|
||||
}
|
||||
for pair in ["BTC/USDC", "ETH/USDC", "DOGE/USDC", "XRP/USDC", "SOL/USDC",
|
||||
@@ -178,11 +168,30 @@ class FrictradeLearning(IStrategy):
|
||||
{"date": "2021-11-10", "price_usd": 68742.00, "note": "record novembre 2021 (institutional demand)"},
|
||||
{"date": "2024-03-05", "price_usd": 69000.00,
|
||||
"note": "nouveau pic début 2024 (source presse, valeur indicative)"},
|
||||
{"date": "2024-03-14", "price_usd": 73816.00,
|
||||
"note": "nouveau pic début 2024 (source presse, valeur indicative)"},
|
||||
{"date": "2024-11-12", "price_usd": 90000.00, "note": ""},
|
||||
{"date": "2024-12-17", "price_usd": 108363.00, "note": ""},
|
||||
{"date": "2025-07-11", "price_usd": 118755.00, "note": "pic juillet 2025 (valeur rapportée par la presse)"},
|
||||
{"date": "2025-08-13", "price_usd": 123748.00, "note": ""},
|
||||
{"date": "2025-10-06", "price_usd": 126198.07,
|
||||
"note": "pic oct. 2025 (source agrégée, à vérifier selon l'exchange)"}
|
||||
]
|
||||
|
||||
def dynamic_trailing_offset(self, price, ath, nb_entries, max_dca=5):
|
||||
dd_ath = (ath - price) / ath
|
||||
dd_ath = max(0.0, min(dd_ath, 0.5))
|
||||
|
||||
dca_risk = min(nb_entries / max_dca, 1.0)
|
||||
|
||||
breathing_score = 0.7 * dd_ath + 0.3 * (1 - dca_risk)
|
||||
breathing_score = min(max(breathing_score, 0.0), 1.0)
|
||||
|
||||
OFFSET_MIN = self.offset_min.value
|
||||
OFFSET_MAX = self.offset_min.value + self.offset_max.value
|
||||
|
||||
return OFFSET_MIN + breathing_score * (OFFSET_MAX - OFFSET_MIN)
|
||||
|
||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str,
|
||||
current_time: datetime, entry_tag: Optional[str], **kwargs) -> bool:
|
||||
|
||||
@@ -235,13 +244,8 @@ class FrictradeLearning(IStrategy):
|
||||
|
||||
return allow_to_buy
|
||||
|
||||
def calculateStepsDcaThresholds(self, last_candle, pair):
|
||||
# def split_ratio_one_third(n, p):
|
||||
# a = n / (2 * p) # première valeur
|
||||
# d = n / (p * (p - 1)) # incrément
|
||||
# return [round(a + i * d, 3) for i in range(p)]
|
||||
def progressive_parts(total, n, first):
|
||||
print('In part')
|
||||
def progressive_parts(self, total, n, first):
|
||||
# print('In part')
|
||||
# conditions impossibles → on évite le solveur
|
||||
if total <= 0 or first <= 0 or n <= 1:
|
||||
return [0] * n
|
||||
@@ -257,21 +261,31 @@ class FrictradeLearning(IStrategy):
|
||||
parts = [round(first * (r ** k), 4) for k in range(n)]
|
||||
return parts
|
||||
|
||||
def calculateStepsDcaThresholds(self, last_candle, pair):
|
||||
# def split_ratio_one_third(n, p):
|
||||
# a = n / (2 * p) # première valeur
|
||||
# d = n / (p * (p - 1)) # incrément
|
||||
# return [round(a + i * d, 3) for i in range(p)]
|
||||
|
||||
# r, parts = progressive_parts(0.4, 40, 0.004)
|
||||
# print("r =", r)
|
||||
# print(parts)
|
||||
|
||||
val = self.pairs[pair]['first_price'] if self.pairs[pair]['first_price'] > 0 else last_candle['mid']
|
||||
|
||||
if self.pairs[pair]['last_ath'] == 0:
|
||||
ath = max(last_candle['mid'], self.get_last_ath_before_candle(last_candle))
|
||||
ath = max(val, self.get_last_ath_before_candle(last_candle['date']))
|
||||
self.pairs[pair]['last_ath'] = ath
|
||||
|
||||
steps = self.approx_value(last_candle['mid'], self.pairs[pair]['last_ath'])
|
||||
self.pairs[pair]['dca_thresholds'] = progressive_parts(
|
||||
(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / last_candle['mid'],
|
||||
ath = self.pairs[pair]['last_ath']
|
||||
|
||||
steps = self.calculateNumberOfSteps(val, ath, max_steps=self.max_steps.value)
|
||||
self.pairs[pair]['dca_thresholds'] = self.progressive_parts(
|
||||
(val - (ath * (1 - self.allow_decrease_rate.value))) / val,
|
||||
steps, self.first_adjust_param.value)
|
||||
print(f"val={last_candle['mid']} lim={self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value)}"
|
||||
print(f"val={val} lim={self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value)}"
|
||||
f" steps={steps}"
|
||||
f"pct={(round(last_candle['mid'] - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / last_candle['mid'], 4)}")
|
||||
f" pct={round((val - (self.pairs[pair]['last_ath'] * (1 - self.allow_decrease_rate.value))) / val, 4)}")
|
||||
print(self.pairs[pair]['dca_thresholds'])
|
||||
|
||||
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float,
|
||||
@@ -285,7 +299,8 @@ class FrictradeLearning(IStrategy):
|
||||
minutes = int(round((current_time - trade.open_date_utc).seconds / 60, 0))
|
||||
profit = trade.calc_profit(rate)
|
||||
force = self.pairs[pair]['force_sell']
|
||||
allow_to_sell = minutes > 30 and (last_candle['hapercent'] < 0 ) or force or (exit_reason == 'force_exit') or (exit_reason == 'stop_loss')
|
||||
# and (last_candle['hapercent'] < 0 )
|
||||
allow_to_sell = True # (last_candle['hapercent'] < 0 ) or force or (exit_reason == 'force_exit') or (exit_reason == 'stop_loss')
|
||||
|
||||
if allow_to_sell:
|
||||
self.trades = list()
|
||||
@@ -306,6 +321,7 @@ class FrictradeLearning(IStrategy):
|
||||
dispo=dispo,
|
||||
profit=round(profit, 2)
|
||||
)
|
||||
self.pairs[pair]['first_amount'] = 0
|
||||
self.pairs[pair]['max_profit'] = 0
|
||||
self.pairs[pair]['force_sell'] = False
|
||||
self.pairs[pair]['has_gain'] = 0
|
||||
@@ -316,8 +332,9 @@ class FrictradeLearning(IStrategy):
|
||||
self.pairs[pair]['last_buy'] = 0
|
||||
self.pairs[pair]['last_date'] = current_time
|
||||
self.pairs[pair]['current_trade'] = None
|
||||
# else:
|
||||
# self.printLog(f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked")
|
||||
else:
|
||||
self.printLog(
|
||||
f"{current_time} SELL triggered for {pair} ({exit_reason} profit={profit} minutes={minutes} percent={last_candle['hapercent']}) but condition blocked")
|
||||
return (allow_to_sell) | (exit_reason == 'force_exit') | (exit_reason == 'stop_loss')
|
||||
|
||||
# def custom_exit(self, pair: str, trade: Trade, current_time, current_rate, current_profit, **kwargs):
|
||||
@@ -387,6 +404,7 @@ class FrictradeLearning(IStrategy):
|
||||
def getLastLost(self, last_candle, pair):
|
||||
last_lost = round((last_candle['close'] - self.pairs[pair]['max_touch']) / self.pairs[pair]['max_touch'], 3)
|
||||
return last_lost
|
||||
|
||||
def getPctFirstBuy(self, pair, last_candle):
|
||||
return round((last_candle['close'] - self.pairs[pair]['first_buy']) / self.pairs[pair]['first_buy'], 3)
|
||||
|
||||
@@ -400,7 +418,8 @@ class FrictradeLearning(IStrategy):
|
||||
lim = 0.005
|
||||
pct = 0.001
|
||||
pct_to_max = lim + pct * self.pairs[pair]['count_of_buys']
|
||||
expected_profit = lim * self.pairs[pair]['total_amount'] # min(3 * lim, max(lim, pct_to_max)) # 0.004 + 0.002 * self.pairs[pair]['count_of_buys'] #min(0.01, first_max)
|
||||
expected_profit = lim * self.pairs[pair][
|
||||
'total_amount'] # min(3 * lim, max(lim, pct_to_max)) # 0.004 + 0.002 * self.pairs[pair]['count_of_buys'] #min(0.01, first_max)
|
||||
|
||||
self.pairs[pair]['expected_profit'] = expected_profit
|
||||
|
||||
@@ -415,7 +434,8 @@ class FrictradeLearning(IStrategy):
|
||||
self.printLog(
|
||||
f"| {'Date':<16} | {'Action':<10} |{'Pair':<5}| {'Trade Type':<18} |{'Rate':>8} | {'Dispo':>6} | {'Profit':>8} "
|
||||
f"| {'Pct':>6} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>7}| {'last_min':>7}|{'Buys':>5}| {'Stake':>5} |"
|
||||
f"{'rsi':>6}|{'rsi_1h':>6}|{'rsi_1d':>6}|{'mlprob':>6}" #|Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
|
||||
f"{'rsi':>6}|{'rsi_1h':>6}|{'rsi_1d':>6}|{'cf_1h':>6}|{'cf_1d':>6}"
|
||||
# |Distmax|s201d|s5_1d|s5_2d|s51h|s52h|smt1h|smt2h|tdc1d|tdc1h"
|
||||
)
|
||||
self.printLineLog()
|
||||
df = pd.DataFrame.from_dict(self.pairs, orient='index')
|
||||
@@ -467,7 +487,7 @@ class FrictradeLearning(IStrategy):
|
||||
f"| {last_max or '-':>7} | {last_min or '-':>7} |{total_counts or '-':>5}|{stake or '-':>7}"
|
||||
f"{round(last_candle['max_rsi_24'], 1) or '-':>6}|{round(last_candle['rsi_1h'], 1) or '-':>6}|{round(last_candle['rsi_1d'], 1) or '-':>6}|"
|
||||
# f"{round(last_candle['rtp_1h'] * 100, 0) or '-' :>6}|{round(last_candle['rtp_1d'] * 100, 0) or '-' :>6}|"
|
||||
f"{round(last_candle['ml_prob'], 1) or '-':>6}|"
|
||||
# f"{round(last_candle['confidence_index_1d'], 3) or '-':>6}|{round(last_candle['confidence_index_1h'], 3) or '-':>6}|"
|
||||
)
|
||||
|
||||
def printLineLog(self):
|
||||
@@ -498,7 +518,8 @@ class FrictradeLearning(IStrategy):
|
||||
dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose']
|
||||
|
||||
dataframe['mid'] = dataframe['open'] + (dataframe['close'] - dataframe['open']) / 2
|
||||
dataframe['sma5'] = dataframe['mid'].ewm(span=5, adjust=False).mean() #dataframe["mid"].rolling(window=5).mean()
|
||||
dataframe['sma5'] = dataframe['mid'].ewm(span=5,
|
||||
adjust=False).mean() # dataframe["mid"].rolling(window=5).mean()
|
||||
dataframe['sma5_deriv1'] = 1000 * (dataframe['sma5'] - dataframe['sma5'].shift(1)) / dataframe['sma5'].shift(1)
|
||||
|
||||
dataframe['sma12'] = dataframe['mid'].ewm(span=12, adjust=False).mean()
|
||||
@@ -506,10 +527,12 @@ class FrictradeLearning(IStrategy):
|
||||
'sma12'].shift(1)
|
||||
|
||||
dataframe['sma24'] = dataframe['mid'].ewm(span=24, adjust=False).mean()
|
||||
dataframe['sma24_deriv1'] = 1000 * (dataframe['sma24'] - dataframe['sma24'].shift(1)) / dataframe['sma24'].shift(1)
|
||||
dataframe['sma24_deriv1'] = 1000 * (dataframe['sma24'] - dataframe['sma24'].shift(1)) / dataframe[
|
||||
'sma24'].shift(1)
|
||||
|
||||
dataframe['sma60'] = dataframe['mid'].ewm(span=60, adjust=False).mean()
|
||||
dataframe['sma60_deriv1'] = 1000 * (dataframe['sma60'] - dataframe['sma60'].shift(1)) / dataframe['sma60'].shift(1)
|
||||
dataframe['sma60_deriv1'] = 1000 * (dataframe['sma60'] - dataframe['sma60'].shift(1)) / dataframe[
|
||||
'sma60'].shift(1)
|
||||
|
||||
# dataframe[f"sma5_inv"] = (dataframe[f"sma5"].shift(2) >= dataframe[f"sma5"].shift(1)) \
|
||||
# & (dataframe[f"sma5"].shift(1) <= dataframe[f"sma5"])
|
||||
@@ -564,16 +587,30 @@ class FrictradeLearning(IStrategy):
|
||||
informative['macdhist'] = macdhist
|
||||
|
||||
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14)
|
||||
|
||||
informative['sma5'] = informative['mid'].ewm(span=5, adjust=False).mean()
|
||||
informative['sma5_deriv1'] = 1000 * (informative['sma5'] - informative['sma5'].shift(1)) / informative[
|
||||
'sma5'].shift(1)
|
||||
|
||||
informative['sma12'] = informative['mid'].ewm(span=12, adjust=False).mean()
|
||||
informative['sma12_deriv1'] = 1000 * (informative['sma12'] - informative['sma12'].shift(1)) / informative[
|
||||
'sma12'].shift(1)
|
||||
|
||||
informative['sma24'] = informative['mid'].ewm(span=24, adjust=False).mean()
|
||||
informative['sma24_deriv1'] = 1000 * (informative['sma24'] - informative['sma24'].shift(1)) / informative['sma24'].shift(1)
|
||||
informative['sma24_deriv1'] = 1000 * (informative['sma24'] - informative['sma24'].shift(1)) / informative[
|
||||
'sma24'].shift(1)
|
||||
|
||||
informative['sma60'] = informative['mid'].ewm(span=60, adjust=False).mean()
|
||||
informative['sma60_deriv1'] = 1000 * (informative['sma60'] - informative['sma60'].shift(1)) / informative['sma60'].shift(1)
|
||||
informative['sma60_deriv1'] = 1000 * (informative['sma60'] - informative['sma60'].shift(1)) / informative[
|
||||
'sma60'].shift(1)
|
||||
|
||||
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=14)
|
||||
self.calculeDerivees(informative, 'rsi', ema_period=12)
|
||||
# informative = self.rsi_trend_probability(informative)
|
||||
|
||||
probas = self.calculModelInformative(informative)
|
||||
# probas = self.calculModelInformative(informative)
|
||||
|
||||
# self.calculateConfiance(informative)
|
||||
|
||||
# informative = self.populate1hIndicators(df=informative, metadata=metadata)
|
||||
# informative = self.calculateRegression(informative, 'mid', lookback=15)
|
||||
@@ -585,6 +622,8 @@ class FrictradeLearning(IStrategy):
|
||||
informative['rsi'] = talib.RSI(informative['mid'], timeperiod=5)
|
||||
# informative = self.rsi_trend_probability(informative)
|
||||
# informative = self.calculateRegression(informative, 'mid', lookback=15)
|
||||
# self.calculateConfiance(informative)
|
||||
|
||||
dataframe = merge_informative_pair(dataframe, informative, '1m', '1d', ffill=True)
|
||||
|
||||
dataframe['last_price'] = dataframe['close']
|
||||
@@ -599,8 +638,8 @@ class FrictradeLearning(IStrategy):
|
||||
filled_buys = trade.select_filled_orders('buy')
|
||||
count = 0
|
||||
amount = 0
|
||||
min_price = 111111111111110;
|
||||
max_price = 0;
|
||||
min_price = 111111111111110
|
||||
max_price = 0
|
||||
for buy in filled_buys:
|
||||
if count == 0:
|
||||
min_price = min(min_price, buy.price)
|
||||
@@ -616,7 +655,7 @@ class FrictradeLearning(IStrategy):
|
||||
self.pairs[pair]['last_buy'] = buy.price
|
||||
count = count + 1
|
||||
amount += buy.price * buy.filled
|
||||
count_buys = count
|
||||
self.pairs[pair]['count_of_buys'] = count
|
||||
self.pairs[pair]['total_amount'] = amount
|
||||
|
||||
dataframe['absolute_min'] = dataframe['mid'].rolling(1440, min_periods=1).min()
|
||||
@@ -845,11 +884,55 @@ class FrictradeLearning(IStrategy):
|
||||
# d = n / (p * (p - 1)) # incrément
|
||||
# return [round(a + i * d, 3) for i in range(p)]
|
||||
#
|
||||
# for val in range(90000, 130000, 10000):
|
||||
# steps = self.approx_value(val, 126000)
|
||||
# print(f"val={val} steps={steps} pct={(val - (126000 * (1 - self.allow_decrease_rate.value))) / val}")
|
||||
# dca = split_ratio_one_third((val - (126000 * (1 - self.allow_decrease_rate.value))) / 126000, steps)
|
||||
# print(dca)
|
||||
allow_decrease_rate = 0.3
|
||||
# for val in range(70000, 140000, 10000):
|
||||
# ath = 126000
|
||||
#
|
||||
# steps = self.calculateNumberOfSteps(val, ath, max_steps=40)
|
||||
# self.printLog(f"allow_decrease_rate={self.allow_decrease_rate.value} val={val} steps={steps} pct={round((val - (ath * (1 - allow_decrease_rate))) / val, 4)}")
|
||||
# # dca = split_ratio_one_third((val - (ath * (1 - self.allow_decrease_rate.value))) / ath, steps)
|
||||
# # self.printLog(dca)
|
||||
# dca_thresholds = self.progressive_parts(
|
||||
# (val - (ath * (1 - self.allow_decrease_rate.value))) / val,
|
||||
# steps, self.first_adjust_param.value)
|
||||
# print(f"val={val} lim={ath * (1 - self.allow_decrease_rate.value)}"
|
||||
# f"steps={steps} "
|
||||
# f"pct={(round(val - (ath * (1 - self.allow_decrease_rate.value))) / val, 4)}")
|
||||
# print(dca_thresholds)
|
||||
ath = 126000
|
||||
|
||||
last_candle = dataframe.iloc[-1].squeeze()
|
||||
val = last_candle['first_price']
|
||||
# steps = self.calculateNumberOfSteps(val, ath, max_steps=40)
|
||||
# self.printLog(
|
||||
# f"allow_decrease_rate={self.allow_decrease_rate.value} val={val} steps={steps} pct={round((val - (ath * (1 - allow_decrease_rate))) / val, 4)}")
|
||||
# dca_thresholds = self.progressive_parts((val - (ath * (1 - self.allow_decrease_rate.value))) / val, steps, self.first_adjust_param.value)
|
||||
# print(f"val={val} lim={ath * (1 - self.allow_decrease_rate.value)}"
|
||||
# f"steps={steps} "
|
||||
# f"pct={(round(val - (ath * (1 - self.allow_decrease_rate.value))) / val, 4)}")
|
||||
# print(dca_thresholds)
|
||||
|
||||
|
||||
if self.dp:
|
||||
if self.dp.runmode.value in ('live', 'dry_run'):
|
||||
full, mises, steps = self.calculateMises(last_candle, pair)
|
||||
# stake = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle))
|
||||
if val and len(self.pairs[pair]['dca_thresholds']) > 0:
|
||||
print(self.pairs[pair]['dca_thresholds'])
|
||||
count = 0
|
||||
pct = 0
|
||||
dataframe = dataframe.copy()
|
||||
for dca in self.pairs[pair]['dca_thresholds']:
|
||||
stake = mises[count]
|
||||
pct += dca
|
||||
offset = self.dynamic_trailing_offset(price=val, ath=ath, nb_entries=count)
|
||||
|
||||
if (count == self.pairs[pair]['count_of_buys']):
|
||||
print(f"next_buy={val * (1 - pct)} count={count} pct={round(pct, 4)}")
|
||||
dataframe[f"next_buy"] = val * (1 - pct)
|
||||
count += 1
|
||||
print(
|
||||
f"stake={stake} count={count} pct={round(pct, 4)} offset={offset} next_buy={val * (1 - pct)}")
|
||||
|
||||
return dataframe
|
||||
|
||||
@@ -910,9 +993,9 @@ class FrictradeLearning(IStrategy):
|
||||
(dataframe["ml_prob"].shift(1) < dataframe["ml_prob"])
|
||||
& (dataframe['sma24_deriv1'] > 0)
|
||||
& (dataframe['sma12_deriv1'] > 0)
|
||||
& (dataframe['open'] < dataframe['max180'] * 0.997),
|
||||
# & (dataframe['min180'].shift(3) == dataframe['min180']),
|
||||
['enter_long', 'enter_tag']
|
||||
# & (dataframe['open'] < dataframe['max180'] * 0.997)
|
||||
# & (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)
|
||||
|
||||
@@ -1029,50 +1112,40 @@ class FrictradeLearning(IStrategy):
|
||||
#
|
||||
# return adjusted_stake_amount
|
||||
|
||||
def approx_value(self, x, X_max):
|
||||
X_min = X_max * (1 - self.allow_decrease_rate.value) # 126198 * 0.4 = 75718,8
|
||||
def calculateNumberOfSteps(self, current, ath, max_steps=0):
|
||||
if (max_steps == 0):
|
||||
max_steps = self.max_steps.value
|
||||
|
||||
X_min = ath * (1 - self.allow_decrease_rate.value) # 126198 * 0.4 = 75718,8
|
||||
Y_min = 1
|
||||
Y_max = self.max_steps.value
|
||||
a = (Y_max - Y_min) / (X_max - X_min) # 39 ÷ (126198 − 126198×0,6) = 0,000772595
|
||||
Y_max = max_steps
|
||||
a = (Y_max - Y_min) / (ath - X_min) # 39 ÷ (126198 − 126198×0,6) = 0,000772595
|
||||
b = Y_min - a * X_min # 1 − (0,000772595 × 75718,8) = −38
|
||||
y = a * x + b # 0,000772595 * 115000 - 38
|
||||
y = a * current + b # 0,000772595 * 115000 - 38
|
||||
return max(round(y), 1) # évite les valeurs négatives
|
||||
|
||||
def adjust_stake_amount(self, pair: str, last_candle: DataFrame):
|
||||
|
||||
if self.pairs[pair]['first_amount'] > 0:
|
||||
return self.pairs[pair]['first_amount']
|
||||
mises = self.pairs[pair]['mises']
|
||||
count = self.pairs[pair]['count_of_buys'] - self.pairs[pair]['has_gain']
|
||||
return mises[count] if count < len(mises) else self.pairs[pair]['first_amount']
|
||||
|
||||
ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
|
||||
full, mises, steps = self.calculateMises(last_candle, pair)
|
||||
|
||||
base_stake = mises[self.pairs[pair]['count_of_buys']] if self.pairs[pair]['count_of_buys'] < len(
|
||||
mises) else full / (steps * 2)
|
||||
return base_stake
|
||||
|
||||
def calculateMises(self, last_candle, pair):
|
||||
ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle['date']))
|
||||
self.pairs[pair]['last_ath'] = ath
|
||||
|
||||
ath_dist = 100 * (ath - last_candle["mid"]) / ath
|
||||
|
||||
|
||||
# ath_dist
|
||||
# 0 ==> 1
|
||||
# 20 ==> 1.5
|
||||
# 40 ==> 2
|
||||
# 50 * (1 + (ath_dist / 40))
|
||||
|
||||
full = self.wallets.get_total_stake_amount()
|
||||
steps = self.approx_value(last_candle['mid'], ath)
|
||||
base_stake = full / steps
|
||||
|
||||
# base_stake = stake * (1 + (ath_dist / 40))
|
||||
|
||||
# Calcule max/min 180
|
||||
low180 = last_candle["min180"]
|
||||
high180 = last_candle["max180"]
|
||||
|
||||
mult = 1 - ((last_candle["mid"] - low180) / (high180 - low180))
|
||||
|
||||
print(f"low={low180} mid={last_candle['mid']} high={high180} mult={mult} ath={ath} ath_dist={round(ath_dist, 2)}" )
|
||||
# base_size = montant de base que tu veux utiliser (ex: stake_amount ou autre)
|
||||
base_size = base_stake # exemple fraction du portefeuille; adapte selon ton code
|
||||
# new stake proportionnel à mult
|
||||
new_stake = base_size #* mult
|
||||
return new_stake
|
||||
steps = self.calculateNumberOfSteps(last_candle['mid'], ath, max_steps=self.max_steps.value)
|
||||
mises = self.progressive_parts(full, steps, full / (steps * 2))
|
||||
print(f"ath={ath} full={full} steps={steps} mises={mises} ")
|
||||
self.pairs[pair]['mises'] = mises
|
||||
return full, mises, steps
|
||||
|
||||
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
||||
current_rate: float, current_profit: float, min_stake: float,
|
||||
@@ -1083,6 +1156,11 @@ class FrictradeLearning(IStrategy):
|
||||
return None
|
||||
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
|
||||
|
||||
if (len(dataframe) < 1):
|
||||
# self.printLog("skip dataframe")
|
||||
return None
|
||||
|
||||
last_candle = dataframe.iloc[-1].squeeze()
|
||||
# before_last_candle = dataframe.iloc[-2].squeeze()
|
||||
# prépare les données
|
||||
@@ -1109,32 +1187,32 @@ class FrictradeLearning(IStrategy):
|
||||
# if self.pairs[pair]['first_buy']:
|
||||
# pct_first = self.getPctFirstBuy(pair, last_candle)
|
||||
|
||||
if profit > - self.pairs[pair]['first_amount'] \
|
||||
and self.wallets.get_available_stake_amount() < self.pairs[pair]['first_amount'] \
|
||||
and last_candle['sma24_deriv1_1h'] < 0:
|
||||
stake_amount = trade.stake_amount
|
||||
self.pairs[pair]['previous_profit'] = profit
|
||||
trade_type = "Sell " + (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="🟧 Sell +",
|
||||
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
|
||||
|
||||
return -stake_amount
|
||||
# if profit > - self.pairs[pair]['first_amount'] \
|
||||
# and self.wallets.get_available_stake_amount() < self.pairs[pair]['first_amount'] \
|
||||
# and last_candle['sma24_deriv1_1h'] < 0:
|
||||
# stake_amount = trade.stake_amount
|
||||
# self.pairs[pair]['previous_profit'] = profit
|
||||
# trade_type = "Sell " + (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="🟥 Stoploss",
|
||||
# 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
|
||||
#
|
||||
# return -stake_amount
|
||||
|
||||
if (self.wallets.get_available_stake_amount() < 10): # or trade.stake_amount >= max_stake:
|
||||
return 0
|
||||
@@ -1155,7 +1233,7 @@ class FrictradeLearning(IStrategy):
|
||||
|
||||
# baisse relative
|
||||
# if minutes % 60 == 0:
|
||||
# ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
|
||||
# ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle['date']))
|
||||
# self.pairs[pair]['last_ath'] = ath
|
||||
# else:
|
||||
# ath = self.pairs[pair]['last_ath']
|
||||
@@ -1166,9 +1244,10 @@ class FrictradeLearning(IStrategy):
|
||||
if len(self.pairs[pair]['dca_thresholds']) == 0:
|
||||
self.calculateStepsDcaThresholds(last_candle, pair)
|
||||
|
||||
dca_threshold = self.pairs[pair]['dca_thresholds'][min(count_of_buys - 1, len(self.pairs[pair]['dca_thresholds']) - 1)]
|
||||
dca_threshold = self.pairs[pair]['dca_thresholds'][
|
||||
min(count_of_buys - 1, len(self.pairs[pair]['dca_thresholds']) - 1)]
|
||||
|
||||
# print(f"{count_of_buys} {ath * (1 - self.allow_decrease_rate.value)} {round(last_candle['mid'], 2)} {round((last_candle['mid'] - (ath * self.allow_decrease_rate.value)) / last_candle['mid'], 2)} {steps} {round(dca_threshold, 4)}")
|
||||
# self.printLog(f"{count_of_buys} {ath * (1 - self.allow_decrease_rate.value)} {round(last_candle['mid'], 2)} {round((last_candle['mid'] - (ath * self.allow_decrease_rate.value)) / last_candle['mid'], 2)} {steps} {round(dca_threshold, 4)}")
|
||||
decline = (last_fill_price - current_rate) / last_fill_price
|
||||
increase = - decline
|
||||
|
||||
@@ -1178,10 +1257,9 @@ class FrictradeLearning(IStrategy):
|
||||
# stake_amount = last_amount * current_rate * 0.5
|
||||
# return stake_amount
|
||||
|
||||
|
||||
########################### ALGO ATH
|
||||
# # --- 1. Calcul ATH local de la paire ---
|
||||
# ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle))
|
||||
# ath = max(self.pairs[pair]['last_max'], self.get_last_ath_before_candle(last_candle['date']))
|
||||
#
|
||||
# # --- 2. Distance ATH - current ---
|
||||
# dd = (current_rate - ath) / ath * 100 # dd <= 0
|
||||
@@ -1208,7 +1286,7 @@ class FrictradeLearning(IStrategy):
|
||||
# return None
|
||||
|
||||
# FIN ########################## ALGO ATH
|
||||
force = hours > self.hours_force.value and last_candle['sma60_deriv1_1h'] > 0
|
||||
force = False #hours > self.hours_force.value and last_candle[self.indic_1h_force_buy.value] > 0
|
||||
condition = last_candle['percent'] > 0 and last_candle['sma24_deriv1'] > 0 \
|
||||
and last_candle['close'] < self.pairs[pair]['first_buy']
|
||||
|
||||
@@ -1219,11 +1297,12 @@ class FrictradeLearning(IStrategy):
|
||||
self.pairs[pair]['previous_profit'] = profit
|
||||
return None
|
||||
|
||||
stake_amount = min(self.wallets.get_available_stake_amount(), self.adjust_stake_amount(pair, last_candle))
|
||||
stake_amount = min(self.wallets.get_available_stake_amount(),
|
||||
self.adjust_stake_amount(pair, last_candle))
|
||||
|
||||
if force:
|
||||
stake_amount = stake_amount / 4
|
||||
# print(f"profit={profit} previous={self.pairs[pair]['previous_profit']} count_of_buys={trade.nr_of_successful_entries}")
|
||||
# if force:
|
||||
# stake_amount = stake_amount / 2
|
||||
# self.printLog(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 '')
|
||||
@@ -1260,11 +1339,14 @@ class FrictradeLearning(IStrategy):
|
||||
self.printLog(exception)
|
||||
return None
|
||||
|
||||
if current_profit > dca_threshold and (increase >= dca_threshold and self.wallets.get_available_stake_amount() > 0)\
|
||||
increase_dca_threshold = 0.003
|
||||
if current_profit > increase_dca_threshold \
|
||||
and (increase >= increase_dca_threshold and self.wallets.get_available_stake_amount() > 0) \
|
||||
and last_candle['rsi'] < 75:
|
||||
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)))
|
||||
stake_amount = max(10, min(self.wallets.get_available_stake_amount(),
|
||||
self.adjust_stake_amount(pair, last_candle)))
|
||||
if stake_amount > 0:
|
||||
self.pairs[pair]['has_gain'] += 1
|
||||
|
||||
@@ -1278,7 +1360,7 @@ class FrictradeLearning(IStrategy):
|
||||
dispo=dispo,
|
||||
pair=trade.pair,
|
||||
rate=current_rate,
|
||||
trade_type='Gain ' + str(round(dca_threshold, 4)),
|
||||
trade_type='Gain ' + str(round(increase, 4)),
|
||||
profit=round(profit, 1),
|
||||
buys=trade.nr_of_successful_entries + 1,
|
||||
stake=round(stake_amount, 2)
|
||||
@@ -1304,7 +1386,7 @@ class FrictradeLearning(IStrategy):
|
||||
# before_last_candle_12 = dataframe.iloc[-13].squeeze()
|
||||
#
|
||||
# expected_profit = self.expectedProfit(pair, last_candle)
|
||||
# # print(f"current_time={current_time} current_profit={current_profit} expected_profit={expected_profit}")
|
||||
# # self.printLog(f"current_time={current_time} current_profit={current_profit} expected_profit={expected_profit}")
|
||||
#
|
||||
# # ----- 1) Charger les variables de trailing pour ce trade -----
|
||||
# max_price = self.pairs[pair]['max_touch']
|
||||
@@ -1325,8 +1407,7 @@ class FrictradeLearning(IStrategy):
|
||||
max_profit = self.pairs[pair]['max_profit']
|
||||
|
||||
# if current_profit > 0:
|
||||
# print(f"profit={profit} max_profit={max_profit} current_profit={current_profit}")
|
||||
|
||||
# self.printLog(f"profit={profit} max_profit={max_profit} current_profit={current_profit}")
|
||||
|
||||
# baisse = 0
|
||||
# if profit > 0:
|
||||
@@ -1351,6 +1432,10 @@ class FrictradeLearning(IStrategy):
|
||||
current_trailing_only_offset_is_reached = self.trailing_only_offset_is_reached
|
||||
current_trailing_stop_positive_offset = self.trailing_stop_positive_offset
|
||||
|
||||
current_trailing_stop_positive_offset = self.dynamic_trailing_offset(price=current_rate,
|
||||
ath=self.pairs[pair]['last_ath'],
|
||||
nb_entries=count_of_buys)
|
||||
|
||||
# max_ = last_candle['max180']
|
||||
# min_ = last_candle['min180']
|
||||
# mid = last_candle['mid']
|
||||
@@ -1393,8 +1478,8 @@ class FrictradeLearning(IStrategy):
|
||||
if last_candle['rsi'] > 90:
|
||||
return f"rsi_{count_of_buys}_{self.pairs[pair]['has_gain']}"
|
||||
|
||||
if last_candle['sma12'] > last_candle['sma24']: # and last_candle['rsi'] < 85:
|
||||
return None
|
||||
# if last_candle['sma12'] > last_candle['sma24']: # and last_candle['rsi'] < 85:
|
||||
# return None
|
||||
|
||||
# if last_candle['sma24_deriv1'] > 0 : #and minutes < 180 and baisse < 30: # and last_candle['sma5_deriv1'] > -0.15:
|
||||
# if (minutes < 180):
|
||||
@@ -1402,27 +1487,28 @@ class FrictradeLearning(IStrategy):
|
||||
# if (minutes > 1440 and last_candle['sma60_deriv1'] > 0) :
|
||||
# return None
|
||||
|
||||
|
||||
# ----- 4) OFFSET : faut-il attendre de dépasser trailing_stop_positive_offset ? -----
|
||||
if current_trailing_only_offset_is_reached and max_profit > current_trailing_stop_positive_offset:
|
||||
# Max profit pas atteint ET perte < 2 * current_trailing_stop_positive
|
||||
if profit > max_profit * (1 - current_trailing_stop_positive): # 2 * current_trailing_stop_positive:
|
||||
print(f"{current_time} trailing non atteint trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} "
|
||||
f"{min(2, max_profit * (1 - current_trailing_stop_positive))}")
|
||||
print(
|
||||
f"{current_time} trailing non atteint trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} "
|
||||
f"max={round(max_profit, 4)} offset={current_trailing_stop_positive_offset}")
|
||||
return None # ne pas activer le trailing encore
|
||||
else:
|
||||
print(f"{current_time} trailing atteint trailing_stop={round(trailing_stop,4)} profit={round(profit, 4)} max={round(max_profit, 4)} "
|
||||
f"{min(2,max_profit * (1 - current_trailing_stop_positive))}")
|
||||
print(
|
||||
f"{current_time} trailing atteint trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} "
|
||||
f"max={round(max_profit, 4)} offset={current_trailing_stop_positive_offset}")
|
||||
else:
|
||||
return None
|
||||
# Sinon : trailing actif dès le début
|
||||
|
||||
# ----- 6) Condition de vente -----
|
||||
if 0 < profit <= trailing_stop and last_candle['mid'] < last_candle['sma5']:
|
||||
if 0 < profit <= trailing_stop: # and last_candle['mid'] < last_candle['sma5']: # and profit > current_trailing_stop_positive_offset:
|
||||
self.pairs[pair]['force_buy'] = True
|
||||
print(
|
||||
f"{current_time} Condition de vente trailing_stop={round(trailing_stop, 4)} profit={round(profit, 4)} max={round(max_profit, 4)} "
|
||||
f"{round(min(2, max_profit * (1 - current_trailing_stop_positive)), 4)}")
|
||||
f"{round(min(2, max_profit * (1 - current_trailing_stop_positive)), 4)} offset={current_trailing_stop_positive_offset}")
|
||||
|
||||
return f"stop_{count_of_buys}_{self.pairs[pair]['has_gain']}"
|
||||
return None
|
||||
@@ -1499,7 +1585,6 @@ class FrictradeLearning(IStrategy):
|
||||
#
|
||||
# return df
|
||||
|
||||
|
||||
def rsi_trend_probability(self, dataframe, short=6, long=12):
|
||||
dataframe = dataframe.copy()
|
||||
|
||||
@@ -1523,9 +1608,9 @@ class FrictradeLearning(IStrategy):
|
||||
return pd.to_datetime(x, utc=True)
|
||||
|
||||
# suppose self.btc_ath_history exists (liste de dict)
|
||||
def get_last_ath_before_candle(self, last_candle):
|
||||
def get_last_ath_before_candle(self, date):
|
||||
|
||||
candle_date = self.to_utc_ts(last_candle['date']) # ou to_utc_ts(last_candle.name)
|
||||
candle_date = self.to_utc_ts(date) # ou to_utc_ts(last_candle.name)
|
||||
best = None
|
||||
for a in self.btc_ath_history: # getattr(self, "btc_ath_history", []):
|
||||
ath_date = self.to_utc_ts(a["date"])
|
||||
@@ -1717,6 +1802,7 @@ class FrictradeLearning(IStrategy):
|
||||
best_f1 = max(f1_score(y_valid, (proba > t)) for t in thresholds)
|
||||
|
||||
return best_f1
|
||||
|
||||
study = optuna.create_study(direction="maximize")
|
||||
study.optimize(objective, n_trials=20)
|
||||
|
||||
@@ -1801,7 +1887,6 @@ class FrictradeLearning(IStrategy):
|
||||
fig = plot_parallel_coordinate(study)
|
||||
fig.write_html(f"{self.path}/parallel_coordinates.html")
|
||||
|
||||
|
||||
# 2️⃣ Sélection des features AVANT calibration
|
||||
sfm = SelectFromModel(self.train_model, threshold="median", prefit=True)
|
||||
selected_features = X_train.columns[sfm.get_support()]
|
||||
@@ -1897,7 +1982,6 @@ class FrictradeLearning(IStrategy):
|
||||
joblib.dump(self.train_model, f"{self.path}/{pair}_rf_model.pkl")
|
||||
print(f"✅ Modèle sauvegardé sous {pair}_rf_model.pkl")
|
||||
|
||||
|
||||
# X = dataframe des features (après shift/rolling/indicators)
|
||||
# y = target binaire ou décimale
|
||||
# model = ton modèle entraîné (RandomForestClassifier ou Regressor)
|
||||
@@ -2226,15 +2310,19 @@ class FrictradeLearning(IStrategy):
|
||||
and not c.startswith('stop_buying')
|
||||
and not c.startswith('target')
|
||||
and not c.startswith('lvl')
|
||||
and not c.startswith('sma5_deriv1_1h')
|
||||
and not c.startswith('sma5_1h')
|
||||
and not c.startswith('sma12_deriv1_1h')
|
||||
and not c.startswith('sma12_1h')
|
||||
and not c.startswith('confidence_index')
|
||||
]
|
||||
# Étape 3 : remplacer inf et NaN par 0
|
||||
dataframe[usable_cols] = dataframe[usable_cols].replace([np.inf, -np.inf], 0).fillna(0)
|
||||
print("Colonnes utilisables pour le modèle :")
|
||||
print(usable_cols)
|
||||
# print("Colonnes utilisables pour le modèle :")
|
||||
# print(usable_cols)
|
||||
# self.model_indicators = usable_cols
|
||||
return usable_cols
|
||||
|
||||
|
||||
def select_uncorrelated_features(self, df, target, top_n=20, corr_threshold=0.7):
|
||||
"""
|
||||
Sélectionne les features les plus corrélées avec target,
|
||||
@@ -2295,11 +2383,12 @@ class FrictradeLearning(IStrategy):
|
||||
factor1 = 100 * (ema_period / 5)
|
||||
factor2 = 10 * (ema_period / 5)
|
||||
|
||||
dataframe[f"{name}{suffixe}_inv"] = (dataframe[f"{name}{suffixe}"].shift(2) >= dataframe[f"{name}{suffixe}"].shift(1)) \
|
||||
dataframe[f"{name}{suffixe}_inv"] = (dataframe[f"{name}{suffixe}"].shift(2) >= dataframe[
|
||||
f"{name}{suffixe}"].shift(1)) \
|
||||
& (dataframe[f"{name}{suffixe}"].shift(1) <= dataframe[f"{name}{suffixe}"])
|
||||
# --- Distance à la moyenne mobile ---
|
||||
dataframe[f"{name}{suffixe}_dist"] = (dataframe['close'] - dataframe[f"{name}{suffixe}"]) / dataframe[f"{name}{suffixe}"]
|
||||
|
||||
dataframe[f"{name}{suffixe}_dist"] = (dataframe['close'] - dataframe[f"{name}{suffixe}"]) / dataframe[
|
||||
f"{name}{suffixe}"]
|
||||
|
||||
# dérivée relative simple
|
||||
dataframe[d1_col] = (dataframe[name] - dataframe[name].shift(1)) / dataframe[name].shift(1)
|
||||
@@ -2380,6 +2469,44 @@ class FrictradeLearning(IStrategy):
|
||||
|
||||
return dataframe
|
||||
|
||||
def calculateConfiance(self, informative):
|
||||
df = informative.copy()
|
||||
# ATR normalisé
|
||||
df['atr_norm'] = talib.ATR(df['high'], df['low'], df['close'], length=14) / df['close']
|
||||
|
||||
# SMA200 & pente
|
||||
df['sma200'] = talib.SMA(df['close'], 200)
|
||||
df['sma200_slope'] = df['sma200'].diff()
|
||||
|
||||
# drawdown
|
||||
df['rolling_ath'] = df['close'].cummax()
|
||||
df['drawdown'] = (df['close'] - df['rolling_ath']) / df['rolling_ath']
|
||||
|
||||
# volume spike
|
||||
df['vol_spike'] = df['volume'] / df['volume'].rolling(20).mean()
|
||||
|
||||
# RSI courts/longs
|
||||
df['rsi14'] = talib.RSI(df['close'], 14)
|
||||
df['rsi60'] = talib.RSI(df['close'], 60)
|
||||
|
||||
# Scores normalisés
|
||||
df['vol_score'] = 1 - np.clip(df['atr_norm'] / 0.05, 0, 1)
|
||||
df['trend_score'] = 1 / (1 + np.exp(-df['sma200_slope'] * 150))
|
||||
df['dd_score'] = 1 - np.clip(abs(df['drawdown']) / 0.3, 0, 1)
|
||||
df['volpanic_score'] = 1 - np.clip(df['vol_spike'] / 3, 0, 1)
|
||||
df['rsi_score'] = 1 / (1 + np.exp(-(df['rsi14'] - df['rsi60']) / 10))
|
||||
|
||||
# Indice final
|
||||
informative['confidence_index'] = (
|
||||
0.25 * df['vol_score'] +
|
||||
0.25 * df['trend_score'] +
|
||||
0.20 * df['dd_score'] +
|
||||
0.15 * df['volpanic_score'] +
|
||||
0.15 * df['rsi_score']
|
||||
)
|
||||
|
||||
return informative
|
||||
|
||||
def calculModelInformative(self, informative):
|
||||
# préparation
|
||||
# print(df)
|
||||
@@ -2415,7 +2542,6 @@ class FrictradeLearning(IStrategy):
|
||||
# joindre probabilités au df (dernières lignes correspondantes)
|
||||
return probas
|
||||
|
||||
|
||||
def prune_features(self, model, dataframe, feature_columns, importance_threshold=0.01):
|
||||
"""
|
||||
Supprime les features dont l'importance est inférieure au seuil.
|
||||
@@ -2444,6 +2570,6 @@ class FrictradeLearning(IStrategy):
|
||||
|
||||
dataframe_pruned = dataframe[kept_features].fillna(0)
|
||||
|
||||
print(f"⚡ Features conservées ({len(kept_features)} / {len(feature_columns)}): {kept_features}")
|
||||
# print(f"⚡ Features conservées ({len(kept_features)} / {len(feature_columns)}): {kept_features}")
|
||||
|
||||
return dataframe_pruned, kept_features
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.2788135593220339
|
||||
0.14152542372881355
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 21 KiB |
BIN
plots/BTC/crash/indicators_vs_prediction.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
32
tools/sklearn/.ipynb_checkpoints/Sinus-checkpoint.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from lightgbm import LGBMRegressor
|
||||
|
||||
# === Données non linéaires ===
|
||||
np.random.seed(0)
|
||||
X = np.linspace(0, 10, 200).reshape(-1, 1)
|
||||
y = np.sin(X).ravel() + np.random.normal(0, 0.1, X.shape[0]) # sinusoïde + bruit
|
||||
|
||||
# === Entraînement du modèle ===
|
||||
model = LGBMRegressor(
|
||||
n_estimators=300, # nombre d’arbres
|
||||
learning_rate=0.05, # taux d’apprentissage (plus petit = plus lisse)
|
||||
max_depth=5 # profondeur des arbres (plus grand = plus complexe)
|
||||
)
|
||||
model.fit(X, y)
|
||||
|
||||
# === Prédiction ===
|
||||
X_test = np.linspace(0, 10, 500).reshape(-1, 1)
|
||||
y_pred = model.predict(X_test)
|
||||
|
||||
# === Visualisation ===
|
||||
plt.figure(figsize=(10, 5))
|
||||
plt.scatter(X, y, color="lightgray", label="Données réelles (sin + bruit)", s=20)
|
||||
plt.plot(X_test, np.sin(X_test), color="green", linestyle="--", label="sin(x) réel")
|
||||
plt.plot(X_test, y_pred, color="red", label="Prédiction LGBM")
|
||||
plt.title("Approximation non linéaire avec LGBMRegressor")
|
||||
plt.xlabel("x")
|
||||
plt.ylabel("y")
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
plt.show()
|
||||
@@ -445,6 +445,46 @@ class Crash:
|
||||
|
||||
return selected_corr
|
||||
|
||||
def drawPredictions(self, df, indicators, y_proba, threshold=0.5):
|
||||
"""
|
||||
Trace simultanément plusieurs indicateurs et la prédiction du modèle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
df : pd.DataFrame
|
||||
Dataframe avec les colonnes des indicateurs et la target.
|
||||
indicators : list[str]
|
||||
Liste des colonnes du dataframe à tracer.
|
||||
y_proba : np.array
|
||||
Probabilités prédites par le modèle (valeurs continues entre 0 et 1)
|
||||
threshold : float
|
||||
Seuil pour convertir probabilité en signal binaire
|
||||
output_file : str
|
||||
Fichier sur lequel sauvegarder le graphique
|
||||
"""
|
||||
plt.figure(figsize=(18, 6))
|
||||
|
||||
# Tracer les indicateurs
|
||||
for col in indicators:
|
||||
plt.plot(df.index, df[col], label=col, alpha=0.7)
|
||||
|
||||
# Tracer la prédiction du modèle (probabilité)
|
||||
plt.plot(df.index, y_proba, label="Prediction prob.", color="black", linestyle="--")
|
||||
|
||||
# Optionnel : signal binaire (1 si prob > threshold)
|
||||
y_signal = (y_proba > threshold).astype(int)
|
||||
plt.scatter(df.index, y_signal, color='red', marker='o', label='Signal > threshold', s=20)
|
||||
|
||||
plt.title("Indicateurs + prédiction MLP")
|
||||
plt.xlabel("Date")
|
||||
plt.ylabel("Valeur / Probabilité")
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{self.path}/indicators_vs_prediction.png")
|
||||
plt.close()
|
||||
|
||||
|
||||
def drawSequentialGraphs(self, model, history, X_train_scaled, X_valid_scaled, y_train,
|
||||
y_valid, thresholds=None, best_threshold=None
|
||||
):
|
||||
@@ -556,6 +596,17 @@ class Crash:
|
||||
# plt.savefig(f"{self.path}/permutation_importance.png")
|
||||
# plt.close()
|
||||
|
||||
# Exemple : on choisit 3 indicateurs du dataframe
|
||||
indicators = ['percent12']
|
||||
|
||||
# y_proba = model.predict(X_valid_scaled).ravel()
|
||||
self.drawPredictions(
|
||||
df=self.dataframe.iloc[-len(y_proba):], # sélectionner la période correspondant à X_valid
|
||||
indicators=indicators,
|
||||
y_proba=y_proba,
|
||||
threshold=0.5
|
||||
)
|
||||
|
||||
# =========================
|
||||
# 7️⃣ Sauvegarde seuil
|
||||
# =========================
|
||||
@@ -567,7 +618,7 @@ class Crash:
|
||||
"best_f1": max(f1s)
|
||||
}
|
||||
|
||||
def optimize_sequential(self, X_train, y_train, X_valid, y_valid, n_trials=20):
|
||||
def optimize_sequential(self, X_train, X_valid, y_train, y_valid , n_trials=20):
|
||||
def objective(trial):
|
||||
tf.keras.backend.clear_session()
|
||||
|
||||
@@ -835,7 +886,7 @@ class Crash:
|
||||
# df['target'] = df['target'].fillna(0).astype(int)
|
||||
|
||||
# label : crash si -n% dans les p heures
|
||||
self.calculateTarget(df)
|
||||
self.initTarget(df)
|
||||
|
||||
self.calculateCorrelation(df)
|
||||
|
||||
@@ -872,7 +923,7 @@ class Crash:
|
||||
assert len(X_train) == len(y_train)
|
||||
assert len(X_valid) == len(y_valid)
|
||||
|
||||
self.train_model, study = self.optimize_sequential(X_train, y_train, X_valid, y_valid, n_trials=50)
|
||||
self.train_model, study = self.optimize_sequential(X_train, X_valid, y_train, y_valid, n_trials=50)
|
||||
|
||||
self.analyseStudy(study)
|
||||
|
||||
@@ -939,7 +990,7 @@ class Crash:
|
||||
# plt.ylabel("Score")
|
||||
# plt.show()
|
||||
|
||||
self.analyze_model(pair, self.train_model, X_train, X_valid, y_train, y_valid)
|
||||
self.analyze_model(self.train_model, X_train, X_valid, y_train, y_valid)
|
||||
|
||||
def analyseImportances(self, selected_features, X_train, X_valid, y_valid):
|
||||
# Feature importance
|
||||
@@ -1137,7 +1188,7 @@ class Crash:
|
||||
|
||||
# FIN SHAP
|
||||
|
||||
def calculateTarget(self, df):
|
||||
def initTarget(self, df):
|
||||
future = df['mid'].shift(-12)
|
||||
df['future_dd'] = (future - df['mid']) / df['mid']
|
||||
df['target'] = (df['future_dd'] > 0.003).astype(int)
|
||||
@@ -1214,7 +1265,7 @@ class Crash:
|
||||
|
||||
print("\n===== ✅ FIN DE L’INSPECTION =====")
|
||||
|
||||
def analyze_model(self, pair, model, X_train, X_valid, y_train, y_valid):
|
||||
def analyze_model(self, model, X_train, X_valid, y_train, y_valid):
|
||||
"""
|
||||
Analyse complète d'un modèle ML supervisé (classification binaire).
|
||||
Affiche performances, importance des features, matrices, seuils, etc.
|
||||
@@ -1398,7 +1449,7 @@ class Crash:
|
||||
df = df.set_index('date')
|
||||
|
||||
# Optionnel : ne garder qu’une plage temporelle
|
||||
df = df["2025-01-01":"2025-07-14"]
|
||||
df = df["2025-01-01":"2025-02-01"]
|
||||
df = df.reset_index('date')
|
||||
|
||||
# Supprimer NaN
|
||||