1562 lines
72 KiB
Python
1562 lines
72 KiB
Python
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
|
|
from pandas import DataFrame
|
|
from typing import Optional, Union, Tuple
|
|
from scipy.signal import find_peaks
|
|
|
|
import logging
|
|
import configparser
|
|
from technical import pivots_points
|
|
# --------------------------------
|
|
|
|
# Add your lib to import here
|
|
import ta
|
|
import talib.abstract as talib
|
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
import requests
|
|
|
|
import joblib
|
|
from sklearn.model_selection import train_test_split
|
|
from sklearn.linear_model import LogisticRegression
|
|
from sklearn.ensemble import HistGradientBoostingClassifier
|
|
from sklearn.ensemble import HistGradientBoostingRegressor
|
|
from sklearn.metrics import accuracy_score
|
|
from sklearn.metrics import mean_squared_error
|
|
from sklearn.metrics import mean_absolute_error
|
|
|
|
logger = logging.getLogger(__name__)
|
|
# Configuration du logger
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
)
|
|
|
|
from tabulate import tabulate
|
|
|
|
|
|
def pprint_df(dframe):
|
|
print(tabulate(dframe, headers='keys', tablefmt='psql', showindex=False))
|
|
|
|
|
|
def normalize(df):
|
|
df = (df - df.min()) / (df.max() - df.min())
|
|
return df
|
|
|
|
|
|
def get_limit_from_config(section, pair):
|
|
file_path = '/HOME/home/souti/freqtrade2/user_data/strategies/Thor_001.txt'
|
|
# Créez un objet ConfigParser
|
|
config = configparser.ConfigParser()
|
|
|
|
try:
|
|
# Lisez le fichier avec les valeurs
|
|
config.read(file_path)
|
|
|
|
# Vérifiez si la section existe
|
|
if config.has_section(section):
|
|
# Obtenez les valeurs à partir de la section et de la clé (pair)
|
|
limit = config.get(section, pair)
|
|
return limit
|
|
# else:
|
|
# raise ValueError(f"La section '{section}' n'existe pas dans le fichier de configuration.")
|
|
except Exception as e:
|
|
print(f"Erreur lors de la lecture du fichier de configuration : {e}")
|
|
return None
|
|
|
|
|
|
# Order(
|
|
# id=123456,
|
|
# pair='BTC/USDT',
|
|
# order_type='limit',
|
|
# side='buy',
|
|
# price=45000.0,
|
|
# amount=0.1,
|
|
# filled=0.1,
|
|
# status='closed',
|
|
# order_date=datetime(2023, 5, 12, 14, 30, 0), # Date de création de l'ordre
|
|
# exchange_order_id='abcdef123456',
|
|
# fee=0.0005,
|
|
# fee_currency='BTC'
|
|
# )
|
|
|
|
class Thor_001(IStrategy):
|
|
levels = [1, 2, 2, 4, 6, 8]
|
|
|
|
# ROI table:
|
|
minimal_roi = {
|
|
"0": 10,
|
|
# "567": 0.273,
|
|
# "2814": 0.12,
|
|
# "7675": 0.05
|
|
}
|
|
|
|
min_max_buys = {
|
|
"BTC/USDT": {"min": 1000000, "max": 0, "profit": 0.01, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
|
|
"ETH/USDT": {"min": 1000000, "max": 0, "profit": 0.01, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
|
|
"DOGE/USDT": {"min": 1000000, "max": 0, "profit": 0.02, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
|
|
"XRP/USDT": {"min": 1000000, "max": 0, "profit": 0.02, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
|
|
"SOL/USDT": {"min": 1000000, "max": 0, "profit": 0.02, 'last_rsi_1d': 0, 'last_sell_price': 1000000},
|
|
"DASH/USDT": {"min": 1000000, "max": 0, "profit": 0.02, 'last_rsi_1d': 0, 'last_sell_price': 1000000}
|
|
}
|
|
|
|
last_sell = {
|
|
"BTC/USDT": 0,
|
|
"ETH/USDT": 0,
|
|
"DOGE/USDT": 0,
|
|
"DASH/USDT": 0,
|
|
"XRP/USDT": 0,
|
|
"SOL/USDT": 0
|
|
}
|
|
# Hyperparameters
|
|
sell_max_threshold = 5.0 # % sous le max où vendre
|
|
sell_min_threshold = 1.0
|
|
|
|
close = 'close'
|
|
open = 'open'
|
|
|
|
# Stoploss:
|
|
stoploss = -0.10 # 0.256
|
|
# Custom stoploss
|
|
use_custom_stoploss = False
|
|
|
|
# Buy hypers
|
|
timeframe = '5m'
|
|
|
|
max_open_trades = 5
|
|
max_amount = 40
|
|
|
|
startup_candle_count = 26
|
|
|
|
# DCA config
|
|
position_adjustment_enable = True
|
|
|
|
plot_config = {
|
|
"main_plot": {
|
|
"min200": {
|
|
"color": "#86c932"
|
|
},
|
|
"max50": {
|
|
"color": "white"
|
|
},
|
|
"max200": {
|
|
"color": "yellow"
|
|
},
|
|
"max_previous_1h": {
|
|
"color": "#da59a6"},
|
|
"min_previous_1h": {
|
|
"color": "#da59a6",
|
|
},
|
|
"sma5_1h": {
|
|
"color": "red",
|
|
},
|
|
"close_1d": {
|
|
"color": "yellow"
|
|
}
|
|
},
|
|
"subplots": {
|
|
"Rsi": {
|
|
"rsi": {
|
|
"color": "pink"
|
|
},
|
|
"rsi_1h": {
|
|
"color": "yellow"
|
|
},
|
|
"rsi_sma_1h": {
|
|
"color": "yellow"
|
|
}
|
|
},
|
|
"Pct": {
|
|
"percent": {
|
|
"color": "blue"
|
|
},
|
|
"percent3": {
|
|
"color": "green"
|
|
},
|
|
"percent12": {
|
|
"color": "yellow"
|
|
},
|
|
"percent24": {
|
|
"color": "pink"
|
|
},
|
|
"sma5_pct_1h": {
|
|
"color": "red",
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# 20 20 40 60 100 160 260 420
|
|
# 50 50 100 300 500
|
|
# fibo = [1, 1, 2, 3, 5, 8, 13, 21]
|
|
# my fibo
|
|
# 50 50 50 100 100 150 200 250 350 450 600 1050
|
|
fibo = [1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21]
|
|
baisse = [1, 2, 3, 5, 7, 10, 14, 19, 26, 35, 47, 63, 84]
|
|
# Ma suite 1 1 1 2 2 3 4 5 7 9 12 16 21
|
|
# Mise 50 50 50 100 100 150 200 250 350 450 600 800 1050
|
|
# Somme Mises 50 100 150 250 350 500 700 950 1300 1750 2350 3150 4200
|
|
# baisse 1 2 3 5 7 10 14 19 26 35 47 63 84
|
|
|
|
trades = list()
|
|
max_profit_pairs = {}
|
|
|
|
profit_b_no_change = BooleanParameter(default=True, space="sell")
|
|
profit_b_quick_lost = BooleanParameter(default=True, space="sell")
|
|
profit_b_sma5 = BooleanParameter(default=True, space="sell")
|
|
profit_b_sma10 = BooleanParameter(default=True, space="sell")
|
|
profit_b_sma20 = BooleanParameter(default=True, space="sell")
|
|
profit_b_quick_gain = BooleanParameter(default=True, space="sell")
|
|
profit_b_quick_gain_3 = BooleanParameter(default=True, space="sell")
|
|
profit_b_old_sma10 = BooleanParameter(default=True, space="sell")
|
|
profit_b_very_old_sma10 = BooleanParameter(default=True, space="sell")
|
|
profit_b_over_rsi = BooleanParameter(default=True, space="sell")
|
|
profit_b_short_loss = BooleanParameter(default=True, space="sell")
|
|
|
|
sell_b_percent = DecimalParameter(0, 0.02, decimals=3, default=0.01, space='sell')
|
|
sell_b_percent3 = DecimalParameter(0, 0.02, decimals=3, default=0.01, space='sell')
|
|
sell_b_candels = IntParameter(0, 48, default=12, space='sell')
|
|
|
|
sell_b_too_old_day = IntParameter(0, 10, default=300, space='sell')
|
|
sell_b_too_old_percent = DecimalParameter(0, 0.02, decimals=3, default=0.01, space='sell')
|
|
|
|
sell_b_profit_no_change = DecimalParameter(0, 0.02, decimals=3, default=0.005, space='sell')
|
|
sell_b_profit_percent12 = DecimalParameter(0, 0.002, decimals=4, default=0.001, space='sell')
|
|
|
|
sell_b_RSI = IntParameter(70, 98, default=88, space='sell')
|
|
sell_b_RSI2 = IntParameter(70, 98, default=88, space='sell')
|
|
sell_b_RSI3 = IntParameter(70, 98, default=80, space='sell')
|
|
|
|
sell_b_RSI2_percent = DecimalParameter(0, 0.02, decimals=3, default=0.01, space='sell')
|
|
# sell_b_expected_profit = DecimalParameter(0, 0.01, decimals=3, default=0.01, space='sell')
|
|
|
|
protection_percent_buy_lost = IntParameter(1, 10, default=5, space='protection')
|
|
# protection_nb_buy_lost = IntParameter(1, 2, default=2, space='protection')
|
|
|
|
protection_fibo = IntParameter(1, 10, default=2, space='protection')
|
|
|
|
# trailing stoploss hyperopt parameters
|
|
# hard stoploss profit
|
|
pHSL = DecimalParameter(-0.200, -0.040, default=-0.08, decimals=3, space='sell', optimize=False, load=True)
|
|
# profit threshold 1, trigger point, SL_1 is used
|
|
pPF_1 = DecimalParameter(0.008, 0.020, default=0.016, decimals=3, space='sell', optimize=True, load=True)
|
|
pSL_1 = DecimalParameter(0.008, 0.020, default=0.011, decimals=3, space='sell', optimize=True, load=True)
|
|
|
|
# profit threshold 2, SL_2 is used
|
|
pPF_2 = DecimalParameter(0.040, 0.100, default=0.080, decimals=3, space='sell', optimize=True, load=True)
|
|
pSL_2 = DecimalParameter(0.020, 0.070, default=0.040, decimals=3, space='sell', optimize=True, load=True)
|
|
|
|
columns_logged = False
|
|
|
|
features = ['rsi_percent', 'rsi_percent3', 'rsi_percent5', 'bb_upperband', 'bb_lowerband', 'close',
|
|
close, 'percent12', 'percent24', 'tag_min', 'tag_percent24', 'tag_stable', 'tag_buy']
|
|
|
|
# Charger les modèles
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.model = None
|
|
|
|
def train_model(self, dataframe: DataFrame):
|
|
# Sélection des colonnes de caractéristiques (features)
|
|
features = self.features
|
|
target = 'future_direction'
|
|
|
|
# Supprimer les lignes avec des valeurs NaN
|
|
# dataframe = dataframe.dropna()
|
|
# dataframe = dataframe.fillna("Unknown") # Remplace NaN par "Unknown"
|
|
|
|
# Création d'une nouvelle DataFrame contenant uniquement les colonnes spécifiées
|
|
# dataframe = old_dataframe[self.features]
|
|
# dataframe['future_direction'] = old_dataframe['future_direction']
|
|
# dataframe['future_change'] = old_dataframe['future_change']
|
|
# dataframe['rsi_pct_range'] = old_dataframe['rsi_pct_range']
|
|
print(f"Shape of dataset: {dataframe.shape}")
|
|
print(dataframe.head())
|
|
|
|
dataframe = dataframe.dropna()
|
|
#dataframe = dataframe.fillna(0) # Remplace NaN par 0
|
|
|
|
# Diviser les données en train (80%) et test (20%)
|
|
X = dataframe[features]
|
|
y = dataframe[target]
|
|
print(f"Shape of dataset: {dataframe.shape}")
|
|
print(dataframe.head())
|
|
|
|
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
|
|
|
# Entraîner le modèle de régression logistique
|
|
model = LogisticRegression(max_iter=5000)
|
|
model.fit(X_train, y_train)
|
|
# Évaluer le modèle
|
|
predictions = model.predict(X_test)
|
|
accuracy = accuracy_score(y_test, predictions)
|
|
print(f"Accuracy du modèle : {accuracy * 100:.2f}%")
|
|
|
|
# # HistGradientBoostingClassifier est conçu pour les problèmes de classification, pas pour la régression.
|
|
# # Si votre problème est une classification (par exemple, prédire une catégorie ou une classe),
|
|
# # vous pouvez utiliser ce modèle.
|
|
# # Remplacer LinearRegression par HistGradientBoostingClassifier
|
|
# # Diviser les données
|
|
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
|
#
|
|
# model = HistGradientBoostingClassifier()
|
|
# model.fit(X_train, y_train)
|
|
#
|
|
# # Prédictions
|
|
# y_pred = model.predict(X_test)
|
|
# accuracy = accuracy_score(y_test, y_pred)
|
|
# print(f"Accuracy: {accuracy:.2f}")
|
|
|
|
return model
|
|
|
|
def add_future_direction(self, dataframe: DataFrame) -> DataFrame:
|
|
dataframe['future_change'] = dataframe['close'].shift(-12) - dataframe['close']
|
|
dataframe['future_direction'] = dataframe['future_change'].apply(lambda x: 1 if x > 0 else 0)
|
|
return dataframe
|
|
|
|
def predict_with_model(self, dataframe: DataFrame, model):
|
|
features = self.features
|
|
|
|
# Supprimer les lignes avec des NaN
|
|
|
|
# Prédire les probabilités
|
|
#dataframe['prediction_prob'] = model.predict_proba(dataframe[features])[:, 1]
|
|
#dataframe['predicted_direction'] = dataframe['prediction_prob'].apply(lambda x: 1 if x > 0.5 else 0)
|
|
|
|
# Assurez-vous que dataframe est une copie complète
|
|
dataframe = dataframe.copy()
|
|
dataframe = dataframe.dropna()
|
|
|
|
# Prédire les probabilités
|
|
dataframe.loc[:, 'prediction_prob'] = model.predict_proba(dataframe[features])[:, 1]
|
|
|
|
# Appliquer la direction prédite
|
|
dataframe.loc[:, 'predicted_direction'] = (dataframe['prediction_prob'] > 0.5).astype(int)
|
|
|
|
return dataframe
|
|
|
|
def color_line(self, action, line):
|
|
if "INC" in action: # Exemple pour un prix croissant
|
|
return f"\033[92m{line}\033[0m" # Vert
|
|
elif "DEC" in action: # Exemple pour un prix décroissant
|
|
return f"\033[91m{line}\033[0m" # Rouge
|
|
elif "Allow Sell" in action: # Exemple pour une vente
|
|
return f"\033[93m{line}\033[0m" # Jaune
|
|
elif "Allow To buy" in action: # Exemple pour un achat
|
|
return f"\033[94m{line}\033[0m" # Bleu
|
|
else:
|
|
return line # Sans modification
|
|
|
|
def log_trade(self, action, pair, date, trade_type=None, rate=None, dispo=None, profit=None, rsi=None, minutes=None,
|
|
first_rate=None, last_rate=None, buys=None, stake=None):
|
|
# Afficher les colonnes une seule fois
|
|
if not self.columns_logged:
|
|
print(
|
|
f"| {'Date':<16} | {'Action':<20} | {'Pair':<10} | {'Trade Type':<18} | {'Rate':>12} | {'Dispo':>6} | {'Profit':>8} | {'RSI':>5} | {'Limit':>10} | {'First Rate':>12} | {'Last Rate':>12} | {'Buys':>5} | {'Stake':>10} |"
|
|
)
|
|
print(
|
|
f"|{'-' * 18}|{'-' * 22}|{'-' * 12}|{'-' * 20}|{'-' * 14}|{'-' * 8}|{'-' * 10}|{'-' * 7}|{'-' * 12}|{'-' * 14}|{'-' * 14}|{'-' * 7}|{'-' * 12}|"
|
|
)
|
|
self.columns_logged = True
|
|
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 = (str(rsi) if rsi else '') + " " + str(rsi - self.min_max_buys[pair]['last_rsi_1d'])
|
|
|
|
#action = self.color_line(action, action)
|
|
print(
|
|
f"| {date:<16} | {action:<20} | {pair:<10} | {trade_type or '-':<18} | {rate or '-':>12} | {dispo or '-':>6} | {profit or '-':>8} | {rsi or '-':>5} | {limit or '-':>10} | {first_rate or '-':>12} | {last_rate or '-':>12} | {buys or '-':>5} | {stake or '-':>10} |"
|
|
)
|
|
|
|
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:
|
|
# count_buys = 0
|
|
# trade = self.getTrade(pair)
|
|
# if trade:
|
|
# filled_buys = trade.select_filled_orders('buy')
|
|
# count_buys = len(filled_buys)
|
|
|
|
# print('entry_tag' + str(entry_tag))
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
# last_candle_288 = dataframe.iloc[-289].squeeze()
|
|
# limit = get_limit_from_config('Achats', pair)
|
|
|
|
# allow_to_buy = True #(not self.stop_all) #& (not self.all_down)
|
|
|
|
#allow_to_buy = (last_candle['rsi_1d'] > 50) # | (last_candle['rsi_1d'] > last_candle_288['rsi_1d']) # (rate <= float(limit)) | (entry_tag == 'force_entry')
|
|
allow_to_buy = \
|
|
(
|
|
((last_candle['ha_red_1d'] == 0) | (entry_tag == "buy_crash")) # & (last_candle['close'] < self.min_max_buys[pair]['last_sell_price'] * 0.99)
|
|
#& (last_candle['rsi_1d'] >= 45)
|
|
)
|
|
self.trades = list()
|
|
dispo = round(self.wallets.get_available_stake_amount())
|
|
stake_amount = self.calculate_stake(pair, rate, last_candle)
|
|
if allow_to_buy:
|
|
self.log_trade(
|
|
date=current_time,
|
|
action="Allow To buy",
|
|
pair=pair,
|
|
trade_type=entry_tag,
|
|
rate=rate,
|
|
dispo=dispo,
|
|
profit=0,
|
|
stake=round(stake_amount, 2),
|
|
rsi=round(last_candle['rsi_1d'])
|
|
)
|
|
self.min_max_buys[pair]['last_rsi_1d'] = round(last_candle['rsi_1d'])
|
|
# logger.info(f"Allow_to_buy {allow_to_buy} {pair} {current_time} Buy={entry_tag} rate={rate} dispo={dispo} rsi_1d={rsi_1d}")
|
|
|
|
return allow_to_buy
|
|
|
|
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:
|
|
# allow_to_sell = (minutes > 30)
|
|
# limit = get_limit_from_config('Ventes', pair)
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
|
|
# & (last_candle['rsi_1d'] <= 50)# rate > float(limit)
|
|
allow_to_sell = (
|
|
last_candle['percent'] < -0.00) | (last_candle['percent3'] < - 0.00) | (40 >= last_candle['rsi'] >= 80
|
|
) #& (last_candle['rsi_1d'] < self.min_max_buys[pair]['last_rsi_1d'])
|
|
string = ""
|
|
# allow_to_sell = (last_candle['rsi_1d'] >= self.min_max_buys[pair]['last_rsi_1d'])
|
|
|
|
dispo = round(self.wallets.get_available_stake_amount())
|
|
|
|
if allow_to_sell:
|
|
self.trades = list()
|
|
self.log_trade(
|
|
date=current_time,
|
|
action="Allow Sell trade",
|
|
pair=pair,
|
|
trade_type=exit_reason,
|
|
first_rate=trade.open_rate,
|
|
rate=last_candle['close'],
|
|
dispo=dispo,
|
|
profit=round(trade.calc_profit(rate, amount), 2),
|
|
rsi=round(last_candle['rsi_1d'])
|
|
)
|
|
self.min_max_buys[pair]['last_rsi_1d'] = round(last_candle['rsi_1d'])
|
|
|
|
else:
|
|
self.log_trade(
|
|
date=current_time,
|
|
action="Cancel Sell trade",
|
|
pair=pair,
|
|
trade_type=exit_reason,
|
|
first_rate=trade.open_rate,
|
|
rate=last_candle['close'],
|
|
dispo=dispo,
|
|
#profit=round(trade.calc_profit(rate, amount), 2),
|
|
rsi=round(last_candle['rsi_1d'])
|
|
)
|
|
ok = (allow_to_sell) | (exit_reason == 'force_exit')
|
|
if ok:
|
|
self.min_max_buys[pair]['last_sell_price'] = last_candle['close']
|
|
self.last_sell[pair] = rate
|
|
return ok
|
|
|
|
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=pair, timeframe=self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
# adjusted_stake_amount = self.adjust_stake_amount(pair, current_candle)
|
|
|
|
# adjusted_stake_amount = self.calculate_stake(pair, current_rate)
|
|
# print(f"Pour {pair}, avec une valeur actuelle de {current_rate}, la mise est de {adjusted_stake_amount:.2f}%")
|
|
adjusted_stake_amount = self.calculate_stake(pair, current_rate, last_candle) # self.config['stake_amount']
|
|
|
|
# logger.info(f"{pair} adjusted_stake_amount{adjusted_stake_amount}")
|
|
|
|
# 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:
|
|
# # Récupérer les données pour le trade en cours
|
|
# dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
|
# last_candle = dataframe.iloc[-1]
|
|
#
|
|
# # Heikin-Ashi values
|
|
# ha_close = last_candle['ha_close_1h']
|
|
# ha_open = last_candle['ha_open_1h']
|
|
# ha_low = last_candle['ha_low_1h']
|
|
# ha_high = last_candle['ha_high_1h']
|
|
#
|
|
# # Calcul de la volatilité et du buffer dynamique
|
|
# ha_range = abs(ha_close - ha_open)
|
|
# ha_relative_diff = ha_range / ha_close if ha_close > 0 else 0
|
|
# stoploss_buffer = ha_relative_diff * 0.5 # Coefficient ajustable
|
|
#
|
|
# # Calcul du stoploss dynamique basé sur Heikin-Ashi
|
|
# if ha_close > ha_open: # Tendance haussière
|
|
# stoploss = ha_open * (1 - stoploss_buffer)
|
|
# else: # Tendance baissière
|
|
# stoploss = ha_low * (1 - stoploss_buffer)
|
|
#
|
|
# # Contrainte de la fourchette [95%, 98%]
|
|
# stoploss_min = current_rate * 0.97
|
|
# stoploss_max = current_rate * 0.985
|
|
# stoploss_final = max(stoploss_min, min(stoploss, stoploss_max))
|
|
#
|
|
# return stoploss_final
|
|
|
|
# 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)
|
|
# # last_candle = dataframe.iloc[-1].squeeze()
|
|
#
|
|
# # # 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
|
|
#
|
|
# filled_buys = trade.select_filled_orders('buy')
|
|
# count_of_buys = len(filled_buys)
|
|
# # first_price = filled_buys[0].price
|
|
#
|
|
# days = (current_time - trade.open_date_utc).days
|
|
#
|
|
# #print(f"entry_tag={trade.entry_tag} max={trade.max_rate} min={trade.min_rate} ")
|
|
# if current_profit > (count_of_buys / 0.6) / 100: #0.0125:
|
|
# sl_profit = 0.6 * 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"{pair} {current_time} stoploss={stoploss:.4f} sl_profit={sl_profit:.4f} current_profit={current_profit:.4f} count_of_buys={count_of_buys}")
|
|
# return stoploss
|
|
|
|
def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
|
|
current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]':
|
|
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
previous_last_candle = dataframe.iloc[-2].squeeze()
|
|
fifth_candle = dataframe.iloc[-6].squeeze()
|
|
if (last_candle['percent'] > 0) | (last_candle['percent3'] > 0.0): # | (last_candle['max200_diff'] > 0.02):
|
|
return None
|
|
|
|
# if not (pair in self.max_profit_pairs):
|
|
# self.max_profit_pairs[pair] = current_profit
|
|
# self.max_profit_pairs[pair] = max(self.max_profit_pairs[pair], current_profit)
|
|
#
|
|
# if (self.max_profit_pairs[pair] > 0.01) & (current_profit > 0):
|
|
# allow_decrease = 0.2 * self.max_profit_pairs[pair]
|
|
# if self.max_profit_pairs[pair] - current_profit > allow_decrease:
|
|
# print("Max profit=" + str(self.max_profit_pairs[pair]) + ' ' + str(current_profit) + ' ' + str(
|
|
# allow_decrease) + ' ' + str(self.max_profit_pairs[pair] - current_profit))
|
|
# return 'max_lost_profit'
|
|
# hours = (current_time - trade.open_date_utc).seconds / 3600
|
|
#
|
|
# if (hours >= 2) & (self.max_profit_pairs[pair] < 0.002) & (self.max_profit_pairs[pair] > - 0.002):
|
|
# return "no_change"
|
|
|
|
# if (current_profit < - 0.03 ):
|
|
# print("Stop loss max profit=" + str(self.max_profit_pairs[pair]) + ' current=' + str(current_profit))
|
|
# return 'stop_loss'
|
|
|
|
filled_buys = trade.select_filled_orders('buy')
|
|
count_of_buys = len(filled_buys)
|
|
|
|
expected_profit = max(self.min_max_buys[pair]['profit'], 0.01 * count_of_buys)
|
|
# print(last_candle['buy_tag'])
|
|
|
|
days = (current_time - trade.open_date_utc).days
|
|
hours = (current_time - trade.open_date_utc).seconds / 3600
|
|
|
|
# fibo_fact = fibo[hours]
|
|
######
|
|
# if last_candle['percent5'] < -0.05:
|
|
# return "stop_loss_005"
|
|
|
|
max_percent = 0.005 # self.min_max_buys[pair]['profit'] / 5 # last_candle['bb_width'] / 3.5 # 0.005
|
|
max_profit = self.min_max_buys[pair]['profit'] # last_candle['bb_width'] * 3 / 4 # 0.015
|
|
|
|
# if (current_profit > expected_profit) & \
|
|
# (last_candle['open'] > last_candle['max_previous_1h']) & \
|
|
# (last_candle['percent'] < 0) \
|
|
# & (last_candle[self.close] > last_candle['sma5_1h']):
|
|
# return 'b_max_previous'
|
|
|
|
# if (current_profit < - 0.03) & \
|
|
# (last_candle['rsi_1d'] < 45) & \
|
|
# (last_candle['percent'] < 0):
|
|
# return 'b_rsi_1d'
|
|
|
|
if (current_profit > expected_profit) & \
|
|
(last_candle['percent12'] < -max_percent) \
|
|
& ((current_time - trade.open_date_utc).seconds >= 3600) \
|
|
& (last_candle[self.close] > last_candle['sma5_1h']) \
|
|
& (80 > last_candle['rsi_1h'] < 50):
|
|
return 'b_percent12'
|
|
# if (current_profit > max_profit) & \
|
|
# ((last_candle['percent'] < - max_percent) | (last_candle['percent3'] < -max_percent) | (
|
|
# last_candle['percent5'] < -max_percent)):
|
|
# return 'b_percent_quick'
|
|
|
|
if self.profit_b_quick_lost.value and (current_profit >= max_profit) \
|
|
& (last_candle['percent3'] < -max_percent) \
|
|
& (last_candle[self.close] > last_candle['sma5_1h']) \
|
|
& (80 > last_candle['rsi_1h'] < 50):
|
|
return "b_quick_lost"
|
|
|
|
# if self.profit_b_no_change.value and (current_profit > self.sell_b_profit_no_change.value) \
|
|
# & (last_candle['percent12'] < self.sell_b_profit_percent12.value) & (last_candle['percent5'] < 0) \
|
|
# & ((current_time - trade.open_date_utc).seconds >= 3600):
|
|
# return "b_no_change"
|
|
|
|
if (current_profit > self.sell_b_percent.value) & (last_candle['percent3'] < - self.sell_b_percent3.value) \
|
|
& ((current_time - trade.open_date_utc).seconds <= 300 * self.sell_b_candels.value):
|
|
return "b_quick_gain_param"
|
|
|
|
if self.profit_b_sma5.value:
|
|
if (current_profit > expected_profit) \
|
|
& ((fifth_candle['sma5'] > last_candle['sma5']) \
|
|
| (last_candle['percent3'] < -expected_profit) | (
|
|
last_candle['percent5'] < -expected_profit)) \
|
|
& ((last_candle['percent'] < 0) & (last_candle['percent3'] < 0)):
|
|
# print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'b_sma5'
|
|
|
|
if self.profit_b_sma10.value:
|
|
if (current_profit > expected_profit) \
|
|
& ((fifth_candle['sma10'] > last_candle['sma10']) \
|
|
| (last_candle['percent3'] < -expected_profit) | (
|
|
last_candle['percent5'] < -expected_profit)) \
|
|
& ((last_candle['percent'] < 0) & (last_candle['percent3'] < 0)):
|
|
# print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'b_sma10'
|
|
|
|
if self.profit_b_sma20.value:
|
|
if (current_profit > expected_profit) \
|
|
& (previous_last_candle['sma10'] > last_candle['sma10']) \
|
|
& ((current_time - trade.open_date_utc).seconds >= 3600) \
|
|
& ((previous_last_candle['sma20'] > last_candle['sma20']) &
|
|
((last_candle['percent5'] < 0) | (last_candle['percent12'] < 0) | (
|
|
last_candle['percent24'] < 0))):
|
|
# print("over_bb_band_sma10_desc", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'b_sma20'
|
|
|
|
if self.profit_b_over_rsi.value:
|
|
if (current_profit > 0) & (previous_last_candle['rsi'] > self.sell_b_RSI.value):
|
|
# & (last_candle['percent'] < 0): #| (previous_last_candle['rsi'] > 75 & last_candle['rsi'] < 70)):
|
|
# print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'b_over_rsi'
|
|
|
|
if (current_profit > 0) & (previous_last_candle['rsi'] > self.sell_b_RSI2.value) & \
|
|
(last_candle[
|
|
'percent'] < - self.sell_b_RSI2_percent.value): # | (previous_last_candle['rsi'] > 75 & last_candle['rsi'] < 70)):
|
|
# print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'b_over_rsi_2'
|
|
|
|
if (current_profit > 0) & (previous_last_candle['rsi'] > self.sell_b_RSI3.value) & \
|
|
(last_candle[self.close] >= last_candle['max200']) \
|
|
& (last_candle[
|
|
'percent'] < - self.sell_b_RSI2_percent.value): # | (previous_last_candle['rsi2'] > 75 & last_candle['rsi'] < 70)):
|
|
# print("over_rsi", pair, trade, " profit=", current_profit, " rate=", current_rate)
|
|
return 'b_over_rsi_max'
|
|
|
|
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
|
|
|
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
# Add all ta features
|
|
pair = metadata['pair']
|
|
|
|
# dataframe['achats'] = get_limit_from_config('Achats', pair)
|
|
# dataframe['ventes'] = get_limit_from_config('Ventes', pair)
|
|
|
|
heikinashi = qtpylib.heikinashi(dataframe)
|
|
dataframe['enter_long'] = ""
|
|
dataframe['enter_tag'] = ""
|
|
dataframe['haopen'] = heikinashi['open']
|
|
dataframe['haclose'] = heikinashi['close']
|
|
dataframe['hapercent'] = (dataframe['haclose'] - dataframe['haopen']) / dataframe['haclose']
|
|
dataframe['mid'] = (dataframe['close'] + dataframe['open']) / 2
|
|
|
|
dataframe['pct_change'] = dataframe[self.close].pct_change(5)
|
|
dataframe['min'] = talib.MIN(dataframe[self.close], timeperiod=200)
|
|
dataframe['min12'] = talib.MIN(dataframe[self.close], timeperiod=12)
|
|
|
|
dataframe['min50'] = talib.MIN(dataframe[self.close], timeperiod=50)
|
|
dataframe['min200'] = talib.MIN(dataframe[self.close], timeperiod=200)
|
|
|
|
dataframe['max50'] = talib.MAX(dataframe[self.close], timeperiod=50)
|
|
dataframe['max144'] = talib.MAX(dataframe[self.close], timeperiod=144)
|
|
dataframe['min_max50'] = (dataframe['max50'] - dataframe['min50']) / dataframe['min50']
|
|
|
|
dataframe['max200'] = talib.MAX(dataframe[self.close], timeperiod=200)
|
|
dataframe['min_max200'] = (dataframe['max200'] - dataframe['min200']) / dataframe['min200']
|
|
dataframe['max200_diff'] = (dataframe['max200'] - dataframe[self.close]) / dataframe[self.close]
|
|
dataframe['max50_diff'] = (dataframe['max50'] - dataframe[self.close]) / dataframe[self.close]
|
|
|
|
dataframe['sma5'] = talib.SMA(dataframe, timeperiod=5)
|
|
dataframe['sma10'] = talib.SMA(dataframe, timeperiod=10)
|
|
dataframe['sma20'] = talib.SMA(dataframe, timeperiod=20)
|
|
dataframe["percent"] = (dataframe[self.close] - dataframe[self.open]) / dataframe[self.open]
|
|
dataframe["percent3"] = (dataframe[self.close] - dataframe[self.open].shift(3)) / dataframe[self.open].shift(3)
|
|
dataframe["percent5"] = (dataframe[self.close] - dataframe[self.open].shift(5)) / dataframe[self.open].shift(5)
|
|
dataframe["percent12"] = (dataframe[self.close] - dataframe[self.open].shift(12)) / dataframe[self.open].shift(
|
|
12)
|
|
dataframe["percent24"] = (dataframe[self.close] - dataframe[self.open].shift(24)) / dataframe[self.open].shift(
|
|
24)
|
|
dataframe["percent48"] = (dataframe[self.close] - dataframe[self.open].shift(48)) / dataframe[self.open].shift(
|
|
48)
|
|
dataframe["percent_max_144"] = (dataframe[self.close] - dataframe["max144"]) / dataframe[self.close]
|
|
# print(metadata['pair'])
|
|
dataframe['rsi'] = talib.RSI(dataframe[self.close], timeperiod=12)
|
|
dataframe['rsi24'] = talib.RSI(dataframe[self.close], timeperiod=24)
|
|
dataframe['rsi48'] = talib.RSI(dataframe[self.close], timeperiod=48)
|
|
|
|
dataframe['volume2'] = dataframe['volume']
|
|
dataframe.loc[dataframe['percent'] < 0, 'volume2'] *= -1
|
|
|
|
# ======================================================================================
|
|
# MACD pour identifier les croisements
|
|
macd = talib.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
|
|
dataframe['macd'] = macd['macd']
|
|
dataframe['macdsignal'] = macd['macdsignal']
|
|
|
|
# Moyennes mobiles pour les tendances
|
|
dataframe['ema_50'] = talib.EMA(dataframe, timeperiod=50)
|
|
dataframe['ema_200'] = talib.EMA(dataframe, timeperiod=200)
|
|
|
|
# ADX pour la force de tendance
|
|
dataframe['adx'] = talib.ADX(dataframe)
|
|
|
|
# Volume moyen pour filtrer les opportunités à faible volume
|
|
dataframe['volume_mean_sma'] = talib.SMA(dataframe['volume'], timeperiod=20)
|
|
# ======================================================================================
|
|
|
|
# ======================================================================================
|
|
# 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_percent"] = (
|
|
(dataframe[self.close] - dataframe["bb_lowerband"]) /
|
|
(dataframe["bb_upperband"] - dataframe["bb_lowerband"])
|
|
)
|
|
# ======================================================================================
|
|
# Normalization
|
|
|
|
dataframe['average_line'] = dataframe[self.close].mean()
|
|
dataframe['average_line_50'] = talib.MIDPOINT(dataframe[self.close], timeperiod=50)
|
|
|
|
dataframe['average_line_288'] = talib.MIDPOINT(dataframe[self.close], timeperiod=288)
|
|
dataframe['average_line_288_098'] = dataframe['average_line_288'] * 0.98
|
|
dataframe['average_line_288_099'] = dataframe['average_line_288'] * 0.99
|
|
# Sort the close prices to find the 4 lowest values
|
|
sorted_close_prices = dataframe[self.close].tail(576).sort_values()
|
|
lowest_4 = sorted_close_prices.head(20)
|
|
|
|
dataframe['lowest_4_average'] = dataframe['min200'] # lowest_4.mean()
|
|
# Propagate this mean value across the entire dataframe
|
|
# dataframe['lowest_4_average'] = dataframe['lowest_4_average'].iloc[0]
|
|
|
|
# # Sort the close prices to find the 4 highest values
|
|
sorted_close_prices = dataframe[self.close].tail(288).sort_values(ascending=False)
|
|
highest_4 = sorted_close_prices.head(20)
|
|
|
|
# # Calculate the mean of the 4 highest values
|
|
dataframe['highest_4_average'] = dataframe['max200'] # highest_4.mean()
|
|
|
|
# # Propagate this mean value across the entire dataframe
|
|
# dataframe['highest_4_average'] = dataframe['highest_4_average'].iloc[0]
|
|
|
|
dataframe['volatility'] = talib.STDDEV(dataframe[self.close], timeperiod=144) / dataframe[self.close]
|
|
dataframe['atr'] = talib.ATR(dataframe['high'], dataframe['low'], dataframe[self.close], timeperiod=144) / \
|
|
dataframe[self.close]
|
|
|
|
# ======================================================================================
|
|
################### INFORMATIVE 1h
|
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1h")
|
|
informative['sma5'] = talib.SMA(informative, timeperiod=10)
|
|
informative['sma5_pct'] = (informative["sma5"] - informative["sma5"].shift(6)) / informative["sma5"]
|
|
informative["max_previous"] = talib.MAX(informative['close'], timeperiod=48)
|
|
informative["min_previous"] = talib.MIN(informative['close'], timeperiod=48)
|
|
|
|
informative['volatility'] = talib.STDDEV(informative['close'], timeperiod=14) / informative['close']
|
|
informative['atr'] = (talib.ATR(informative['high'], informative['low'], informative['close'], timeperiod=14)) / \
|
|
informative['close']
|
|
informative['rsi'] = talib.RSI(informative['close'], timeperiod=12)
|
|
informative['rsi_sma'] = talib.SMA(informative['rsi'], timeperiod=10)
|
|
heikinashi = qtpylib.heikinashi(informative)
|
|
informative['ha_open'] = heikinashi['open']
|
|
informative['ha_close'] = heikinashi['close']
|
|
informative['ha_high'] = heikinashi['high']
|
|
informative['ha_low'] = heikinashi['low']
|
|
|
|
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1h", ffill=True)
|
|
|
|
# Calcul des seuils dynamiques
|
|
dataframe['sell_threshold_max'] = dataframe['max_previous_1h'] - (
|
|
dataframe['max_previous_1h'] * self.sell_max_threshold / 100)
|
|
dataframe['sell_threshold_min'] = dataframe['min_previous_1h'] + (
|
|
dataframe['min_previous_1h'] * self.sell_min_threshold / 100)
|
|
|
|
# ======================================================================================
|
|
################### INFORMATIVE 1d
|
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe="1d")
|
|
sorted_close_prices = informative['close'].tail(365).sort_values()
|
|
lowest_4 = sorted_close_prices.head(4)
|
|
informative['lowest_4'] = lowest_4.mean()
|
|
|
|
sorted_close_prices = informative['close'].tail(365).sort_values(ascending=False)
|
|
highest_4 = sorted_close_prices.head(4)
|
|
informative['highest_4'] = highest_4.mean()
|
|
|
|
last_14_days = informative.tail(14)
|
|
|
|
# Récupérer le minimum et le maximum de la colonne 'close' des 14 derniers jours
|
|
min_14_days = last_14_days['close'].min()
|
|
max_14_days = last_14_days['close'].max()
|
|
informative['lowest'] = min_14_days
|
|
informative['highest'] = max_14_days
|
|
informative['pct_min_max'] = (max_14_days - min_14_days) / min_14_days
|
|
informative['mid_min_max'] = min_14_days + (max_14_days - min_14_days) / 2
|
|
informative['middle'] = informative['lowest_4'] + (informative['highest_4'] - informative['lowest_4']) / 2
|
|
informative['mid_min_max_0.98'] = informative['mid_min_max'] * 0.98
|
|
informative['rsi'] = talib.RSI(informative['close'], timeperiod=12)
|
|
informative['rsi_sma'] = talib.SMA(informative['rsi'], timeperiod=12)
|
|
informative["percent"] = (informative[self.close] - informative[self.open]) / informative[self.open]
|
|
|
|
# Heikin Ashi (simplified)
|
|
heikinashi = qtpylib.heikinashi(informative)
|
|
informative['ha_open'] = heikinashi['open']
|
|
informative['ha_close'] = heikinashi['close']
|
|
informative['ha_high'] = heikinashi['high']
|
|
informative['ha_low'] = heikinashi['low']
|
|
# informative['ha_close'] = (informative['open'] + informative['high'] + informative['low'] + informative['close']) / 4
|
|
# informative['ha_open'] = informative['ha_close'].shift(1).combine_first(informative['open'])
|
|
informative['ha_red'] = informative['ha_close'] < informative['ha_open']
|
|
|
|
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, "1d", ffill=True)
|
|
|
|
dataframe['count_buys'] = 0
|
|
|
|
dataframe['last_price'] = dataframe[self.close]
|
|
dataframe['first_price'] = dataframe[self.close]
|
|
dataframe['mid_price'] = (dataframe['last_price'] + dataframe['first_price']) / 2
|
|
dataframe['close01'] = dataframe.iloc[-1][self.close] * 1.01
|
|
dataframe['amount'] = 0
|
|
dataframe['limit'] = dataframe[self.close]
|
|
count_buys = 0
|
|
if self.dp:
|
|
if self.dp.runmode.value in ('live', 'dry_run'):
|
|
self.getOpenTrades()
|
|
|
|
for trade in self.trades:
|
|
if trade.pair != pair:
|
|
continue
|
|
filled_buys = trade.select_filled_orders('buy')
|
|
dataframe['count_buys'] = len(filled_buys)
|
|
count = 0
|
|
amount = 0
|
|
for buy in filled_buys:
|
|
if count == 0:
|
|
dataframe['first_price'] = buy.price
|
|
dataframe['close01'] = buy.price * 1.01
|
|
|
|
# Order(id=2396, trade=1019, order_id=29870026652, side=buy, filled=0.00078, price=63921.01,
|
|
# status=closed, date=2024-08-26 02:20:11)
|
|
dataframe['last_price'] = buy.price
|
|
print(buy)
|
|
count = count + 1
|
|
amount += buy.price * buy.filled
|
|
dataframe['mid_price'] = (dataframe['last_price'] + dataframe['first_price']) / 2
|
|
count_buys = count
|
|
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:
|
|
trade = trades[-1]
|
|
print('closed trade pair is : ')
|
|
dataframe['expected_profit'] = (1 + self.expectedProfit(pair, dataframe.iloc[-1])) * dataframe[
|
|
'last_price']
|
|
|
|
dataframe['buy_level'] = dataframe['lowest_4_average'] * (1 - self.levels[count_buys] / 100)
|
|
|
|
# ======================================================================================================
|
|
|
|
dataframe['tag_min'] = (dataframe[self.close] <= dataframe['min200'] * 1.002)
|
|
dataframe['tag_percent24'] = (dataframe['percent24'] <= -self.min_max_buys[pair]['profit'])
|
|
dataframe['tag_stable'] = (dataframe['min50'].shift(6) == dataframe['min50'])
|
|
dataframe['tag_buy'] = dataframe['tag_min'].shift(6) & dataframe['tag_percent24'].shift(6) & dataframe[
|
|
'tag_stable']
|
|
|
|
self.getBinanceOrderBook(pair, dataframe)
|
|
|
|
# =========================================================================
|
|
dataframe['rsi_range'] = pd.cut(dataframe['rsi'], bins=[0,10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
|
|
labels=['00', '10', '20', '30', '40', '50', '60', '70', '80', '90'])
|
|
dataframe['rsi_percent'] = 100 * (dataframe['rsi'] - dataframe['rsi'].shift(1)) / dataframe['rsi']
|
|
dataframe['rsi_percent3'] = 100 * (dataframe['rsi'] - dataframe['rsi'].shift(3)) / dataframe['rsi']
|
|
dataframe['rsi_percent5'] = 100 * (dataframe['rsi'] - dataframe['rsi'].shift(5)) / dataframe['rsi']
|
|
dataframe['rsi_pct_range'] = pd.cut(dataframe['rsi_percent3'],
|
|
bins=[-1000, -80, -60, -40, -20, 0, 20, 40, 60, 80, 1000],
|
|
labels=['-100', '-80', '-60', '-40', '-20', '0', '20', '40', '60', '80'])
|
|
|
|
# Calcul des changements futurs
|
|
dataframe['future_change'] = dataframe['close'].shift(-12) - dataframe['close']
|
|
dataframe['future_direction'] = dataframe['future_change'].apply(lambda x: 1 if x > 0 else 0)
|
|
|
|
rsi_probabilities = dataframe.groupby('rsi_pct_range')['future_direction'].mean()
|
|
print(f"Probabilités de hausse selon rsi_pct_range :\n", rsi_probabilities)
|
|
|
|
return dataframe
|
|
|
|
def getOpenTrades(self):
|
|
# if len(self.trades) == 0:
|
|
print('search open trades')
|
|
if self.dp:
|
|
if self.dp.runmode.value in ('live', 'dry_run'):
|
|
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 populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
pair = metadata['pair']
|
|
|
|
self.getOpenTrades()
|
|
# expected_profit = self.expectedProfit(pair, dataframe.iloc[-1])
|
|
# self.getBinanceOrderBook(pair, dataframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
# limit = last_candle['first_price'] * (1 - self.baisse[last_candle['count_buys']] / 100)
|
|
|
|
# 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('calcul expected_profit ' + str(expected_profit))
|
|
|
|
buy_level = dataframe[self.open] # dataframe['buy_level'] # self.get_buy_level(pair, dataframe)
|
|
|
|
dataframe.loc[
|
|
(
|
|
(dataframe['max200_diff'] >= 0.018)
|
|
& (dataframe[self.open] < dataframe['average_line_288'])
|
|
& (dataframe[self.close] < dataframe['min12'] * 1.002)
|
|
& (dataframe['min12'].shift(2) == dataframe['min12'])
|
|
), ['enter_long', 'enter_tag']] = (1, 'buy_min_max200')
|
|
|
|
dataframe.loc[
|
|
(
|
|
(
|
|
(dataframe['percent12'].shift(3) < -0.015) |
|
|
(dataframe['percent24'].shift(3) < -0.022) |
|
|
(dataframe['percent48'].shift(3) < -0.030)
|
|
)
|
|
& (dataframe[self.close].shift(3) <= dataframe['min50'].shift(3) * 1.002)
|
|
& (dataframe[self.open].shift(3) < dataframe['average_line_50'].shift(3))
|
|
& (
|
|
(dataframe['min200'].shift(3) == dataframe['min200'])
|
|
)
|
|
), ['enter_long', 'enter_tag']] = (1, 'buy_0_percent12')
|
|
dataframe.loc[
|
|
(
|
|
(dataframe['count_buys'] == 0)
|
|
&
|
|
(
|
|
(dataframe[self.close] <= dataframe['sma5_1h'] * (1 - self.min_max_buys[pair]['profit'])) |
|
|
(dataframe['percent24'] <= -self.min_max_buys[pair]['profit'])
|
|
)
|
|
& (dataframe[self.close] <= dataframe['sma5_1h'])
|
|
& ((last_candle['close'] - last_candle['min_previous_1h']) / (last_candle['max_previous_1h'] - last_candle['close']) < 0.2)
|
|
), ['enter_long', 'enter_tag']] = (1, 'buy_percent12')
|
|
|
|
# dataframe.loc[
|
|
# (
|
|
# (dataframe['percent_1d'] <= -0.1)
|
|
# & (dataframe['min12'].shift(12) == dataframe['min12'])
|
|
# ), ['enter_long', 'enter_tag']] = (1, 'buy_crash')
|
|
|
|
nan_columns = dataframe.columns[dataframe.isna().any()].tolist()
|
|
print("Colonnes contenant des NaN :", nan_columns)
|
|
|
|
# Compter les colonnes avec uniquement des NaN
|
|
num_only_nan_columns = dataframe.isna().all().sum()
|
|
print(f"Nombre de colonnes contenant uniquement des NaN : {num_only_nan_columns}")
|
|
|
|
# Compter les NaN par colonne
|
|
nan_count_per_column = dataframe.isna().sum()
|
|
print("Nombre de NaN par colonne :")
|
|
print(nan_count_per_column)
|
|
columns_with_nan = nan_count_per_column[nan_count_per_column > 1000]
|
|
print("Colonnes avec au moins 1000 NaN :")
|
|
print(columns_with_nan)
|
|
|
|
#self.model = joblib.load('regression_model.pkl')
|
|
# Former le modèle si ce n'est pas déjà fait
|
|
if self.model is None:
|
|
print("Calculate model")
|
|
dataframe = self.add_future_direction(dataframe)
|
|
self.model = self.train_model(dataframe)
|
|
else:
|
|
print("Load model")
|
|
|
|
# Coefficients du modèle
|
|
print("Coefficients :", self.model.coef_)
|
|
|
|
# Ordonnée à l'origine (intercept)
|
|
print("Ordonnée à l'origine :", self.model.intercept_)
|
|
# Hyperparamètres du modèle
|
|
print("Hyperparamètres du modèle :", self.model.get_params())
|
|
|
|
|
|
# Faire des prédictions
|
|
dataframe = self.predict_with_model(dataframe, self.model)
|
|
# Sauvegarde du modèle
|
|
# joblib.dump(self.model, 'regression_model.pkl')
|
|
|
|
# Entrée sur un signal de hausse
|
|
# dataframe.loc[
|
|
# (dataframe['predicted_direction'] == 1),
|
|
# ['enter_long', 'enter_tag']] = (1, 'buy_predict')
|
|
|
|
return dataframe
|
|
|
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
# dataframe.loc[
|
|
# (
|
|
# (dataframe['sma5_pct_1h'].shift(24) < dataframe['sma5_pct_1h'].shift(12))
|
|
# & (dataframe['sma5_pct_1h'].shift(12) > dataframe['sma5_pct_1h'])
|
|
# & (dataframe['sma5_pct_1h'] < 0.035)
|
|
# ), ['sell', 'exit_long']] = (1, 'sell_sma5_pct_1h')
|
|
return dataframe
|
|
|
|
def adjust_trade_position(self, trade: Trade, current_time: datetime,
|
|
current_rate: float, current_profit: float, min_stake: float,
|
|
max_stake: float, **kwargs):
|
|
pair = trade.pair
|
|
# self.min_max_buys[pair][min] = min(self.min_max_buys[pair][min], current_rate)
|
|
# self.min_max_buys[pair][max] = max(self.min_max_buys[pair][max], current_rate)
|
|
|
|
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
|
|
# print(dataframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
last_candle_1 = dataframe.iloc[-2].squeeze()
|
|
last_candle_3 = dataframe.iloc[-4].squeeze()
|
|
last_candle_12 = dataframe.iloc[-13].squeeze()
|
|
last_candle_24 = dataframe.iloc[-25].squeeze()
|
|
return None
|
|
|
|
# "& (last_candle['sma5_pct_1h'] < -0.035)
|
|
if (len(dataframe) < 1) | (self.wallets.get_available_stake_amount() < 10):
|
|
return None
|
|
if pair not in ('BTC/USDT', 'DOGE/USDT', 'ETH/USDT'):
|
|
return None
|
|
if last_candle['rsi_1d'] is None:
|
|
return None
|
|
if (last_candle['ha_red_1d'] is None) | (last_candle['ha_red_1d'] == 1):
|
|
return None
|
|
max_buys = 10
|
|
|
|
filled_buys = trade.select_filled_orders('buy')
|
|
count_of_buys = len(filled_buys)
|
|
if count_of_buys >= max_buys:
|
|
return None
|
|
first_price = filled_buys[0].price
|
|
days = 0
|
|
minutes = 0
|
|
last_price = first_price
|
|
for buy in filled_buys:
|
|
minutes = (current_time - buy.order_date_utc).seconds / 60
|
|
days = (current_time - buy.order_date_utc).days
|
|
last_price = buy.price
|
|
condition = (last_candle['sma5_pct_1h'] >= 0)
|
|
dispo = round(self.wallets.get_available_stake_amount())
|
|
profit = ((current_rate - last_price) / current_rate)
|
|
rsi_1d = last_candle['rsi_1d']
|
|
|
|
if (
|
|
# (last_candle_3['volume'] * 5 < last_candle['volume'])
|
|
(last_candle['rsi_1h'] > 50)
|
|
#(last_candle['rsi_1d'] >= self.min_max_buys[pair]['last_rsi_1d'])
|
|
& (minutes > 180)
|
|
& (current_rate > max(first_price, last_price) * (1 + count_of_buys / 100))
|
|
):
|
|
stake_amount = self.calculate_stake(pair, current_rate, last_candle)
|
|
self.log_trade(
|
|
date=current_time,
|
|
action="INC price",
|
|
pair=trade.pair,
|
|
rate=current_rate,
|
|
minutes=minutes,
|
|
first_rate=first_price,
|
|
last_rate=last_price,
|
|
buys=count_of_buys,
|
|
stake=round(stake_amount, 2),
|
|
profit=round(profit * 100, 2),
|
|
dispo=dispo,
|
|
rsi=round(last_candle['rsi_1d'])
|
|
|
|
)
|
|
self.min_max_buys[pair]['last_rsi_1d'] = round(last_candle['rsi_1d'])
|
|
|
|
# logger.info(
|
|
# f"INC price={trade.pair} {current_time} minutes={minutes} first={first_price:.4f} last={last_price:.4f} rate={current_rate:.4f} buys={count_of_buys} stake={stake_amount:.4f}")
|
|
return stake_amount
|
|
|
|
# self.protection_nb_buy_lost.value
|
|
# limit = last_candle[limits[count_of_buys]]
|
|
limit = last_price * (1 - self.fibo[count_of_buys] / 100)
|
|
# limit = last_price * (0.99)
|
|
# stake_amount = min(200, self.adjust_stake_amount(pair, last_candle) * self.fibo[count_of_buys])
|
|
# stake_amount = self.config.get('stake_amount', 50) * self.fibo[count_of_buys]
|
|
stake_amount = self.calculate_stake(pair, current_rate, last_candle) * self.fibo[count_of_buys]
|
|
percent_max = (last_candle['max200'] - last_candle['close']) / last_candle['close']
|
|
if (
|
|
False
|
|
& (last_candle['rsi_1d'] >= self.min_max_buys[pair]['last_rsi_1d'])
|
|
#(last_candle_1['rsi'] < 12)
|
|
#(last_price <= first_price)
|
|
& (current_rate <= limit)
|
|
& (minutes > 180)):
|
|
fact = - profit / 0.05
|
|
stake_amount = 100 #min(self.wallets.get_available_stake_amount(), max(100, min(666, stake_amount * fact)))
|
|
self.min_max_buys[pair]['last_rsi_1d'] = round(last_candle['rsi_1d'])
|
|
|
|
self.log_trade(
|
|
date=current_time,
|
|
action="DEC price",
|
|
pair=trade.pair,
|
|
rate=current_rate,
|
|
minutes=minutes,
|
|
first_rate=first_price,
|
|
last_rate=last_price,
|
|
buys=count_of_buys,
|
|
stake=round(stake_amount, 2),
|
|
profit=round(profit * 100, 2),
|
|
dispo=dispo,
|
|
rsi=round(last_candle['rsi_1d'])
|
|
)
|
|
|
|
# logger.info(
|
|
# f"DEC price={trade.pair} {current_time} minutes={minutes} first={first_price:.4f} last={last_price:.4f} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f} rsi={rsi_1d:.4f}")
|
|
|
|
return stake_amount
|
|
|
|
# if days > 1:
|
|
# self.log_trade(
|
|
# date=current_time,
|
|
# action="NO BUY",
|
|
# pair=trade.pair,
|
|
# rate=current_rate,
|
|
# minutes=minutes,
|
|
# first_rate=first_price,
|
|
# last_rate=last_price,
|
|
# buys=count_of_buys,
|
|
# profit=round(profit * 100, 2),
|
|
# dispo=dispo,
|
|
# rsi=round(last_candle['rsi_1d'])
|
|
# )
|
|
return None
|
|
|
|
# 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}")
|
|
|
|
if (0 < count_of_buys <= max_buys) & (current_rate <= limit) & (condition) & (minutes > 180):
|
|
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"Adjust price={trade.pair} {current_time} first={first_price:.4f} rate={current_rate:.4f} buys={count_of_buys} limit={limit:.4f} stake={stake_amount:.4f}")
|
|
|
|
return min(self.wallets.get_available_stake_amount(), stake_amount)
|
|
except Exception as exception:
|
|
print(exception)
|
|
return None
|
|
return None
|
|
|
|
# def adjust_stake_amount(self, pair: str, dataframe: DataFrame):
|
|
# # Calculer le minimum des 14 derniers jours
|
|
# current_price = dataframe[self.close]
|
|
#
|
|
# v_max = max(current_price, self.min_max_buys[pair]['max'])
|
|
# v_min = min(current_price, self.min_max_buys[pair]['min'])
|
|
#
|
|
# adjusted_stake_amount = self.config['stake_amount'] * v_min / v_max
|
|
#
|
|
# #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[self.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 expectedProfit(self, pair: str, dataframe: DataFrame):
|
|
|
|
current_price = dataframe['last_price'] # dataframe[self.close]
|
|
|
|
# trade = self.getTrade(pair)
|
|
# if trade:
|
|
# current_price = trade.open_rate
|
|
|
|
# Calculer le max des 14 derniers jours
|
|
min_14_days = dataframe['lowest_1d']
|
|
max_14_days = dataframe['highest_1d']
|
|
percent = (max_14_days - current_price) / (min_14_days)
|
|
|
|
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}")
|
|
|
|
# self.analyze_conditions(pair, dataframe)
|
|
return expected_profit
|
|
|
|
def getBinanceOrderBook(self, pair, dataframe: DataFrame):
|
|
"""Fetch the order book (depth) from Binance."""
|
|
# print(dataframe)
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
symbol = pair.replace('/', '')
|
|
|
|
try:
|
|
url = f"https://api.binance.com/api/v3/depth?symbol={symbol}&limit=5000"
|
|
response = requests.get(url)
|
|
data = response.json()
|
|
|
|
# Extract bids and asks from the order book
|
|
asks, bids = self.calculateSMA(20, data['asks'], data['bids']) # Ventes List of [price, quantity]
|
|
# bids = data['bids']
|
|
# asks = data['asks'] # Achats List of [price, quantity]
|
|
|
|
# Process the depth data as you need it
|
|
# bid_volume = sum([float(bid[1]) for bid in bids]) # Sum of all bid volumes
|
|
# $ * nb / $ => nb
|
|
bid_volume = sum([float(bid[0]) * float(bid[1]) / float(last_candle[self.close]) for bid in bids[:10]])
|
|
# ask_volume = sum([float(ask[1]) for ask in asks]) # Sum of all ask volumes
|
|
ask_volume = sum([float(ask[0]) * float(ask[1]) / float(last_candle[self.close]) for ask in asks[:10]])
|
|
|
|
# Example: add the difference in volumes as an indicator
|
|
if bid_volume and ask_volume:
|
|
self.updateLastValue(dataframe, 'depth_bid_ask_diff', round(bid_volume - ask_volume, 2))
|
|
else:
|
|
self.updateLastValue(dataframe, 'depth_bid_ask_diff', 0)
|
|
|
|
# probabilité baisse hausse sur les n premiers élements
|
|
for start in [0, 50, 100, 150]:
|
|
self.updateLastValue(dataframe, 'prob_hausse_' + str(start + 50),
|
|
self.calculateProbaNb(asks, bids, start, start + 50))
|
|
# dataframe['prob_hausse_' + str(nb)] = self.calculateProbaNb(asks, bids, nb)
|
|
# Analyse des prix moyens pondérés par les volumes (VWAP) :
|
|
#
|
|
# Le VWAP (Volume Weighted Average Price) peut être utilisé pour comprendre la pression directionnelle.
|
|
# Si le VWAP basé sur les ordres d'achat est plus élevé que celui des ordres de vente,
|
|
# cela peut indiquer une probabilité de hausse.
|
|
nb = 50
|
|
|
|
bid_vwap = sum([float(bid[0]) * float(bid[1]) for bid in bids[:nb]]) / sum(
|
|
[float(bid[1]) for bid in bids[:nb]])
|
|
ask_vwap = sum([float(ask[0]) * float(ask[1]) for ask in asks[:nb]]) / sum(
|
|
[float(ask[1]) for ask in asks[:nb]])
|
|
|
|
if bid_vwap > ask_vwap:
|
|
self.updateLastValue(dataframe, 'vwap_hausse',
|
|
round(100 * (bid_vwap - ask_vwap) / (bid_vwap + ask_vwap), 2))
|
|
else:
|
|
self.updateLastValue(dataframe, 'vwap_hausse',
|
|
- round(100 * (ask_vwap - bid_vwap) / (bid_vwap + ask_vwap), 2))
|
|
|
|
current_price = last_candle[self.close] # le prix actuel du marché
|
|
|
|
# Calcul du seuil de variation de 1%
|
|
lower_threshold = current_price * 0.99
|
|
upper_threshold = current_price * 1.01
|
|
|
|
# Volumes d'achat (bids) sous 1% du prix actuel
|
|
bid_volume_1percent = sum(
|
|
[float(bid[1]) for bid in bids if current_price >= float(bid[0]) >= lower_threshold])
|
|
|
|
# Volumes de vente (asks) au-dessus de 1% du prix actuel
|
|
ask_volume_1percent = sum(
|
|
[float(ask[1]) for ask in asks if current_price <= float(ask[0]) <= upper_threshold])
|
|
|
|
# Estimation de la probabilité basée sur le déséquilibre des volumes
|
|
total_volume = bid_volume_1percent + ask_volume_1percent
|
|
if total_volume > 0:
|
|
prob_hausse = bid_volume_1percent / total_volume
|
|
prob_baisse = ask_volume_1percent / total_volume
|
|
else:
|
|
prob_hausse = prob_baisse = 0
|
|
|
|
self.updateLastValue(dataframe, 'proba_hausse_1%', round(prob_hausse * 100, 2))
|
|
self.updateLastValue(dataframe, 'proba_baisse_1%', round(prob_baisse * 100, 2))
|
|
print(f"Probabilité de hausse de 1%: {prob_hausse * 100:.2f}%")
|
|
print(f"Probabilité de baisse de 1%: {prob_baisse * 100:.2f}%")
|
|
|
|
self.calculateResistance(pair, asks, dataframe)
|
|
self.calculateSupport(pair, bids, dataframe)
|
|
|
|
dataframe['r_s'] = 100 * (dataframe['r_min'] - dataframe['s_min']) / dataframe['s_min']
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error fetching order book: {e}")
|
|
return None, None
|
|
|
|
def calculateProbaNb(self, asks, bids, start, nb):
|
|
top_bids = sum([float(bid[1]) for bid in bids[start:nb]])
|
|
top_asks = sum([float(ask[1]) for ask in asks[start:nb]])
|
|
if top_bids > top_asks:
|
|
proba = round(100 * (top_bids - top_asks) / (top_bids + top_asks), 2)
|
|
else:
|
|
proba = - round(100 * (top_asks - top_bids) / (top_bids + top_asks), 2)
|
|
return proba
|
|
|
|
def calculateResistance(self, pair, asks, dataframe: DataFrame):
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
|
|
# Filtrage +-5%
|
|
current_price = float(last_candle[self.close])
|
|
lower_bound = current_price * 0.95
|
|
upper_bound = current_price * 1.05
|
|
ask_prices = [float(ask[0]) for ask in asks]
|
|
ask_volumes = [float(ask[1]) for ask in asks]
|
|
ask_df = pd.DataFrame({'price': ask_prices, 'volume': ask_volumes})
|
|
filtered_ask_df = ask_df[(ask_df['price'] >= lower_bound) & (ask_df['price'] <= upper_bound)]
|
|
# Trier le DataFrame sur la colonne 'volume' en ordre décroissant
|
|
sorted_ask_df = filtered_ask_df.sort_values(by='volume', ascending=False)
|
|
|
|
# Ne garder que les 3 premières lignes (les 3 plus gros volumes)
|
|
top_3_asks = sorted_ask_df.head(3)
|
|
print(top_3_asks)
|
|
|
|
# Convertir les ordres de vente en numpy array pour faciliter le traitement
|
|
asks_array = np.array(filtered_ask_df, dtype=float)
|
|
|
|
# Détecter les résistances : on peut définir qu'une résistance est un niveau de prix où la quantité est élevée
|
|
# Ex: seuil de résistance à partir des 10% des plus grosses quantités
|
|
resistance_threshold = np.percentile(asks_array[:, 1], 90)
|
|
resistances = asks_array[asks_array[:, 1] >= resistance_threshold]
|
|
|
|
# Afficher les résistances trouvées
|
|
# print(f"{pair} Niveaux de résistance détectés:")
|
|
# for resistance in resistances:
|
|
# print(f"{pair} Prix: {resistance[0]}, Quantité: {resistance[1]}")
|
|
|
|
# Exemple : somme des quantités sur des plages de prix
|
|
# Intervalles de 10 USDT
|
|
step = last_candle[self.close] / 100
|
|
price_intervals = np.arange(asks_array[:, 0].min(), asks_array[:, 0].max(), step=step)
|
|
|
|
for start_price in price_intervals:
|
|
end_price = start_price + step
|
|
mask = (asks_array[:, 0] >= start_price) & (asks_array[:, 0] < end_price)
|
|
volume_in_range = asks_array[mask, 1].sum()
|
|
amount = volume_in_range * end_price
|
|
print(
|
|
f"Prix entre {start_price:.6f} et {end_price:.6f}: Volume total = {volume_in_range:.2f} amount={amount:.2f}")
|
|
|
|
# Trier les asks par quantité en ordre décroissant
|
|
asks_sorted = asks_array[asks_array[:, 1].argsort()][::-1]
|
|
|
|
# Sélectionner les trois plus gros resistances
|
|
top_3_resistances = asks_sorted[:3]
|
|
|
|
# Afficher les trois plus gros resistances
|
|
print("Les trois plus grosses resistances détectées : ")
|
|
self.updateLastValue(dataframe, 'r3', top_3_resistances[0][0])
|
|
self.updateLastValue(dataframe, 'r2', top_3_resistances[1][0])
|
|
self.updateLastValue(dataframe, 'r1', top_3_resistances[2][0])
|
|
self.updateLastValue(dataframe, 'r_min',
|
|
min(top_3_resistances[0][0], top_3_resistances[1][0], top_3_resistances[2][0]))
|
|
for resistance in top_3_resistances:
|
|
print(f"{pair} Prix: {resistance[0]}, Quantité: {resistance[1]}")
|
|
|
|
def calculateSupport(self, pair, bids, dataframe: DataFrame):
|
|
last_candle = dataframe.iloc[-1].squeeze()
|
|
|
|
# Convert to pandas DataFrame to apply moving average
|
|
current_price = float(last_candle[self.close])
|
|
lower_bound = current_price * 0.95
|
|
upper_bound = current_price * 1.05
|
|
bid_prices = [float(bid[0]) for bid in bids]
|
|
bid_volumes = [float(bid[1]) for bid in bids]
|
|
bid_df = pd.DataFrame({'price': bid_prices, 'volume': bid_volumes})
|
|
filtered_bid_df = bid_df[(bid_df['price'] >= lower_bound) & (bid_df['price'] <= upper_bound)]
|
|
# Trier le DataFrame sur la colonne 'volume' en ordre décroissant
|
|
sorted_bid_df = filtered_bid_df.sort_values(by='volume', ascending=False)
|
|
|
|
# Ne garder que les 3 premières lignes (les 3 plus gros volumes)
|
|
top_3_bids = sorted_bid_df.head(3)
|
|
print(top_3_bids)
|
|
|
|
# Convertir les ordres d'achat en numpy array pour faciliter le traitement
|
|
bids_array = np.array(filtered_bid_df, dtype=float)
|
|
|
|
# Détecter les supports : on peut définir qu'un support est un niveau de prix où la quantité est élevée
|
|
# Ex: seuil de support à partir des 10% des plus grosses quantités
|
|
support_threshold = np.percentile(bids_array[:, 1], 90)
|
|
supports = bids_array[bids_array[:, 1] >= support_threshold]
|
|
|
|
# Afficher les supports trouvés
|
|
# print(f"{pair} Niveaux de support détectés:")
|
|
# for support in supports:
|
|
# print(f"{pair} Prix: {support[0]}, Quantité: {support[1]}")
|
|
|
|
step = last_candle[self.close] / 100
|
|
# Exemple : somme des quantités sur des plages de prix pour les bids
|
|
price_intervals = np.arange(bids_array[:, 0].min(), bids_array[:, 0].max(), step=step) # Intervalles de 10 USDT
|
|
|
|
for start_price in price_intervals:
|
|
end_price = start_price + step
|
|
mask = (bids_array[:, 0] >= start_price) & (bids_array[:, 0] < end_price)
|
|
volume_in_range = bids_array[mask, 1].sum()
|
|
amount = volume_in_range * end_price
|
|
print(
|
|
f"Prix entre {start_price:.6f} et {end_price:.6f}: Volume total = {volume_in_range:.2f} amount={amount:.2f}")
|
|
|
|
# Trier les bids par quantité en ordre décroissant
|
|
bids_sorted = bids_array[bids_array[:, 1].argsort()][::-1]
|
|
|
|
# Sélectionner les trois plus gros supports
|
|
top_3_supports = bids_sorted[:3]
|
|
|
|
# Afficher les trois plus gros supports
|
|
print("Les trois plus gros supports détectés:")
|
|
|
|
self.updateLastValue(dataframe, 's1', top_3_supports[0][0])
|
|
self.updateLastValue(dataframe, 's2', top_3_supports[1][0])
|
|
self.updateLastValue(dataframe, 's3', top_3_supports[2][0])
|
|
self.updateLastValue(dataframe, 's_min', max(top_3_supports[0][0], top_3_supports[1][0], top_3_supports[2][0]))
|
|
|
|
for support in top_3_supports:
|
|
print(f"{pair} Prix: {support[0]}, Quantité: {support[1]}")
|
|
|
|
def updateLastValue(self, df: DataFrame, col, value):
|
|
if col in df.columns:
|
|
print(f"update last col {col} {value}")
|
|
df.iloc[-1, df.columns.get_loc(col)] = value
|
|
else:
|
|
print(f"update all col {col} {value}")
|
|
df[col] = value
|
|
|
|
# def update_last_record(self, dataframe: DataFrame, new_data):
|
|
# # Vérifiez si de nouvelles données ont été reçues
|
|
# if new_data is not None:
|
|
# # Ne mettez à jour que la dernière ligne du dataframe
|
|
# last_index = dataframe.index[-1] # Sélectionne le dernier enregistrement
|
|
# dataframe.loc[last_index] = new_data # Met à jour le dernier enregistrement avec les nouvelles données
|
|
# return dataframe
|
|
|
|
def calculateSMA(self, nb, asks, bids):
|
|
# Prepare data for plotting
|
|
bid_prices = [float(bid[0]) for bid in bids]
|
|
bid_volumes = [float(bid[1]) for bid in bids]
|
|
|
|
ask_prices = [float(ask[0]) for ask in asks]
|
|
ask_volumes = [float(ask[1]) for ask in asks]
|
|
|
|
# Convert to pandas DataFrame to apply moving average
|
|
bid_df = pd.DataFrame({'price': bid_prices, 'volume': bid_volumes})
|
|
ask_df = pd.DataFrame({'price': ask_prices, 'volume': ask_volumes})
|
|
|
|
# Apply a rolling window to calculate a 10-value simple moving average (SMA)
|
|
bid_df['volume_sma'] = bid_df['volume'].rolling(window=nb).mean()
|
|
ask_df['volume_sma'] = ask_df['volume'].rolling(window=nb).mean()
|
|
|
|
# Pour bid_df
|
|
bid_df = bid_df.dropna(subset=['volume_sma'])
|
|
bids_with_sma = list(zip(bid_df['price'], bid_df['volume_sma']))
|
|
|
|
# Pour ask_df
|
|
ask_df = ask_df.dropna(subset=['volume_sma'])
|
|
asks_with_sma = list(zip(ask_df['price'], ask_df['volume_sma']))
|
|
|
|
# print(bids_with_sma)
|
|
# print(asks_with_sma)
|
|
|
|
return asks_with_sma, bids_with_sma
|
|
|
|
def calculate_stake(self, pair, value, last_candle):
|
|
"""
|
|
Calcule la mise en pourcentage (entre 20% et 100%) pour une paire donnée
|
|
en fonction de la valeur actuelle et de la plage min/max de la paire.
|
|
|
|
Parameters:
|
|
pair (str): La paire (ex: "BTC/USDT").
|
|
value (float): La valeur actuelle de la paire.
|
|
|
|
Returns:
|
|
float: Le pourcentage de mise, entre 20% et 100%.
|
|
"""
|
|
# if pair not in self.min_max_buys:
|
|
# raise ValueError(f"La paire {pair} n'est pas définie dans min_max_buys")
|
|
|
|
min_val = last_candle['min_previous_1h'] # self.min_max_buys[pair]["min"]
|
|
max_val = last_candle['max_previous_1h'] # self.min_max_buys[pair]["max"]
|
|
|
|
amount = self.config['stake_amount']
|
|
return amount
|
|
if (max_val == min_val) | (np.isnan(max_val)):
|
|
return amount
|
|
if value <= min_val:
|
|
return amount # Mise minimale de 20%
|
|
elif value >= max_val:
|
|
return amount / 5 # Mise maximale de 100%
|
|
else:
|
|
# Calcul de la position relative entre min et max
|
|
position = (value - min_val) / (max_val - min_val)
|
|
# Interpolation linéaire entre 20% et 100%
|
|
stake = amount - (position * (amount * 4 / 5))
|
|
return stake
|