761 lines
34 KiB
Python
761 lines
34 KiB
Python
# 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.persistence import Trade
|
|
from typing import Optional, Tuple, Union
|
|
from datetime import timezone, timedelta, datetime
|
|
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, stoploss_from_open,
|
|
IntParameter, IStrategy, merge_informative_pair, informative, stoploss_from_absolute)
|
|
import logging
|
|
|
|
# noinspection PyUnresolvedReferences
|
|
from freqtrade.strategy import (IStrategy, informative)
|
|
from pandas import DataFrame
|
|
# --------------------------------
|
|
# Add your lib to import here
|
|
# import talib.abstract as ta
|
|
import pandas as pd
|
|
import ta
|
|
import talib.abstract as talib
|
|
from ta.utils import dropna
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
from functools import reduce
|
|
import numpy as np
|
|
from scipy.special import binom
|
|
from ta.trend import SMAIndicator, EMAIndicator, MACD, ADXIndicator
|
|
from ta.momentum import RSIIndicator, StochasticOscillator
|
|
|
|
|
|
class HeikinAshi(IStrategy):
|
|
plot_config = {
|
|
"main_plot": {
|
|
"min288": {
|
|
"color": "#197260"
|
|
},
|
|
'max288': {
|
|
'color': 'green'
|
|
},
|
|
'mid288': {
|
|
'color': 'blue'
|
|
},
|
|
'hasma5': {
|
|
'color': 'red'
|
|
},
|
|
'max48': {
|
|
'color': 'yellow'
|
|
},
|
|
'min48': {
|
|
'color': 'yellow'
|
|
},
|
|
'sma12': {
|
|
'color': 'pink'
|
|
},
|
|
'ema5_1d': {
|
|
'color': "#74effc"
|
|
},
|
|
'ema20_1d': {
|
|
'color': "cyan"
|
|
},
|
|
},
|
|
"subplots": {
|
|
"Percent": {
|
|
"hapercent": {
|
|
"color": "#74effc"
|
|
},
|
|
"percent12": {
|
|
'color': 'blue'
|
|
}
|
|
},
|
|
'up_down': {
|
|
'up_pct': {
|
|
'color': 'red'
|
|
},
|
|
'down_pct': {
|
|
'color': 'blue'
|
|
}
|
|
},
|
|
'tag': {
|
|
'rsi_downtrend': {
|
|
'color': 'red'
|
|
},
|
|
'ma_downtrend': {
|
|
'color': 'blue'
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
# Stoploss:
|
|
stoploss = -1
|
|
|
|
# Optimal timeframe use it in your config
|
|
timeframe = '5m'
|
|
columns_logged = False
|
|
max_entry_position_adjustment = 30
|
|
startup_candle_count = 288
|
|
|
|
# Trailing stoploss
|
|
# trailing_stop = False
|
|
# trailing_stop_positive = 0.001
|
|
# trailing_stop_positive_offset = 0.015
|
|
# trailing_only_offset_is_reached = True
|
|
position_adjustment_enable = True
|
|
dca = {}
|
|
|
|
pairs = {
|
|
pair: {
|
|
"first_buy": 0,
|
|
"last_max": 0,
|
|
"trade_info": {},
|
|
"max_touch": 0.0,
|
|
"last_sell": 0.0,
|
|
"last_buy": 0.0,
|
|
'count_of_buys': 0,
|
|
'current_profit': 0,
|
|
'expected_profit': 0,
|
|
"last_candle": {},
|
|
"last_trade": None,
|
|
'base_stake_amount': 0,
|
|
'stop_buy': False
|
|
}
|
|
for pair in ["BTC/USDT", "ETH/USDT", "DOGE/USDT", "DASH/USDT", "XRP/USDT", "SOL/USDT"]
|
|
}
|
|
|
|
decalage = IntParameter(0, 10, default=3, space='buy')
|
|
|
|
########################################## END RESULT PASTE PLACE #####################################
|
|
# ------------------------------------------------------------------------------------------------------------------
|
|
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
|
current_rate: float, current_profit: float,
|
|
min_stake: Optional[float], max_stake: float,
|
|
current_entry_rate: float, current_exit_rate: float,
|
|
current_entry_profit: float, current_exit_profit: float,
|
|
**kwargs
|
|
) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
|
|
|
|
# ne rien faire si ordre deja en cours
|
|
if trade.has_open_orders:
|
|
return None
|
|
if (self.wallets.get_available_stake_amount() < 50): # or trade.stake_amount >= max_stake:
|
|
return 0
|
|
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
last_candle_3 = dataframe.iloc[-4].squeeze()
|
|
# last_candle_24 = dataframe.iloc[-25].squeeze()
|
|
|
|
# if (last_candle['sma5_diff_1d'] < -0.1):
|
|
# return None
|
|
# prépare les données
|
|
count_of_buys = trade.nr_of_successful_entries
|
|
current_time = current_time.astimezone(timezone.utc)
|
|
open_date = trade.open_date.astimezone(timezone.utc)
|
|
dispo = round(self.wallets.get_available_stake_amount())
|
|
hours = (current_time - trade.date_last_filled_utc).total_seconds() / 3600.0
|
|
pct_max = round((last_candle['close'] - self.pairs[trade.pair]['last_buy']) / self.pairs[trade.pair]['last_buy'], 3)
|
|
|
|
# limit_buy = 5
|
|
# and (count_of_buys < limit_buy) \
|
|
if (hours > 2) \
|
|
and (last_candle['sma5_diff_1h'] > -0.3) \
|
|
and (last_candle['sma5_diff_1d'] > -1) \
|
|
and (last_candle['enter_long'] == 1) \
|
|
and (pct_max < -0.015):
|
|
# and (last_candle_decalage['min12'] == last_candle['min12']) \
|
|
# and (last_candle_decalage['close'] < last_candle_decalage['mid288']):
|
|
additional_stake = self.calculate_stake(trade.pair, last_candle, current_profit) # self.config['stake_amount']
|
|
self.log_trade(
|
|
last_candle=last_candle,
|
|
date=current_time,
|
|
action="Loss -",
|
|
dispo=dispo,
|
|
pair=trade.pair,
|
|
rate=current_rate,
|
|
trade_type=last_candle['enter_tag'],
|
|
profit=round(current_profit, 4), # round(current_profit * trade.stake_amount, 2),
|
|
buys=trade.nr_of_successful_entries + 1,
|
|
stake=round(additional_stake, 2),
|
|
trade=trade
|
|
)
|
|
self.expectedProfit(trade.pair, last_candle)
|
|
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 additional_stake
|
|
|
|
# pct_limit = (-0.015 * limit_buy) + (- 0.03 * (count_of_buys - limit_buy))
|
|
# if (hours > 6) and (count_of_buys >= limit_buy) \
|
|
# and (last_candle['sma5_diff_1d'] > -1.5) \
|
|
# and (last_candle['sma5_diff_1h'] > -0.3) \
|
|
# and (pct_max < pct_limit or (hours > 24 and last_candle['close'] < self.pairs[trade.pair]['last_buy'])) \
|
|
# and ((last_candle['enter_long'] == 1) or
|
|
# (last_candle['percent48'] < - 0.03 and last_candle['min200'] == last_candle_3['min200'])):
|
|
# additional_stake = self.calculate_stake(trade.pair, last_candle, current_profit) #* (-current_profit / 0.1)
|
|
# self.log_trade(
|
|
# last_candle=last_candle,
|
|
# date=current_time,
|
|
# action="Loss -",
|
|
# dispo=dispo,
|
|
# pair=trade.pair,
|
|
# rate=current_rate,
|
|
# trade_type=last_candle['enter_tag'],
|
|
# profit=round(current_profit, 4), # round(current_profit * trade.stake_amount, 2),
|
|
# buys=trade.nr_of_successful_entries + 1,
|
|
# stake=round(additional_stake, 2),
|
|
# trade=trade
|
|
# )
|
|
# self.expectedProfit(trade.pair, last_candle)
|
|
# 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 additional_stake
|
|
|
|
# if (current_profit > 0) and (count_of_buys >= limit_buy) and (hours > 24) and (last_candle['enter_long'] == 1):
|
|
# additional_stake = self.calculate_stake(trade.pair, last_candle, 1)
|
|
# self.log_trade(
|
|
# last_candle=last_candle,
|
|
# date=current_time,
|
|
# action="Gain +",
|
|
# dispo=dispo,
|
|
# pair=trade.pair,
|
|
# rate=current_rate,
|
|
# trade_type=last_candle['enter_tag'],
|
|
# profit=round(current_profit, 4), # round(current_profit * trade.stake_amount, 2),
|
|
# buys=trade.nr_of_successful_entries + 1,
|
|
# stake=round(additional_stake, 2)
|
|
# )
|
|
# self.expectedProfit(trade.pair, last_candle)
|
|
# 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 additional_stake
|
|
return None
|
|
|
|
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
|
proposed_stake: float, min_stake: float, max_stake: 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()
|
|
|
|
return self.calculate_stake(pair, last_candle, 0)
|
|
|
|
def calculate_stake(self, pair, last_candle, current_profit=0.0):
|
|
|
|
pct_max = round((last_candle['close'] - self.pairs[pair]['first_buy']) / self.pairs[pair]['first_buy'], 3) # round(100 * self.pairs[pair]['current_profit'], 1)
|
|
# print(pct_max)
|
|
if current_profit == 0:
|
|
m = max(last_candle['max12_1d'], last_candle['close'])
|
|
stake_amount = self.config['stake_amount']
|
|
if last_candle['close'] > round(last_candle['max12_1d'], 1):
|
|
stake_amount = stake_amount / 2
|
|
else:
|
|
if last_candle['min12_1d'] > 0:
|
|
if last_candle['close'] < last_candle['min12_1d']:
|
|
stake_amount = stake_amount * 2
|
|
else:
|
|
if last_candle['close'] < last_candle['min12_1d'] + (last_candle['max12_1d'] - last_candle['min12_1d']) / 2 :
|
|
stake_amount = stake_amount * 1.5
|
|
self.pairs[pair]['base_buy'] = stake_amount
|
|
else:
|
|
stake_amount = self.pairs[pair]['base_buy']
|
|
|
|
if pct_max < 0:
|
|
stake_amount = stake_amount * ((1 - pct_max * 3) - pct_max / 0.1)
|
|
|
|
|
|
|
|
return min(self.wallets.get_available_stake_amount(), stake_amount)
|
|
|
|
# def dca_strategy(self, base_price = 10000, drop_percent=20, steps=7, method="linear"):
|
|
#
|
|
# """
|
|
# Calcule la répartition des mises en fonction du pourcentage de baisse et du nombre de paliers.
|
|
#
|
|
# :param total_amount: Montant total à investir (ex: 2000€)
|
|
# :param drop_percent: Pourcentage total de baisse (ex: 20%)
|
|
# :param steps: Nombre de paliers d'achat (ex: 5)
|
|
# :param method: Méthode de répartition: "linear", "exponential", "custom"
|
|
# :return: Liste des mises et des prix d'achat
|
|
# """
|
|
# base_price = 10000 # Prix initial du BTC (modifiable)
|
|
# prices = [base_price * (1 - (drop_percent / 100) * (i / (steps - 1))) for i in range(steps)]
|
|
# total_amount = self.wallets.get_available_stake_amount()
|
|
# if method == "linear":
|
|
# amounts = [total_amount / steps] * steps # Montant égal à chaque palier
|
|
# elif method == "exponential":
|
|
# weights = [2 ** i for i in range(steps)]
|
|
# total_weight = sum(weights)
|
|
# amounts = [(w / total_weight) * total_amount for w in weights]
|
|
# elif method == "custom":
|
|
# weights = [1, 2, 3, 4, 5] # Exemple de répartition personnalisée
|
|
# total_weight = sum(weights)
|
|
# amounts = [(w / total_weight) * total_amount for w in weights]
|
|
# else:
|
|
# raise ValueError("Méthode invalide. Choisir 'linear', 'exponential' ou 'custom'.")
|
|
#
|
|
# return {"prices": prices, "amounts": amounts}
|
|
|
|
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:
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
dispo = round(self.wallets.get_available_stake_amount())
|
|
|
|
# if last_candle['max12_1d'] > 0 and last_candle['close'] > last_candle['max5_1d']:
|
|
# self.log_trade(
|
|
# last_candle=last_candle,
|
|
# date=current_time,
|
|
# action="CANCEL BUY",
|
|
# pair=pair,
|
|
# rate=rate,
|
|
# dispo=dispo,
|
|
# profit=0,
|
|
# stake=0
|
|
# )
|
|
# return False
|
|
|
|
# Exemple d'utilisation :
|
|
# self.dca = self.dca_strategy(base_price=last_candle['close'], method="exponential")
|
|
# print(self.dca)
|
|
|
|
if last_candle['sma5_diff_1d'] < - 0.6:
|
|
self.log_trade(
|
|
last_candle=last_candle,
|
|
date=current_time,
|
|
action="CANCEL BUY",
|
|
pair=pair,
|
|
rate=rate,
|
|
dispo=dispo,
|
|
profit=0,
|
|
stake=0,
|
|
trade_type='cancel'
|
|
)
|
|
return False
|
|
self.pairs[pair]['first_buy'] = rate
|
|
self.pairs[pair]['last_buy'] = rate
|
|
self.pairs[pair]['max_touch'] = last_candle['close']
|
|
self.pairs[pair]['last_candle'] = last_candle
|
|
self.pairs[pair]['count_of_buys'] = 1
|
|
self.pairs[pair]['current_profit'] = 0
|
|
stake_amount = self.calculate_stake(pair, last_candle, 0)
|
|
|
|
self.expectedProfit(pair, last_candle)
|
|
|
|
# if self.pairs[pair]['stop_buy']:
|
|
# if last_candle['sma5_diff_1d'] > 0:
|
|
# self.pairs[pair]['stop_buy'] = False
|
|
# else:
|
|
# self.log_trade(
|
|
# last_candle=last_candle,
|
|
# date=current_time,
|
|
# action="CANCEL BUY",
|
|
# pair=pair,
|
|
# rate=rate,
|
|
# dispo=dispo,
|
|
# profit=0,
|
|
# trade_type='stop_buy',
|
|
# buys=1,
|
|
# stake=0
|
|
# )
|
|
# return False
|
|
# self.columns_logged = False
|
|
print(
|
|
f"|{'-' * 18}+{'-' * 12}+{'-' * 12}+{'-' * 20}+{'-' * 14}+{'-' * 8}+{'-' * 10}+{'-' * 7}+{'-' * 13}+{'-' * 14}+{'-' * 14}+{'-' * 7}+{'-' * 12}|"
|
|
)
|
|
|
|
self.log_trade(
|
|
last_candle=last_candle,
|
|
date=current_time,
|
|
action="START BUY",
|
|
pair=pair,
|
|
rate=rate,
|
|
dispo=dispo,
|
|
profit=0,
|
|
trade_type=entry_tag,
|
|
buys=1,
|
|
stake=round(stake_amount, 2)
|
|
)
|
|
return True
|
|
|
|
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float,
|
|
time_in_force: str,
|
|
exit_reason: str, current_time, **kwargs) -> bool:
|
|
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
dispo = round(self.wallets.get_available_stake_amount())
|
|
|
|
allow_to_sell = (last_candle['percent5'] < -0.00)
|
|
ok = (allow_to_sell) | (exit_reason == 'force_exit')
|
|
if ok:
|
|
self.pairs[pair]['expected_profit'] = 0
|
|
self.pairs[pair]['last_sell'] = rate
|
|
self.pairs[pair]['last_trade'] = trade
|
|
self.pairs[pair]['last_candle'] = last_candle
|
|
self.log_trade(
|
|
last_candle=last_candle,
|
|
date=current_time,
|
|
action="Sell",
|
|
pair=pair,
|
|
trade_type=exit_reason,
|
|
rate=last_candle['close'],
|
|
dispo=dispo,
|
|
profit=round(trade.calc_profit(rate, amount), 2),
|
|
trade=trade
|
|
)
|
|
self.pairs[pair]['max_touch'] = 0
|
|
self.pairs[pair]['last_buy'] = 0
|
|
|
|
# print(f"Sell {current_time} {exit_reason} rate={rate:.3f} amount={amount} profit={amount * rate:.3f}")
|
|
|
|
return ok
|
|
|
|
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()
|
|
before_last_candle = dataframe.iloc[-2].squeeze()
|
|
|
|
max_touch_before = self.pairs[pair]['max_touch']
|
|
self.pairs[pair]['last_max'] = max(last_candle['haclose'], self.pairs[pair]['last_max'])
|
|
|
|
last_lost = (last_candle['close'] - max_touch_before) / max_touch_before
|
|
count_of_buys = trade.nr_of_successful_entries
|
|
|
|
self.pairs[pair]['count_of_buys'] = count_of_buys
|
|
self.pairs[pair]['current_profit'] = current_profit
|
|
|
|
days = (current_time - trade.open_date_utc).days
|
|
days = max(1, days)
|
|
factor = 1
|
|
# if days > 10:
|
|
# factor = 1 + days / 10
|
|
expected_profit = self.expectedProfit(pair, last_candle) #self.pairs[pair]['expected_profit'] / factor
|
|
|
|
# print(
|
|
# f"{current_time} days={days} expected={expected_profit:.3f} rate={current_rate} max_touch={max_touch_before:.1f} profit={current_profit:.3f} last_lost={expected:.3f} buys={count_of_buys} percent={last_candle['percent']:.4f}")
|
|
# if count_of_buys >= 5 and current_profit < 0 and (last_candle['percent12'] < -0.015):
|
|
# self.pairs[pair]['stop_buy'] = True
|
|
# return 'count_' + str(count_of_buys)
|
|
|
|
if (last_candle['percent3'] < 0.0) & (current_profit > last_candle['min_max200'] / 3):
|
|
return 'min_max200_' + str(count_of_buys)
|
|
|
|
# if (current_profit > expected_profit) \
|
|
# & (last_candle['percent5'] < 0.0) \
|
|
# & (last_lost > - current_profit / 5):
|
|
# return 'lost_' + str(count_of_buys)
|
|
self.pairs[pair]['max_touch'] = max(last_candle['haclose'], self.pairs[pair]['max_touch'])
|
|
|
|
# if (current_profit > 0.004) \
|
|
# & (last_candle['hapercent'] < 0.0) \
|
|
# & (last_candle['percent3'] < - min(0.01, current_profit / 4)):
|
|
# return 'profit_' + str(count_of_buys)
|
|
|
|
def detect_loose_hammer(self, df: DataFrame, fact=2.5) -> DataFrame:
|
|
"""
|
|
Détection large de marteaux : accepte des corps plus gros, ne vérifie pas le volume,
|
|
ne demande pas de divergence, juste un pattern visuel simple.
|
|
"""
|
|
|
|
body = abs(df['close'] - df['open'])
|
|
upper_shadow = abs(df['high'] - np.maximum(df['close'], df['open']))
|
|
lower_shadow = abs(np.minimum(df['close'], df['open']) - df['low'])
|
|
|
|
# Critères simplifiés :
|
|
df['loose_hammer'] = (
|
|
(lower_shadow > body * fact) # mèche basse > corps
|
|
& (upper_shadow < body) # petite mèche haute
|
|
& (df['low'] < df['bb_lowerband'])
|
|
).astype(int)
|
|
df['won_hammer'] = (
|
|
(upper_shadow > body * fact) # mèche basse > corps
|
|
& (lower_shadow < body) # petite mèche haute
|
|
& (df['high'] > df['bb_upperband'])
|
|
).astype(int)
|
|
|
|
return df
|
|
|
|
def expectedProfit(self, pair: str, last_candle):
|
|
|
|
# last_buy = self.pairs[pair]['last_buy']
|
|
# max_touch = self.pairs[pair]['max_touch']
|
|
#
|
|
expected_profit = min(0.1, max(0.01, last_candle['min_max200'] * 0.5 + self.pairs[pair]['count_of_buys'] * 0.0005))
|
|
#((max_touch - last_buy) / max_touch)
|
|
self.pairs[pair]['expected_profit'] = max(0.004, expected_profit)
|
|
|
|
return expected_profit
|
|
|
|
def log_trade(self, action, pair, date, trade_type=None, rate=None, dispo=None, profit=None, buys=None, stake=None,
|
|
last_candle=None, trade=None):
|
|
# Afficher les colonnes une seule fois
|
|
if self.config.get('runmode') == 'hyperopt':
|
|
return
|
|
if self.columns_logged % 30 == 0:
|
|
# print(
|
|
# f"|{'-' * 18}+{'-' * 12}+{'-' * 12}+{'-' * 20}+{'-' * 14}+{'-' * 8}+{'-' * 10}+{'-' * 7}+{'-' * 13}+{'-' * 14}+{'-' * 14}+{'-' * 7}+{'-' * 12}|"
|
|
# )
|
|
print(
|
|
f"| {'Date':<16} | {'Action':<10} | {'Pair':<10} | {'Type s1d s1h':<18} | {'Rate':>12} | {'Dispo':>6} | {'Profit':>8} | {'Pct':>5} | {'max_touch':>11} | {'last_lost':>12} | {'last_max':>12} | {'Buys':>5} | {'Stake':>10} |"
|
|
)
|
|
print(
|
|
f"|{'-' * 18}+{'-' * 12}+{'-' * 12}+{'-' * 20}+{'-' * 14}+{'-' * 8}+{'-' * 10}+{'-' * 7}+{'-' * 13}+{'-' * 14}+{'-' * 14}+{'-' * 7}+{'-' * 12}|"
|
|
)
|
|
self.columns_logged += 1
|
|
date = str(date)[:16] if date else "-"
|
|
limit = None
|
|
# if buys is not None:
|
|
# limit = round(last_rate * (1 - self.fibo[buys] / 100), 4)
|
|
|
|
rsi = ''
|
|
rsi_pct = ''
|
|
# if last_candle is not None:
|
|
# if (not np.isnan(last_candle['rsi_1d'])) and (not np.isnan(last_candle['rsi_1h'])):
|
|
# rsi = str(int(last_candle['rsi_1d'])) + " " + str(int(last_candle['rsi_1h']))
|
|
# if (not np.isnan(last_candle['rsi_pct_1d'])) and (not np.isnan(last_candle['rsi_pct_1h'])):
|
|
# rsi_pct = str(int(10000 * last_candle['bb_mid_pct_1d'])) + " " + str(
|
|
# int(last_candle['rsi_pct_1d'])) + " " + str(int(last_candle['rsi_pct_1h']))
|
|
|
|
# first_rate = self.percent_threshold.value
|
|
# last_rate = self.threshold.value
|
|
# action = self.color_line(action, action)
|
|
sma5_1d = ''
|
|
sma5_1h = ''
|
|
|
|
sma5 = str(sma5_1d) + ' ' + str(sma5_1h)
|
|
|
|
expected = str(round(self.pairs[pair]['expected_profit'], 3))
|
|
|
|
max_touch = round(last_candle['max12_1d'], 1) #round(self.pairs[pair]['max_touch'], 1)
|
|
pct_max = round(100 * (last_candle['close'] - self.pairs[pair]['first_buy']) / self.pairs[pair]['first_buy'], 1) # round(100 * self.pairs[pair]['current_profit'], 1)
|
|
|
|
if trade_type is not None:
|
|
trade_type = trade_type \
|
|
+ " " + str(round(last_candle['sma5_diff_1d'], 1)) \
|
|
+ " " + str(round(last_candle['sma5_diff_1h'], 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} | {max_touch or '-':>11} | {expected or '-':>12} | {round(self.pairs[pair]['last_max'], 2) or '-':>12} | {buys or '-':>5} | {stake or '-':>10} |"
|
|
)
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
heikinashi = qtpylib.heikinashi(dataframe)
|
|
dataframe['haopen'] = heikinashi['open']
|
|
dataframe['haclose'] = heikinashi['close']
|
|
dataframe['hamid'] = dataframe['haclose'] + (dataframe['haopen'] - dataframe['haclose']) / 2
|
|
dataframe['mid'] = dataframe['open'] + (dataframe['close'] - dataframe['open']) / 2
|
|
dataframe['sma12'] = dataframe['mid'].rolling(12).sum() / 12
|
|
|
|
dataframe['hasma5'] = dataframe['hamid'].rolling(5).sum() / 5
|
|
dataframe['hasma5_diff'] = dataframe['hasma5'] - dataframe['hasma5'].shift(1)
|
|
dataframe['halow'] = heikinashi['low']
|
|
dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose']
|
|
dataframe['min12'] = talib.MIN(dataframe['close'], timeperiod=12)
|
|
dataframe['max12'] = talib.MAX(dataframe['close'], timeperiod=12)
|
|
dataframe['min48'] = talib.MIN(dataframe['close'], timeperiod=48)
|
|
dataframe['max48'] = talib.MAX(dataframe['close'], timeperiod=48)
|
|
dataframe['min288'] = talib.MIN(dataframe['close'], timeperiod=288)
|
|
dataframe['max288'] = talib.MAX(dataframe['close'], timeperiod=288)
|
|
dataframe['mid288'] = dataframe['min288'] + (dataframe['max288'] - dataframe['min288']) / 2
|
|
dataframe['min200'] = talib.MIN(dataframe['close'], timeperiod=200)
|
|
dataframe['max200'] = talib.MAX(dataframe['close'], timeperiod=200)
|
|
dataframe['min_max200'] = (dataframe['max200'] - dataframe['min200']) / dataframe['min200']
|
|
|
|
dataframe["percent"] = (dataframe["close"] - dataframe["open"]) / dataframe["open"]
|
|
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['average_line_288'] = talib.MIDPOINT(dataframe['close'], timeperiod=288)
|
|
|
|
# Bollinger Bands
|
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
|
dataframe['bb_lowerband'] = bollinger['lower']
|
|
dataframe['bb_middleband'] = bollinger['mid']
|
|
dataframe['bb_upperband'] = bollinger['upper']
|
|
dataframe['bb_diff'] = (dataframe['bb_upperband'] - dataframe['bb_lowerband']) / dataframe['bb_lowerband']
|
|
|
|
# Compter les baisses consécutives
|
|
dataframe['down'] = dataframe['hapercent'] <= 0.001
|
|
dataframe['up'] = dataframe['hapercent'] >= -0.001
|
|
dataframe['down_count'] = - dataframe['down'].astype(int) * (
|
|
dataframe['down'].groupby((dataframe['down'] != dataframe['down'].shift()).cumsum()).cumcount() + 1)
|
|
dataframe['up_count'] = dataframe['up'].astype(int) * (
|
|
dataframe['up'].groupby((dataframe['up'] != dataframe['up'].shift()).cumsum()).cumcount() + 1)
|
|
|
|
# Créer une colonne vide
|
|
dataframe['down_pct'] = self.calculateUpDownPct(dataframe, 'down_count')
|
|
dataframe['up_pct'] = self.calculateUpDownPct(dataframe, 'up_count')
|
|
|
|
# # ======================================================================================Decrease
|
|
# ################### INFORMATIVE 1d
|
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d")
|
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
|
informative['bb_lowerband'] = bollinger['lower']
|
|
informative['bb_middleband'] = bollinger['mid']
|
|
informative['bb_upperband'] = bollinger['upper']
|
|
# # Moving Averages
|
|
# informative['ema5'] = EMAIndicator(informative['close'], window=5).ema_indicator()
|
|
# informative['ema20'] = EMAIndicator(informative['close'], window=20).ema_indicator()
|
|
# informative['ma_downtrend'] = (informative['close'] < informative['ema5']) & (informative['ema5'] < informative['ema20'])
|
|
#
|
|
# # RSI
|
|
# informative['rsi'] = RSIIndicator(informative['close'], window=14).rsi()
|
|
# informative['rsi_downtrend'] = informative['rsi'] < 50
|
|
informative['max5'] = talib.MAX(informative['close'], timeperiod=5)
|
|
informative['max12'] = talib.MAX(informative['close'], timeperiod=12)
|
|
informative['min5'] = talib.MIN(informative['close'], timeperiod=5)
|
|
informative['min12'] = talib.MIN(informative['close'], timeperiod=12)
|
|
informative['sma5'] = talib.SMA(informative, timeperiod=5)
|
|
informative['sma5_diff'] = 100 * (informative['sma5'] - informative['sma5'].shift(1)) / informative['sma5']
|
|
informative = self.detect_loose_hammer(informative, 1.5)
|
|
|
|
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True)
|
|
|
|
dataframe = self.detect_loose_hammer(dataframe)
|
|
|
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1h")
|
|
informative['sma5'] = talib.SMA(informative, timeperiod=5)
|
|
informative['sma5_diff'] = 100 * (informative['sma5'] - informative['sma5'].shift(1)) / informative['sma5']
|
|
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1h", ffill=True)
|
|
|
|
self.calculateBearishCandles(dataframe, 200)
|
|
self.calculateBearishCandles(dataframe, 48)
|
|
|
|
return dataframe
|
|
|
|
def calculateBearishCandles(self, dataframe, lookback_period):
|
|
# Trouver l'index du dernier maximum sur 200 bougies
|
|
rolling_max_indices = dataframe['high'].rolling(window=lookback_period, min_periods=1).apply(np.argmax,
|
|
raw=True)
|
|
# Convertir ces indices en index globaux du DataFrame
|
|
dataframe['last_max_index'] = dataframe.index - (lookback_period - 1) + rolling_max_indices
|
|
# Création d'une colonne pour stocker le nombre de bougies baissières depuis le dernier max
|
|
bearish_counts = np.zeros(len(dataframe))
|
|
for i in range(lookback_period, len(dataframe)):
|
|
last_max_idx = int(dataframe.loc[i, 'last_max_index'])
|
|
bearish_counts[i] = (dataframe.loc[last_max_idx:i, 'close'] < dataframe.loc[last_max_idx:i, 'open']).sum()
|
|
dataframe['bearish_candles_since_max' + str(lookback_period)] = bearish_counts
|
|
|
|
def calculateUpDownPct(self, dataframe, key):
|
|
down_pct_values = np.full(len(dataframe), np.nan)
|
|
# Remplir la colonne avec les bons calculs
|
|
for i in range(len(dataframe)):
|
|
shift_value = abs(int(dataframe[key].iloc[i])) # Récupérer le shift actuel
|
|
if i - shift_value > 1: # Vérifier que le shift ne dépasse pas l'index
|
|
down_pct_values[i] = 100 * (dataframe['close'].iloc[i] - dataframe['close'].iloc[i - shift_value]) / \
|
|
dataframe['close'].iloc[i - shift_value]
|
|
return down_pct_values
|
|
|
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
Buy strategy Hyperopt will build and use.
|
|
"""
|
|
d = self.decalage.value
|
|
# dataframe.loc[
|
|
# (dataframe['halow'].shift(d) <= dataframe['min12'].shift(d))
|
|
# & (dataframe['min12'].shift(d) == dataframe['min12'])
|
|
# # & (dataframe['close'] < dataframe['hasma5'])
|
|
# # & (dataframe['bb_diff'] > 0.01)
|
|
# ,
|
|
# ['enter_long', 'enter_tag']] = [1, 'buy_halow']
|
|
|
|
# dataframe.loc[
|
|
# (dataframe['hasma5_diff'].shift(2) >= dataframe['hasma5_diff'].shift(1))
|
|
# & (dataframe['hasma5_diff'].shift(1) <= dataframe['hasma5_diff'])
|
|
# # & (dataframe['bb_diff'] > 0.01)
|
|
# ,
|
|
# ['enter_long', 'enter_tag']] = [1, 'buy_hasma5_diff']
|
|
|
|
# dataframe.loc[
|
|
# (dataframe['halow'].shift(decalage) <= dataframe['min288'].shift(decalage))
|
|
# # & (dataframe['min288'].shift(decalage) == dataframe['min288'])
|
|
# # & (dataframe['open'] <= dataframe['bb_middleband'])
|
|
# # & (dataframe['bb_diff'] > 0.01)
|
|
# ,
|
|
# 'buy']=1
|
|
|
|
dataframe.loc[
|
|
(
|
|
(dataframe['down_count'].shift(1) <= -8)
|
|
| (dataframe['percent12'] <= -0.012)
|
|
)
|
|
& (dataframe['down_count'] == 0)
|
|
# & (dataframe['bearish_candles_since_max48'] > 20)
|
|
,
|
|
['enter_long', 'enter_tag']] = [1, 'down']
|
|
|
|
# dataframe.loc[
|
|
# (
|
|
# # (dataframe['hapercent'] > 0)
|
|
# (dataframe['down_count'].shift(1) < - 6)
|
|
# & (dataframe['down_count'] == 0)
|
|
# & (dataframe['down_pct'].shift(1) <= -0.5)
|
|
# ), ['enter_long', 'enter_tag']] = (1, 'buy_hapercent')
|
|
# dataframe.loc[(dataframe['loose_hammer'] == 1)
|
|
# ,
|
|
# ['enter_long', 'enter_tag']] = [1, 'hammer']
|
|
# dataframe.loc[
|
|
# (
|
|
# (dataframe['low'] <= dataframe['min200'])
|
|
# # & (dataframe['min_max200'] > 0.015)
|
|
# # & (dataframe['percent5'] < 0)
|
|
# # & (dataframe['haopen'] < buy_level)
|
|
# # & (dataframe['open'] < dataframe['average_line_288'])
|
|
# & (dataframe['min200'].shift(3) == dataframe['min200'])
|
|
# ), ['enter_long', 'enter_tag']] = (1, 'min200')
|
|
return dataframe
|
|
|
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
"""
|
|
Sell strategy Hyperopt will build and use.
|
|
"""
|
|
# dataframe.loc[
|
|
# (qtpylib.crossed_above(dataframe['haclose'], dataframe['haopen'])),
|
|
# 'sell']=1
|
|
return dataframe
|
|
|
|
def informative_pairs(self):
|
|
# get access to all pairs available in whitelist.
|
|
pairs = self.dp.current_whitelist()
|
|
informative_pairs = [(pair, '1d') for pair in pairs]
|
|
informative_pairs += [(pair, '1h') for pair in pairs]
|
|
|
|
return informative_pairs
|