From 8a1c69e53a63b1aa61aabaafa945622e17d8e661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Delacotte?= Date: Sun, 9 Mar 2025 16:53:02 +0100 Subject: [PATCH] Synchronise --- DecisionTreeStrategy.py | 2 + HammerReversalStrategy.py | 340 +++++++++++++++++++++++++++----------- HeikinAshi.py | 127 ++++++++++++++ Zeus_8_3_2_B_4_2.py | 228 ++++++++++++------------- 4 files changed, 486 insertions(+), 211 deletions(-) create mode 100644 HeikinAshi.py diff --git a/DecisionTreeStrategy.py b/DecisionTreeStrategy.py index d815bee..ac90233 100644 --- a/DecisionTreeStrategy.py +++ b/DecisionTreeStrategy.py @@ -395,6 +395,8 @@ class DecisionTreeStrategy(IStrategy): current_entry_profit: float, current_exit_profit: float, **kwargs ) -> float | None | tuple[float | None, str | None]: + if trade.has_open_orders: + return None # Obtain pair dataframe (just to show how to access it) dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) diff --git a/HammerReversalStrategy.py b/HammerReversalStrategy.py index 8db1f11..34cf2c5 100644 --- a/HammerReversalStrategy.py +++ b/HammerReversalStrategy.py @@ -18,6 +18,15 @@ class HammerReversalStrategy(IStrategy): "main_plot": { "enter_tag": { "color": "#197260" + }, + 'sma5_1d': { + 'color': 'green' + }, + 'bb_upperband_1d': { + 'color': 'blue' + }, + 'bb_lowerband_1d': { + 'color': 'red' } }, "subplots": { @@ -25,10 +34,40 @@ class HammerReversalStrategy(IStrategy): "hammer": { "color": "blue" }, - "inv_hammer": { + "loose_hammer": { + "color": "#c1b255" + }, + "hammer_1h": { + "color": "blue" + }, + "loose_hammer_1h": { + "color": "#c1b255" + }, + "hammer_1d": { + "color": "blue" + }, + "loose_hammer_1d": { "color": "#c1b255" } + }, + 'Percent': { + 'percent3_1d': { + "color": 'pink' + }, + 'percent3': { + "color": 'red' + }, + 'percent5': { + "color": 'green' + }, + 'percent12': { + "color": 'blue' + }, + 'percent48': { + "color": 'yellow' + } } + } } minimal_roi = { @@ -51,58 +90,67 @@ class HammerReversalStrategy(IStrategy): position_adjustment_enable = True columns_logged = False max_entry_position_adjustment = 20 + startup_candle_count = 288 - def new_adjust_trade_position(self, trade, current_time, current_rate, current_profit, min_stake, max_stake, - **kwargs) -> float: - dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) - last_candle = dataframe.iloc[-1].squeeze() - - count_of_buys, hours, days, first_price, last_price = self.getTradeInfos(current_time, trade) - - # Initialisation des user_data (backtest compatible) - if 'dynamic_stoploss' not in trade.user_data: - trade.user_data['dynamic_stoploss'] = first_price * 0.98 # Stoploss initial à -2% - - if hours < 1 or trade.stake_amount >= max_stake: - return 0 - - # Ajustement en cas de perte : renfort à la baisse - if (last_candle['close'] < first_price) and (last_candle['touch_support']) and (current_profit < -0.015 * count_of_buys): - additional_stake = self.config['stake_amount'] - print(f"Adjust Loss - {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys}") - return max(additional_stake, 0) - - # Ajustement en cas de gain : renfort à la hausse - if (last_candle['close'] > first_price) and (current_profit > 0.01 * count_of_buys): - additional_stake = self.config['stake_amount'] - - # Mise à jour du stoploss dynamique (on lock un profit partiel par exemple) - new_stoploss = current_rate * 0.99 # Stoploss dynamique à -1% sous le prix actuel - trade.user_data['dynamic_stoploss'] = max(trade.user_data['dynamic_stoploss'], new_stoploss) - - print(f"Adjust Gain + {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys}") - return max(additional_stake, 0) - - return 0 - + # def new_adjust_trade_position(self, trade, current_time, current_rate, current_profit, min_stake, max_stake, + # **kwargs) -> float: + # dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) + # last_candle = dataframe.iloc[-1].squeeze() + # + # count_of_buys, hours, days, first_price, last_price = self.getTradeInfos(current_time, trade) + # + # # Initialisation des user_data (backtest compatible) + # if 'dynamic_stoploss' not in trade.user_data: + # trade.user_data['dynamic_stoploss'] = first_price * 0.98 # Stoploss initial à -2% + # + # if hours < 1 or trade.stake_amount >= max_stake: + # return 0 + # + # # Ajustement en cas de perte : renfort à la baisse + # if (last_candle['close'] < first_price) and (last_candle['touch_support']) and (current_profit < -0.015 * count_of_buys): + # additional_stake = self.config['stake_amount'] + # print(f"Adjust Loss - {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys}") + # return max(additional_stake, 0) + # + # # Ajustement en cas de gain : renfort à la hausse + # if (last_candle['close'] > first_price) and (current_profit > 0.01 * count_of_buys): + # additional_stake = self.config['stake_amount'] + # + # # Mise à jour du stoploss dynamique (on lock un profit partiel par exemple) + # new_stoploss = current_rate * 0.99 # Stoploss dynamique à -1% sous le prix actuel + # trade.user_data['dynamic_stoploss'] = max(trade.user_data['dynamic_stoploss'], new_stoploss) + # + # print(f"Adjust Gain + {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys}") + # return max(additional_stake, 0) + # + # return 0 def adjust_trade_position(self, trade, current_time, current_rate, current_profit, min_stake, max_stake, **kwargs) -> float: + if trade.has_open_orders: + return None + dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() + last_candle_288 = dataframe.iloc[-288].squeeze() """ Ajuste la position suite à un signal de sortie partielle. """ count_of_buys, hours, days, first_price, last_price = self.getTradeInfos(current_time, trade) - if hours < 1 or trade.stake_amount >= max_stake: + # (hours < 1) or + if (self.wallets.get_available_stake_amount() < 50): # or trade.stake_amount >= max_stake: return 0 dispo = round(self.wallets.get_available_stake_amount()) + factor = 1 + if (count_of_buys > 4): + factor = count_of_buys / 4 + if (count_of_buys > 1) \ and (current_profit > 0.01) \ - and (last_candle['close'] < self.pairs[trade.pair]['max_touch'] * 0.99) \ - and (last_candle['percent5'] < 0): + and (last_candle['haclose'] < self.pairs[trade.pair]['max_touch'] * 0.99) \ + and (last_candle['percent5'] < 0) and (last_candle['percent12'] < 0): # print(f"Adjust Sell all {current_time} rate={current_rate:.3f} stake={trade.stake_amount} count={count_of_buys} profit={profit:.1f}") self.log_trade( @@ -113,14 +161,22 @@ class HammerReversalStrategy(IStrategy): pair=trade.pair, rate=current_rate, trade_type='Sell', - profit=round(trade.calc_profit(current_rate, trade.amount), 2), # round(current_profit * trade.stake_amount, 2), + profit=round(current_profit, 4), # round(current_profit * trade.stake_amount, 2), buys=trade.nr_of_successful_entries, stake=round(- trade.stake_amount, 2) ) + self.pairs[trade.pair]['last_max'] = max(last_candle['haclose'], self.pairs[trade.pair]['last_max']) + self.pairs[trade.pair]['max_touch'] = last_candle['haclose'] + self.pairs[trade.pair]['last_buy'] = last_candle['haclose'] + self.pairs[trade.pair]['last_sell'] = 0 return - trade.stake_amount - if (last_candle['close'] < first_price) and (last_candle['touch_support']) and (current_profit < -0.015 * count_of_buys): - additional_stake = self.calculate_stake(trade.pair, last_candle) + if (last_candle['close'] < first_price) \ + and (last_candle['touch_support']) \ + and ((count_of_buys <= 4 and last_candle_288['sma5_1h'] <= last_candle['sma5_1h']) + or (count_of_buys > 4 and last_candle_288['sma5_1d'] <= last_candle['sma5_1d'])) \ + and (current_profit < -0.015 * count_of_buys * factor): + additional_stake = self.calculate_stake(trade.pair, last_candle, factor) # print(f"Adjust Loss - {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys} profit={profit:.1f}") self.log_trade( @@ -135,11 +191,14 @@ class HammerReversalStrategy(IStrategy): buys=trade.nr_of_successful_entries, stake=round(additional_stake, 2) ) - + self.pairs[trade.pair]['last_max'] = last_candle['haclose'] + self.pairs[trade.pair]['max_touch'] = last_candle['haclose'] + self.pairs[trade.pair]['last_buy'] = 0 + self.pairs[trade.pair]['last_sell'] = last_candle['haclose'] return max(additional_stake, 0) if (last_candle['close'] > first_price) and (current_profit > 0.01 * count_of_buys): - additional_stake = self.calculate_stake(trade.pair, last_candle) + additional_stake = self.calculate_stake(trade.pair, last_candle, 1) self.log_trade( last_candle=last_candle, date=current_time, @@ -152,7 +211,10 @@ class HammerReversalStrategy(IStrategy): buys=count_of_buys, stake=round(additional_stake, 2) ) - + self.pairs[trade.pair]['last_max'] = last_candle['haclose'] + self.pairs[trade.pair]['max_touch'] = last_candle['haclose'] + self.pairs[trade.pair]['last_buy'] = last_candle['haclose'] + self.pairs[trade.pair]['last_sell'] = 0 # print(f"Adjust Gain + {current_time} rate={current_rate:.3f} stake={additional_stake} count={count_of_buys} profit={profit:.1f}") return max(additional_stake, 0) @@ -160,17 +222,17 @@ class HammerReversalStrategy(IStrategy): use_custom_stoploss = True - def new_custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, - current_profit: float, **kwargs) -> float: - - if 'dynamic_stoploss' in trade.user_data: - stoploss_price = trade.user_data['dynamic_stoploss'] - if current_rate < stoploss_price: - print(f"Stoploss touché ! Vente forcée {pair} à {current_rate}") - return 0.001 # on force une sortie immédiate (stop très proche) - - # Sinon on reste sur le stoploss standard de la stratégie - return -1 # Exemple: 5% de perte max + # def new_custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, + # current_profit: float, **kwargs) -> float: + # + # if 'dynamic_stoploss' in trade.user_data: + # stoploss_price = trade.user_data['dynamic_stoploss'] + # if current_rate < stoploss_price: + # print(f"Stoploss touché ! Vente forcée {pair} à {current_rate}") + # return 0.001 # on force une sortie immédiate (stop très proche) + # + # # Sinon on reste sur le stoploss standard de la stratégie + # return -1 # Exemple: 5% de perte max def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, @@ -179,24 +241,26 @@ class HammerReversalStrategy(IStrategy): # Obtenir les données actuelles pour cette paire last_candle = dataframe.iloc[-1].squeeze() - return self.calculate_stake(pair, last_candle) + return self.calculate_stake(pair, last_candle, 1) - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, - current_rate: float, current_profit: float, **kwargs) -> float: - dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - - # Obtenir les données actuelles pour cette paire - last_candle = dataframe.iloc[-1].squeeze() - # self.getTradeInfos(current_time, trade) - # print(f"current_profit={current_profit} mises=" + str(round(self.pairs[pair]['trade_info']['mises'], 4))) - limit_sell = (last_candle['close'] - self.pairs[trade.pair]['max_touch']) / self.pairs[trade.pair]['max_touch'] - - if (current_profit > 0.01) & (limit_sell < -0.01) & (last_candle['percent12'] < 0): # & (limit_sell < -0.01) & (last_candle['DI+_1h'] < 10): - sl_profit = 0.85 * current_profit # n% du profit en cours - else: - sl_profit = -1 # Hard stop-loss - stoploss = stoploss_from_open(sl_profit, current_profit) - return stoploss + # def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + # current_rate: float, current_profit: float, **kwargs) -> float: + # dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + # + # # Obtenir les données actuelles pour cette paire + # last_candle = dataframe.iloc[-1].squeeze() + # # self.getTradeInfos(current_time, trade) + # # print(f"current_profit={current_profit} mises=" + str(round(self.pairs[pair]['trade_info']['mises'], 4))) + # limit_sell = (last_candle['close'] - self.pairs[trade.pair]['max_touch']) / self.pairs[trade.pair]['max_touch'] + # + # if (current_profit > 0.01) & (limit_sell < -0.01) & (last_candle['percent12'] < 0): # & (limit_sell < -0.01) & (last_candle['DI+_1h'] < 10): + # sl_profit = 0.85 * current_profit # n% du profit en cours + # print(f"Stoploss {current_time} {current_rate} set to {sl_profit} / {limit_sell} / {self.pairs[trade.pair]['max_touch']}") + # + # else: + # sl_profit = -1 # Hard stop-loss + # stoploss = stoploss_from_open(sl_profit, current_profit) + # return stoploss def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]': @@ -204,16 +268,20 @@ class HammerReversalStrategy(IStrategy): Custom exit function for dynamic trade exits. """ dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - self.pairs[pair]['max_touch'] = max(current_rate, self.pairs[pair]['max_touch']) + last_candle = dataframe.iloc[-1].squeeze() + # Calcul de la "distance de sécurité" avant stockage dans max_touch + limit_sell = (last_candle['haclose'] - self.pairs[pair]['max_touch']) / self.pairs[pair]['max_touch'] - # # Obtenir les données actuelles pour cette paire - # last_candle = dataframe.iloc[-1].squeeze() - # previous_last_candle = dataframe.iloc[-2].squeeze() - # if (last_candle['percent'] > 0) | (last_candle['percent3'] > 0.0) | (last_candle['percent5'] > 0.0): - # return None - # - # if current_profit > 0 and last_candle['inv_hammer'] > 0: - # return 'Sell_Hammer' + self.pairs[pair]['max_touch'] = max(last_candle['haclose'], self.pairs[pair]['max_touch']) + + + # On ne déclenche le trailing stop que si un profit mini a déjà été atteint + # and (limit_sell < -0.01) + if (current_profit > 0.01) and (last_candle['percent12'] < 0) and (last_candle['percent5'] < 0): + print(f"Custom Exit Triggered - {current_time} - Price: {current_rate:.2f} - Profit: {current_profit:.2%}") + print(f"Max touch: {self.pairs[pair]['max_touch']:.2f}, Limit sell: {limit_sell:.2%}") + + return 'trailing_stop_exit' return None @@ -222,12 +290,15 @@ class HammerReversalStrategy(IStrategy): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() dispo = round(self.wallets.get_available_stake_amount()) - stake_amount = self.calculate_stake(pair, last_candle) + stake_amount = self.calculate_stake(pair, last_candle, 1) - self.pairs[pair]['last_max'] = max(rate, self.pairs[pair]['last_max']) - self.pairs[pair]['max_touch'] = rate - self.pairs[pair]['last_buy'] = rate - + # if (self.pairs[pair]['last_sell'] > 0) and \ + # (self.pairs[pair]['last_sell'] - last_candle['close']) / self.pairs[pair]['last_sell'] < 0.012: + # return False + self.pairs[pair]['last_max'] = max(last_candle['haclose'], self.pairs[pair]['last_max']) + self.pairs[pair]['max_touch'] = last_candle['haclose'] + self.pairs[pair]['last_buy'] = last_candle['haclose'] + self.pairs[pair]['last_sell'] = 0 #print(f"Buy {current_time} {entry_tag} rate={rate:.3f} amount={amount}") self.log_trade( last_candle=last_candle, @@ -255,6 +326,7 @@ class HammerReversalStrategy(IStrategy): # self.pairs[pair]['last_max'] = 0 # self.pairs[pair]['max_touch'] = 0 self.pairs[pair]['last_buy'] = 0 + self.pairs[pair]['last_sell'] = rate self.log_trade( last_candle=last_candle, date=current_time, @@ -270,10 +342,16 @@ class HammerReversalStrategy(IStrategy): return ok def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + heikinashi = qtpylib.heikinashi(dataframe) + + dataframe['haopen'] = heikinashi['open'] + dataframe['haclose'] = heikinashi['close'] + dataframe['hapercent'] = dataframe['haclose'].pct_change() + dataframe['hammer'] = ta.CDLHAMMER(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close']) dataframe['inv_hammer'] = ta.CDLINVERTEDHAMMER(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close']) - # Volume dataframe['volume_mean'] = ta.SMA(dataframe['volume'], timeperiod=20) dataframe['volume_above_avg'] = dataframe['volume'] > 1.2 * dataframe['volume_mean'] @@ -320,22 +398,41 @@ class HammerReversalStrategy(IStrategy): dataframe["percent3"] = dataframe['close'].pct_change(3) dataframe["percent5"] = dataframe['close'].pct_change(5) dataframe["percent12"] = dataframe['close'].pct_change(12) + dataframe["percent48"] = dataframe['close'].pct_change(48) dataframe = self.pattern_hammer(dataframe) dataframe = self.detect_hammer_with_context(dataframe) dataframe = self.detect_loose_hammer(dataframe) #dataframe = self.detect_squeeze_pump(dataframe) + # ====================================================================================== + ################### INFORMATIVE 1h + informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1h") + # informative['hammer'] = ta.CDLHAMMER(informative['open'], informative['high'], informative['low'], informative['close']) + informative = self.detect_loose_hammer(informative) + informative = self.detect_hammer_with_context(informative) + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(informative), window=20, stds=2) + informative['bb_lowerband'] = bollinger['lower'] + informative['bb_middleband'] = bollinger['mid'] + informative['bb_upperband'] = bollinger['upper'] + informative['sma5'] = ta.SMA(informative, timeperiod=5) + informative["percent3"] = informative['close'].pct_change(3) + dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1h", ffill=True) + # ====================================================================================== ################### INFORMATIVE 1d informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d") # informative['hammer'] = ta.CDLHAMMER(informative['open'], informative['high'], informative['low'], informative['close']) informative = self.detect_loose_hammer(informative) - informative['max7'] = ta.MAX(informative['close'], timeperiod=7) + informative = self.detect_hammer_with_context(informative) + informative['sma5'] = ta.SMA(informative, timeperiod=5) + informative["percent3"] = informative['sma5'].pct_change(3) + + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(informative), window=20, stds=2) + informative['bb_lowerband'] = bollinger['lower'] + informative['bb_middleband'] = bollinger['mid'] + informative['bb_upperband'] = bollinger['upper'] - informative['bb_upperband'], informative['bb_middleband'], informative['bb_lowerband'] = ta.BBANDS( - informative['close'], timeperiod=20 - ) dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True) dataframe['hammer_marker'] = np.where(dataframe['hammer_signal'], dataframe['low'] * 0.99, np.nan) @@ -355,7 +452,7 @@ class HammerReversalStrategy(IStrategy): , # Divergence possible ['enter_long', 'enter_tag']] = [1, 'buy_hammer'] - # dataframe.loc[ + # d ataframe.loc[ # (dataframe['hammer2'] > 0) # # & (dataframe['close'] < dataframe['bb_middleband']) # # (dataframe['volume_above_avg']) & @@ -367,7 +464,8 @@ class HammerReversalStrategy(IStrategy): # , # Divergence possible # ['enter_long', 'enter_tag']] = [1, 'buy_hammer2'] dataframe.loc[ - (dataframe['loose_hammer'] > 0) + (dataframe['percent3'] < - 0.005) + & (dataframe['percent48'] < 0.02) , # Divergence possible ['enter_long', 'enter_tag']] = [1, 'buy_loose_hammer'] @@ -585,17 +683,61 @@ class HammerReversalStrategy(IStrategy): sma5 = str(sma5_1d) + ' ' + str(sma5_1h) first_rate = self.pairs[pair]['last_max'] - if action != 'Sell': - profit = round((last_candle['close'] - self.pairs[pair]['last_max']) / self.pairs[pair]['last_max'], 2) + # if action != 'Sell': + # profit = round((last_candle['close'] - self.pairs[pair]['last_max']) / self.pairs[pair]['last_max'], 2) + limit_sell = rsi_pct # round((last_candle['close'] - self.pairs[pair]['last_max']) / self.pairs[pair]['last_max'], 4) - max7_1d = last_candle['max7_1d'] #round(100 * (last_candle['close'] - self.pairs[pair]['last_max']) / self.pairs[pair]['last_max'], 1) + max7_1d = round(self.pairs[pair]['max_touch'], 1) #last_candle['max7_1d'] #round(100 * (last_candle['close'] - self.pairs[pair]['last_max']) / self.pairs[pair]['last_max'], 1) pct_max = round(100 * (last_candle['close'] - max7_1d) / max7_1d, 1) print( - f"| {date:<16} | {action:<10} | {pair:<10} | {trade_type or '-':<18} | {rate or '-':>12} | {dispo or '-':>6} | {profit or '-':>8} | {pct_max or '-':>5} | {max7_1d or '-':>11} | {self.pairs[pair]['max_touch'] or '-':>12} | {self.pairs[pair]['last_max'] or '-':>12} | {buys or '-':>5} | {stake or '-':>10} |" + f"| {date:<16} | {action:<10} | {pair:<10} | {trade_type or '-':<18} | {rate or '-':>12} | {dispo or '-':>6} | {profit or '-':>8} | {pct_max or '-':>5} | {max7_1d or '-':>11} | {round(self.pairs[pair]['max_touch'], 2) or '-':>12} | {round(self.pairs[pair]['last_max'],2) or '-':>12} | {buys or '-':>5} | {stake or '-':>10} |" ) - def calculate_stake(self, pair, last_candle): - factor = 1 - 2 * (last_candle['close'] - last_candle['max7_1d']) / last_candle['max7_1d'] - + def calculate_stake(self, pair, last_candle, factor=1): amount = self.config['stake_amount'] * factor #1000 / self.first_stack_factor.value self.protection_stake_amount.value # return amount + + @property + def protections(self): + return [ + { + "method": "CooldownPeriod", + "stop_duration_candles": 12 + } + # { + # "method": "MaxDrawdown", + # "lookback_period_candles": self.lookback.value, + # "trade_limit": self.trade_limit.value, + # "stop_duration_candles": self.protection_stop.value, + # "max_allowed_drawdown": self.protection_max_allowed_dd.value, + # "only_per_pair": False + # }, + # { + # "method": "StoplossGuard", + # "lookback_period_candles": 24, + # "trade_limit": 4, + # "stop_duration_candles": self.protection_stoploss_stop.value, + # "only_per_pair": False + # }, + # { + # "method": "StoplossGuard", + # "lookback_period_candles": 24, + # "trade_limit": 4, + # "stop_duration_candles": 2, + # "only_per_pair": False + # }, + # { + # "method": "LowProfitPairs", + # "lookback_period_candles": 6, + # "trade_limit": 2, + # "stop_duration_candles": 60, + # "required_profit": 0.02 + # }, + # { + # "method": "LowProfitPairs", + # "lookback_period_candles": 24, + # "trade_limit": 4, + # "stop_duration_candles": 2, + # "required_profit": 0.01 + # } + ] \ No newline at end of file diff --git a/HeikinAshi.py b/HeikinAshi.py new file mode 100644 index 0000000..e5c7d3d --- /dev/null +++ b/HeikinAshi.py @@ -0,0 +1,127 @@ +# Heracles Strategy: Strongest Son of GodStra +# ( With just 1 Genome! its a bacteria :D ) +# Author: @Mablue (Masoud Azizi) +# github: https://github.com/mablue/ +# IMPORTANT:Add to your pairlists inside config.json (Under StaticPairList): +# { +# "method": "AgeFilter", +# "min_days_listed": 100 +# }, +# IMPORTANT: INSTALL TA BEFOUR RUN(pip install ta) +# +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --spaces roi buy --strategy Heracles +# ###################################################################### +# --- Do not remove these libs --- +from freqtrade.strategy.parameters import IntParameter, DecimalParameter +from freqtrade.strategy.interface import IStrategy +from pandas import DataFrame +# -------------------------------- +# Add your lib to import here +# import talib.abstract as ta +import pandas as pd +import ta +from ta.utils import dropna +import freqtrade.vendor.qtpylib.indicators as qtpylib +from functools import reduce +import numpy as np + + +class Heracles(IStrategy): + ########################################## RESULT PASTE PLACE ########################################## + # 10/100: 25 trades. 18/4/3 Wins/Draws/Losses. Avg profit 5.92%. Median profit 6.33%. Total profit 0.04888306 BTC ( 48.88Σ%). Avg duration 4 days, 6:24:00 min. Objective: -11.42103 + + # Buy hyperspace params: + buy_params = { + "buy_crossed_indicator_shift": 9, + "buy_div_max": 0.75, + "buy_div_min": 0.16, + "buy_indicator_shift": 15, + } + + # Sell hyperspace params: + sell_params = { + } + + # ROI table: + minimal_roi = { + "0": 0.598, + "644": 0.166, + "3269": 0.115, + "7289": 0 + } + + # Stoploss: + stoploss = -0.256 + + # Optimal timeframe use it in your config + timeframe = '4h' + + # Trailing stoploss + trailing_stop = True + trailing_stop_positive = 0.001 + trailing_stop_positive_offset = 0.015 + trailing_only_offset_is_reached = True + + ########################################## END RESULT PASTE PLACE ###################################### + + # buy params + buy_div_min = DecimalParameter(0, 1, default=0.16, decimals=2, space='buy') + buy_div_max = DecimalParameter(0, 1, default=0.75, decimals=2, space='buy') + buy_indicator_shift = IntParameter(0, 20, default=16, space='buy') + buy_crossed_indicator_shift = IntParameter(0, 20, default=9, space='buy') + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + dataframe = dropna(dataframe) + + dataframe['volatility_kcw'] = ta.volatility.keltner_channel_wband( + dataframe['high'], + dataframe['low'], + dataframe['close'], + window=20, + window_atr=10, + fillna=False, + original_version=True + ) + + dataframe['volatility_dcp'] = ta.volatility.donchian_channel_pband( + dataframe['high'], + dataframe['low'], + dataframe['close'], + window=10, + offset=0, + fillna=False + ) + + return dataframe + + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Buy strategy Hyperopt will build and use. + """ + conditions = [] + + IND = 'volatility_dcp' + CRS = 'volatility_kcw' + DFIND = dataframe[IND] + DFCRS = dataframe[CRS] + + d = DFIND.shift(self.buy_indicator_shift.value).div( + DFCRS.shift(self.buy_crossed_indicator_shift.value)) + + # print(d.min(), "\t", d.max()) + conditions.append( + d.between(self.buy_div_min.value, self.buy_div_max.value)) + + if conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'buy']=1 + + return dataframe + + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Sell strategy Hyperopt will build and use. + """ + dataframe.loc[:, 'sell'] = 0 + return dataframe diff --git a/Zeus_8_3_2_B_4_2.py b/Zeus_8_3_2_B_4_2.py index 69b7ae2..4542390 100644 --- a/Zeus_8_3_2_B_4_2.py +++ b/Zeus_8_3_2_B_4_2.py @@ -24,6 +24,7 @@ import ta import talib.abstract as talib import freqtrade.vendor.qtpylib.indicators as qtpylib import requests +from datetime import timezone, timedelta logger = logging.getLogger(__name__) @@ -233,7 +234,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # allow_to_buy = rate <= dataframe['lbp_3'] self.trades = list() dispo = round(self.wallets.get_available_stake_amount()) - logger.info(f"{pair} allow_to_buy {allow_to_buy} limit={limit} Buy {entry_tag} {current_time} dispo={dispo}") + logger.info(f"BUY {pair} allow_to_buy {allow_to_buy} limit={limit} Buy {entry_tag} {current_time} dispo={dispo}") return allow_to_buy @@ -250,14 +251,14 @@ class Zeus_8_3_2_B_4_2(IStrategy): if allow_to_sell: self.trades = list() - logger.info('Sell trade ' + exit_reason + ' ' + str(current_time) + ' ' + pair + " dispo=" + str( + logger.info('Sell ' + exit_reason + ' ' + str(current_time) + ' ' + pair + " dispo=" + str( round(self.wallets.get_available_stake_amount())) # "+ str(amount) + ' ' + str(rate) + " open_rate=" + str(trade.open_rate) + " rate=" + str(rate) + " profit=" + str( trade.calc_profit(rate, amount)) + " " + string) # del self.max_profit_pairs[pair] else: - logger.info('Cancel Sell trade ' + exit_reason + ' ' + str(current_time) + ' ' + pair) + logger.info('Cancel Sell ' + exit_reason + ' ' + str(current_time) + ' ' + pair) return (allow_to_sell) | (exit_reason == 'force_exit') def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, @@ -272,36 +273,35 @@ class Zeus_8_3_2_B_4_2(IStrategy): # Use default stake amount. return adjusted_stake_amount - - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, - current_rate: float, current_profit: float, **kwargs) -> float: - - # # hard stoploss profit - # HSL = self.pHSL.value - # PF_1 = self.pPF_1.value - # SL_1 = self.pSL_1.value - # PF_2 = self.pPF_2.value - # SL_2 = self.pSL_2.value - # - # # For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated - # # between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value - # # rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used. - # - # if current_profit > PF_2: - # sl_profit = SL_2 + (current_profit - PF_2) - # elif current_profit > PF_1: - # sl_profit = SL_1 + ((current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1)) - # else: - # sl_profit = HSL - - #print(f"entry_tag={trade.entry_tag} max={trade.max_rate} min={trade.min_rate} ") - if current_profit > 0.0125: - sl_profit = 0.75 * current_profit # 75% du profit en cours - else: - sl_profit = self.pHSL.value # Hard stop-loss - stoploss = stoploss_from_open(sl_profit, current_profit) - logger.info(f"stoploss={stoploss}") - return stoploss + # + # def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + # current_rate: float, current_profit: float, **kwargs) -> float: + # + # # # hard stoploss profit + # # HSL = self.pHSL.value + # # PF_1 = self.pPF_1.value + # # SL_1 = self.pSL_1.value + # # PF_2 = self.pPF_2.value + # # SL_2 = self.pSL_2.value + # # + # # # For profits between PF_1 and PF_2 the stoploss (sl_profit) used is linearly interpolated + # # # between the values of SL_1 and SL_2. For all profits above PL_2 the sl_profit value + # # # rises linearly with current profit, for profits below PF_1 the hard stoploss profit is used. + # # + # # if current_profit > PF_2: + # # sl_profit = SL_2 + (current_profit - PF_2) + # # elif current_profit > PF_1: + # # sl_profit = SL_1 + ((current_profit - PF_1) * (SL_2 - SL_1) / (PF_2 - PF_1)) + # # else: + # # sl_profit = HSL + # + # #print(f"entry_tag={trade.entry_tag} max={trade.max_rate} min={trade.min_rate} ") + # if current_profit > 0.0125: + # sl_profit = 0.75 * current_profit # 75% du profit en cours + # else: + # sl_profit = self.pHSL.value # Hard stop-loss + # stoploss = stoploss_from_open(sl_profit, current_profit) + # return stoploss # # dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) @@ -347,33 +347,33 @@ class Zeus_8_3_2_B_4_2(IStrategy): # print('current_profit=' + str(current_profit) + ' stop from open=' + str(slfo)) # return slfo - # def custom_exit(self, pair: str, trade: Trade, current_time, current_rate, current_profit, **kwargs): - # - # dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - # last_candle = dataframe.iloc[-1].squeeze() - # - # # self.analyze_conditions(pair, dataframe) - # - # print("---------------" + pair + "----------------") - # expected_profit = self.expectedProfit(pair, last_candle) - # - # dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - # last_candle = dataframe.iloc[-1] - # - # # Calcul du prix cible basé sur l'ATR - # atr_take_profit = trade.open_rate + (last_candle['atr'] * 2) # Prendre profit à 2x l'ATR - # - # logger.info(f"{pair} Custom exit atr_take_profit={atr_take_profit:.4f}") - # # if current_rate >= atr_take_profit: - # # return 'sell_atr_take_profit' - # - # if (last_candle['percent3'] < -0.002) & (last_candle['percent12'] < 0) & ( - # current_profit > last_candle['min_max200'] / 2): - # self.trades = list() - # return 'min_max200' - # if (last_candle['percent12'] <= -0.01) & (current_profit >= expected_profit): - # self.trades = list() - # return 'profit' + def custom_exit(self, pair: str, trade: Trade, current_time, current_rate, current_profit, **kwargs): + + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = dataframe.iloc[-1].squeeze() + + # self.analyze_conditions(pair, dataframe) + + # print("---------------" + pair + "----------------") + expected_profit = self.expectedProfit(pair, last_candle) + + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = dataframe.iloc[-1] + + # Calcul du prix cible basé sur l'ATR + atr_take_profit = trade.open_rate + (last_candle['atr'] * 2) # Prendre profit à 2x l'ATR + + # logger.info(f"{pair} Custom exit atr_take_profit={atr_take_profit:.4f}") + # if current_rate >= atr_take_profit: + # return 'sell_atr_take_profit' + + if (last_candle['percent3'] < -0.002) & (last_candle['percent12'] < 0) & ( + current_profit > last_candle['min_max200'] / 2): + self.trades = list() + return 'min_max200' + if (last_candle['percent12'] <= -0.01) & (current_profit >= expected_profit): + self.trades = list() + return 'profit' def informative_pairs(self): # get access to all pairs available in whitelist. @@ -548,6 +548,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): dataframe['limit'] = dataframe['last_price'] * (1 - self.baisse[count] / 100) dataframe['amount'] = amount print(f"amount= {amount}") + # trades = Trade.get_trades([Trade.is_open is False]).all() trades = Trade.get_trades_proxy(is_open=False, pair=metadata['pair']) if trades: @@ -627,19 +628,19 @@ class Zeus_8_3_2_B_4_2(IStrategy): self.trades = Trade.get_open_trades() return self.trades - def getTrade(self, pair): - trades = self.getOpenTrades() - trade_for_pair = None - for trade in trades: - if trade.pair == pair: - trade_for_pair = trade - break - return trade_for_pair + # def getTrade(self, pair): + # trades = self.getOpenTrades() + # trade_for_pair = None + # for trade in trades: + # if trade.pair == pair: + # trade_for_pair = trade + # break + # return trade_for_pair def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: pair = metadata['pair'] - self.getOpenTrades() + # self.getOpenTrades() expected_profit = self.expectedProfit(pair, dataframe.iloc[-1]) # self.getBinanceOrderBook(pair, dataframe) last_candle = dataframe.iloc[-1].squeeze() @@ -648,7 +649,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # self.updateLastValue(dataframe, 'expected_profit', expected_profit) print("---------------" + pair + "----------------") print('adjust stake amount ' + str(self.adjust_stake_amount(pair, dataframe.iloc[-1]))) - print('adjust exit price ' + str(self.adjust_exit_price(dataframe.iloc[-1]))) + # print('adjust exit price ' + str(self.adjust_exit_price(dataframe.iloc[-1]))) print('calcul expected_profit ' + str(expected_profit)) buy_level = dataframe['buy_level'] # self.get_buy_level(pair, dataframe) @@ -705,7 +706,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): ) & (dataframe['percent'] >= -0.0005) & (dataframe['min12'].shift(2) == dataframe['min12']) - ), ['buy', 'enter_tag']] = (1, 'buy_min_max200_2') + ), ['enter_long', 'enter_tag']] = (1, 'buy_min_max200_2') dataframe.loc[ ( ((dataframe['count_buys'] > 0) & (dataframe['close'] <= dataframe['limit'])) @@ -715,7 +716,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): (dataframe['min12'].shift(2) == dataframe['min12']) | (dataframe['min200'].shift(60) >= dataframe['min200'] * 1.03) ) - ), ['buy', 'enter_tag']] = (1, 'buy_count_buy') + ), ['enter_long', 'enter_tag']] = (1, 'buy_count_buy') dataframe.loc[ ( @@ -737,7 +738,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): | (dataframe['percent12'] < -0.022) | (dataframe['percent24'] < -0.022) ) - ), ['buy', 'enter_tag']] = (1, 'buy_0_percent12') + ), ['enter_long', 'enter_tag']] = (1, 'buy_0_percent12') dataframe.loc[ ( # (dataframe['percent12'] < -0.015) @@ -745,7 +746,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): & (dataframe['open'] < dataframe['average_line_50']) & (dataframe['close'] < dataframe['min12'] * 1.002) & (dataframe['min12'].shift(2) == dataframe['min12']) - ), ['buy', 'enter_tag']] = (1, 'buy_percent12') + ), ['enter_long', 'enter_tag']] = (1, 'buy_percent12') dataframe.loc[ ( @@ -755,7 +756,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): & (dataframe['min50'].shift(3) == dataframe['min50']) & (dataframe['close'] <= dataframe['min50'] * 1.002) & (dataframe['open'] < dataframe['average_line_288']) - ), ['buy', 'enter_tag']] = (1, 'buy_percent_max_144') + ), ['enter_long', 'enter_tag']] = (1, 'buy_percent_max_144') dataframe.loc[ ( (dataframe['close'] <= dataframe['min200'] * 1.002) @@ -763,7 +764,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): & (dataframe['pct_change'] < 0) & (dataframe['haopen'] < buy_level) & (dataframe['open'] < dataframe['average_line_288']) - ), ['buy', 'enter_tag']] = (1, 'buy_min_max_200') + ), ['enter_long', 'enter_tag']] = (1, 'buy_min_max_200') dataframe.loc[ ( (dataframe['percent_max_144'] <= -0.02) @@ -773,21 +774,21 @@ class Zeus_8_3_2_B_4_2(IStrategy): & (dataframe['close'] <= dataframe['average_line_288_099']) & (dataframe['min50'].shift(3) == dataframe['min50']) & (dataframe['close'] <= dataframe['min50'] * 1.002) - ), ['buy', 'enter_tag']] = (1, 'buy_close_02') - dataframe.loc[ - ( - (dataframe['close'] <= dataframe['lowest_4_average'] * 1.002) - & (dataframe['haopen'] >= dataframe['lbp_3']) - & (dataframe['haclose'] <= dataframe['lbp_3']) - & (dataframe['haopen'] < buy_level) - ), ['buy', 'enter_tag']] = (1, 'buy_lbp_3') + ), ['enter_long', 'enter_tag']] = (1, 'buy_close_02') + # dataframe.loc[ + # ( + # (dataframe['close'] <= dataframe['lowest_4_average'] * 1.002) + # & (dataframe['haopen'] >= dataframe['lbp_3']) + # & (dataframe['haclose'] <= dataframe['lbp_3']) + # & (dataframe['haopen'] < buy_level) + # ), ['enter_long', 'enter_tag']] = (1, 'buy_lbp_3') dataframe.loc[ ( (dataframe['close'] <= dataframe['lowest_4_average'] * 1.002) & (dataframe['haopen'] >= dataframe['average_line_288_098']) & (dataframe['haclose'] <= dataframe['average_line_288_098']) & (dataframe['haopen'] < buy_level) - ), ['buy', 'enter_tag']] = (1, 'buy_average_line_288_098') + ), ['enter_long', 'enter_tag']] = (1, 'buy_average_line_288_098') dataframe.loc[ ( (dataframe['close'].shift(2) <= dataframe['min200']) @@ -797,9 +798,9 @@ class Zeus_8_3_2_B_4_2(IStrategy): & (dataframe['count_buys'] == 0 | ((dataframe['count_buys'] > 0) & (dataframe['close'] <= dataframe['limit'])) ) - ), ['buy', 'enter_tag']] = (1, 'buy_min200') + ), ['enter_long', 'enter_tag']] = (1, 'buy_min200') - dataframe['test'] = np.where(dataframe['buy'] == 1, dataframe['close'] * 1.01, np.nan) + dataframe['test'] = np.where(dataframe['enter_long'] == 1, dataframe['close'] * 1.01, np.nan) return dataframe @@ -810,7 +811,7 @@ class Zeus_8_3_2_B_4_2(IStrategy): # for trade in self.trades: # if trade.pair != pair: # continue - # filled_buys = trade.select_filled_orders('buy') + # filled_buys = trade.select_filled_orders('enter_long') # print('populate_buy_trend filled_buys : ' + str(len(filled_buys))) # # Affichez les valeurs # print(pair, limit) @@ -848,10 +849,10 @@ class Zeus_8_3_2_B_4_2(IStrategy): if count_of_buys >= max_buys: return None - if 'buy' in last_candle: - condition = (last_candle['buy'] == 1) - else: - condition = False + # if 'buy' in last_candle: + # condition = (last_candle['buy'] == 1) + # else: + # condition = False # self.protection_nb_buy_lost.value # limits = ['lbp_3', 'lbp_6', 'lbp_9', 'lbp_12', 'lbp_20'] # limit = last_candle[limits[count_of_buys]] @@ -859,17 +860,20 @@ class Zeus_8_3_2_B_4_2(IStrategy): stake_amount = min(200, self.adjust_stake_amount(pair, last_candle) * self.fibo[count_of_buys]) # print("Adjust " + trade.pair + " time=" + str(current_time) + ' rate=' + str(current_rate) + " buys=" + str(count_of_buys) + " limit=" + str(limit) + " stake=" + str(stake_amount)) - logger.info( - f"Adjust price={trade.pair} buy={condition} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f}") + # logger.info( + # f"Adjust price={trade.pair} buy={condition} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f}") + 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 - if (0 < count_of_buys <= max_buys) & (current_rate <= limit) & (condition): + if (days_since_open > count_of_buys) & (0 < count_of_buys <= max_buys) & (current_rate <= limit) & (last_candle['enter_long'] == 1): try: # This then calculates current safety order size # stake_amount = stake_amount * pow(1.5, count_of_buys) # print("Effective Adjust " + trade.pair + " time=" + str(current_time) + ' rate=' + str(current_rate) + " buys=" + str(count_of_buys) + " limit=" + str(limit) + " stake=" + str(stake_amount)) logger.info( - f"Effective Adjust price={trade.pair} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f}") + f"Adjust {current_time} price={trade.pair} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f}") return stake_amount except Exception as exception: @@ -907,24 +911,24 @@ class Zeus_8_3_2_B_4_2(IStrategy): # adjusted_stake_amount_2 = max(base_stake_amount / 2.5, min(75, base_stake_amount * percent)) - print( - f"Stack amount ajusté price={current_price} max_min={max_min_4:.4f} min_14={min_14_days_4:.4f} max_14={max_14_days_4:.4f} factor={factor_4:.4f} percent={percent_4:.4f} amount={adjusted_stake_amount:.4f}") + # print( + # f"Stack amount ajusté price={current_price} max_min={max_min_4:.4f} min_14={min_14_days_4:.4f} max_14={max_14_days_4:.4f} factor={factor_4:.4f} percent={percent_4:.4f} amount={adjusted_stake_amount:.4f}") # print(f"Stack amount ajusté price={current_price} max_min={max_min:.4f} min_14={min_14_days:.4f} max_14={max_14_days:.4f} factor={factor:.4f} percent={percent:.4f} amount={adjusted_stake_amount_2:.4f}") return adjusted_stake_amount - def adjust_exit_price(self, dataframe: DataFrame): - # Calculer le max des 14 derniers jours - min_14_days = dataframe['lowest_1d'] - max_14_days = dataframe['highest_1d'] - entry_price = dataframe['fbp'] - current_price = dataframe['close'] - percent = 0.5 * (max_14_days - min_14_days) / min_14_days - exit_price = (1 + percent) * entry_price - - print(f"Exit price ajusté price={current_price:.4f} max_14={max_14_days:.4f} exit_price={exit_price:.4f}") - - return exit_price + # def adjust_exit_price(self, dataframe: DataFrame): + # # Calculer le max des 14 derniers jours + # min_14_days = dataframe['lowest_1d'] + # max_14_days = dataframe['highest_1d'] + # entry_price = dataframe['fbp'] + # current_price = dataframe['close'] + # percent = 0.5 * (max_14_days - min_14_days) / min_14_days + # exit_price = (1 + percent) * entry_price + # + # print(f"Exit price ajusté price={current_price:.4f} max_14={max_14_days:.4f} exit_price={exit_price:.4f}") + # + # return exit_price def adjust_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: @@ -954,8 +958,8 @@ class Zeus_8_3_2_B_4_2(IStrategy): min_max = dataframe['pct_min_max_1d'] # (max_14_days - min_14_days) / min_14_days expected_profit = min(0.1, max(0.01, dataframe['min_max200'] * 0.5)) - print( - f"Expected profit price={current_price:.4f} min_max={min_max:.4f} min_14={min_14_days:.4f} max_14={max_14_days:.4f} percent={percent:.4f} expected_profit={expected_profit:.4f}") + # print( + # f"Expected profit price={current_price:.4f} min_max={min_max:.4f} min_14={min_14_days:.4f} max_14={max_14_days:.4f} percent={percent:.4f} expected_profit={expected_profit:.4f}") # self.analyze_conditions(pair, dataframe) return expected_profit