diff --git a/strategies/bol_trend/Backtester.py b/strategies/bol_trend/Backtester.py new file mode 100644 index 0000000..aba474b --- /dev/null +++ b/strategies/bol_trend/Backtester.py @@ -0,0 +1,355 @@ +import pandas as pd +import numpy as np +import ccxt +import ta +from datetime import datetime, timedelta +import matplotlib.pyplot as plt +from tabulate import tabulate + + +class Backtester: + def __init__(self, params_coin, timeframe='1m', start_date=None, end_date=None, leverage=1, taker_fee=0.0006): + self.params_coin = params_coin + self.timeframe = timeframe + self.start_date = start_date + self.end_date = end_date + self.leverage = leverage # Levier + self.taker_fee = taker_fee # Frais de taker + self.exchange = ccxt.binance() # Vous pouvez changer d'échange si nécessaire + self.data = {} + self.results = {} + self.initial_balance = 1000 # Capital initial en USDT + self.balance = self.initial_balance + self.total_fees = 0 # Total des frais payés + + def fetch_data(self): + print("Fetching historical data...") + for pair in self.params_coin: + symbol = pair.replace(':', '') + data = [] + since = self.exchange.parse8601(self.start_date.isoformat()) + end_timestamp = self.exchange.parse8601(self.end_date.isoformat()) + while since < end_timestamp: + try: + ohlcv = self.exchange.fetch_ohlcv(symbol, timeframe=self.timeframe, since=since, limit=1000) + if not ohlcv: + break + df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']) + since = ohlcv[-1][0] + 1 + data.append(df) + except Exception as e: + print(f"Error fetching data for {pair}: {e}") + break + if data: + df_pair = pd.concat(data) + df_pair['timestamp'] = pd.to_datetime(df_pair['timestamp'], unit='ms') + df_pair.set_index('timestamp', inplace=True) + self.data[pair] = df_pair + else: + print(f"No data fetched for {pair}") + + def calculate_indicators(self): + print("Calculating indicators...") + for pair in self.data: + df = self.data[pair] + params = self.params_coin[pair] + + # Bandes de Bollinger + bol_band = ta.volatility.BollingerBands(close=df["close"], window=params["bb_window"], window_dev=params["bb_std"]) + df["lower_band"] = bol_band.bollinger_lband() + df["higher_band"] = bol_band.bollinger_hband() + df["ma_band"] = bol_band.bollinger_mavg() + + # EMA 20 et sa pente + df['ema_20'] = ta.trend.ema_indicator(close=df['close'], window=20) + df['ema_20_slope_pct'] = df['ema_20'].pct_change() * 100 + + # EMA à long terme + df['long_ema'] = ta.trend.ema_indicator(close=df['close'], window=params["long_ma_window"]) + df['long_ma'] = ta.trend.sma_indicator(close=df['close'], window=params["long_ma_window"]) + + # Décalage des valeurs pour la condition sur n-1 + df["n1_close"] = df["close"].shift(1) + df["n1_higher_band"] = df["higher_band"].shift(1) + + # Calcul du RSI + df['rsi'] = ta.momentum.rsi(close=df['close'], window=14) + + # Calcul du MACD + macd = ta.trend.MACD(close=df['close']) + df['macd'] = macd.macd() + df['macd_signal'] = macd.macd_signal() + + # Calcul de la largeur des Bandes de Bollinger en pourcentage + df['bb_width'] = df['higher_band'] - df['lower_band'] + df['bb_width_pct'] = (df['bb_width'] / df['ma_band']) * 100 + + # Calcul de la moyenne mobile du volume + df['volume_mean'] = df['volume'].rolling(window=20).mean() + + # Supprimer les lignes avec des valeurs manquantes + df.dropna(inplace=True) + + # Enregistrer les données avec les indicateurs + self.data[pair] = df + + def open_long(self, row, params): + ema_slope_tolerance = params["ema_slope_tolerance"] + bb_width_tolerance = params["bb_width_tolerance"] + rsi_threshold = params["rsi_threshold"] + + if (row['n1_close'] < row['n1_higher_band'] + and row['volume'] > row['volume_mean'] + # and row['close'] > row['ema_20'] + and row['close'] > row['higher_band'] + and row['close'] > row['long_ma'] + # and row['ema_20_slope_pct'] > -ema_slope_tolerance + # and row['bb_width_pct'] < bb_width_tolerance + and row['rsi'] > rsi_threshold + ): + return True + else: + return False + + def close_long(self, row, position, params): + rsi_exit_threshold = params.get('rsi_exit_threshold', 70) + macd_exit = params.get('macd_exit', True) + trailing_stop_percentage = params.get('trailing_stop_percentage', 2.0) + + # Mise à jour du plus haut atteint pour le trailing stop-loss + highest_price = max(position['highest_price'], row['high']) + position['highest_price'] = highest_price + trailing_stop_price = highest_price * (1 - trailing_stop_percentage / 100) + + condition1 = row['close'] < row['ma_band'] + condition2 = row['rsi'] > rsi_exit_threshold + condition3 = macd_exit and (row['macd'] < row['macd_signal']) + condition4 = row['close'] <= trailing_stop_price + + if condition1 or condition2 or condition3 or condition4: + return True + else: + return False + + def run_backtest(self): + print("Running backtest...") + self.positions = {} + self.trades = [] + for pair in self.data: + df = self.data[pair] + params = self.params_coin[pair] + wallet_exposure = params["wallet_exposure"] + position = None + + for index, row in df.iterrows(): + # Vérifier si une position est ouverte + if position is None: + # Vérifier les conditions d'ouverture + if self.open_long(row, params): + # Ouvrir une position + position_size_usd = self.balance * wallet_exposure * self.leverage # Appliquer le levier + required_margin = position_size_usd / self.leverage # Marge nécessaire + # Calcul des frais d'ouverture + opening_fee = position_size_usd * self.taker_fee + total_cost = required_margin + opening_fee + if total_cost > self.balance: + print(f"Insufficient balance to open position on {pair}") + continue + position_size = position_size_usd / row['close'] + position = { + 'entry_price': row['close'], + 'entry_time': index, + 'size': position_size, + 'highest_price': row['high'] + } + # Calcul du prix de liquidation + position['liquidation_price'] = position['entry_price'] * (1 - 1 / self.leverage) + self.balance -= total_cost # Déduire la marge et les frais du solde + self.total_fees += opening_fee + print(f"Opened position on {pair} at {index} price {row['close']} with leverage {self.leverage}") + else: + # Vérifier si la position est liquidée + if row['low'] <= position['liquidation_price']: + # La position est liquidée + required_margin = position['size'] * position['entry_price'] / self.leverage + pnl = -required_margin # Perte égale à la marge initiale + self.total_fees += 0 # Pas de frais supplémentaires en cas de liquidation (ou ajouter si applicable) + self.trades.append({ + 'pair': pair, + 'entry_time': position['entry_time'], + 'exit_time': index, + 'entry_price': position['entry_price'], + 'exit_price': position['liquidation_price'], + 'pnl': pnl, + 'result': 'Liquidation' + }) + print(f"Position liquidée sur {pair} à {index} au prix {position['liquidation_price']} PnL: {pnl:.2f} USDT") + position = None + continue # Passer à la prochaine itération + + # Vérifier les conditions de clôture + if self.close_long(row, position, params): + # Fermer la position + exit_price = row['close'] + required_margin = position['size'] * position['entry_price'] / self.leverage + # Calcul des frais de clôture + closing_fee = (position['size'] * exit_price * self.leverage) * self.taker_fee + price_difference = (exit_price - position['entry_price']) + gross_pnl = price_difference * position['size'] * self.leverage # PnL brut avant frais + pnl = gross_pnl - closing_fee # PnL net après frais de clôture + self.balance += required_margin # Récupérer la marge initiale + self.balance += pnl # Ajouter le profit ou la perte net(te) + self.total_fees += closing_fee + # Déterminer si le trade est gagnant ou perdant + if pnl > 0: + trade_result = "Gagnant" + else: + trade_result = "Perdant" + self.trades.append({ + 'pair': pair, + 'entry_time': position['entry_time'], + 'exit_time': index, + 'entry_price': position['entry_price'], + 'exit_price': exit_price, + 'pnl': pnl, + 'result': trade_result # Ajouter le résultat du trade + }) + print(f"Closed {trade_result} position on {pair} at {index} price {row['close']} PnL: {pnl:.2f} USDT") + position = None # Réinitialiser la position + + # Si une position est encore ouverte à la fin des données + if position is not None: + exit_price = df.iloc[-1]['close'] + required_margin = position['size'] * position['entry_price'] / self.leverage + # Vérifier si la position est liquidée + if df.iloc[-1]['low'] <= position['liquidation_price']: + # La position est liquidée + pnl = -required_margin # Perte égale à la marge initiale + self.total_fees += 0 # Pas de frais supplémentaires en cas de liquidation (ou ajouter si applicable) + self.trades.append({ + 'pair': pair, + 'entry_time': position['entry_time'], + 'exit_time': df.iloc[-1].name, + 'entry_price': position['entry_price'], + 'exit_price': position['liquidation_price'], + 'pnl': pnl, + 'result': 'Liquidation' + }) + print(f"Position liquidée sur {pair} à {df.iloc[-1].name} au prix {position['liquidation_price']} PnL: {pnl:.2f} USDT") + else: + # Fermer la position normalement + # Calcul des frais de clôture + closing_fee = (position['size'] * exit_price * self.leverage) * self.taker_fee + price_difference = (exit_price - position['entry_price']) + gross_pnl = price_difference * position['size'] * self.leverage # PnL brut avant frais + pnl = gross_pnl - closing_fee # PnL net après frais de clôture + self.balance += required_margin # Récupérer la marge initiale + self.balance += pnl # Ajouter le profit ou la perte net(te) + self.total_fees += closing_fee + # Déterminer si le trade est gagnant ou perdant + if pnl > 0: + trade_result = "Gagnant" + else: + trade_result = "Perdant" + self.trades.append({ + 'pair': pair, + 'entry_time': position['entry_time'], + 'exit_time': df.iloc[-1].name, + 'entry_price': position['entry_price'], + 'exit_price': exit_price, + 'pnl': pnl, + 'result': trade_result # Ajouter le résultat du trade + }) + print(f"Closed remaining {trade_result} position on {pair} at {df.iloc[-1].name} price {exit_price} PnL: {pnl:.2f} USDT") + position = None # Réinitialiser la position + + self.final_balance = self.balance + self.results = { + 'initial_balance': self.initial_balance, + 'final_balance': self.final_balance, + 'profit': self.final_balance - self.initial_balance, + 'trades': self.trades + } + + def calculate_hold_profit(self): + self.hold_profits = {} + for pair in self.data: + df = self.data[pair] + start_price = df.iloc[0]['close'] + end_price = df.iloc[-1]['close'] + price_change = (end_price - start_price) / start_price * 100 + self.hold_profits[pair] = price_change + print(f"Hold profit for {pair}: {price_change:.2f}%") + + def print_results(self): + print("\nBacktest Results:") + print(f"Initial Balance: {self.initial_balance:.2f} USDT") + print(f"Final Balance: {self.final_balance:.2f} USDT") + net_profit = self.final_balance - self.initial_balance + profit_percentage = net_profit / self.initial_balance * 100 + print(f"Net Profit: {net_profit:.2f} USDT ({profit_percentage:.2f}%)") + print(f"Total Fees Paid: {self.total_fees:.2f} USDT") + print(f"Total Trades: {len(self.trades)}") + + wins = [t for t in self.trades if t['result'] == 'Gagnant'] + losses = [t for t in self.trades if t['result'] == 'Perdant'] + liquidations = [t for t in self.trades if t['result'] == 'Liquidation'] + total_win_pnl = sum(t['pnl'] for t in wins) + total_loss_pnl = sum(t['pnl'] for t in losses) + total_liq_pnl = sum(t['pnl'] for t in liquidations) + + print(f"Winning Trades: {len(wins)}") + print(f"Losing Trades: {len(losses)}") + print(f"Liquidated Trades: {len(liquidations)}") + print(f"Total Winning PnL: {total_win_pnl:.2f} USDT") + print(f"Total Losing PnL: {total_loss_pnl:.2f} USDT") + print(f"Total Liquidation PnL: {total_liq_pnl:.2f} USDT") + if len(self.trades) > 0: + win_rate = len(wins) / len(self.trades) * 100 + print(f"Win Rate: {win_rate:.2f}%") + else: + win_rate = 0 + print("No trades executed.") + + # Afficher les profits du holding + print("\nHold Profits:") + for pair in self.hold_profits: + print(f"{pair}: {self.hold_profits[pair]:.2f}%") + + # Comparaison globale + avg_hold_profit = sum(self.hold_profits.values()) / len(self.hold_profits) + print(f"\nAverage Hold Profit: {avg_hold_profit:.2f}%") + print(f"Strategy Profit: {profit_percentage:.2f}%") + + + table = [ + ["Initial Balance", f"{self.initial_balance:.2f} USDT"], + ["Final Balance", f"{self.final_balance:.2f} USDT"], + ["Net Profit", f"{net_profit:.2f} USDT ({profit_percentage:.2f}%)"], + ["Total Fees Paid", f"{self.total_fees:.2f} USDT"], + ["Total Trades", f"{len(self.trades)}"], + ["Winning Trades", f"{len(wins)}"], + ["Losing Trades", f"{len(losses)}"], + ["Liquidated Trades", f"{len(liquidations)}"], + ["Total Winning PnL", f"{total_win_pnl:.2f} USDT"], + ["Total Losing PnL", f"{total_loss_pnl:.2f} USDT"], + ["Total Liquidation PnL", f"{total_liq_pnl:.2f} USDT"], + ["Win Rate", f"{win_rate:.2f}%"], + # ["Max Drawdown", f"{self.max_drawdown:.2f}%"], + ["Average Hold Profit", f"{avg_hold_profit:.2f}%"], + ["Strategy Profit", f"{profit_percentage:.2f}%"] + ] + + # Affichage du tableau + print("\nBacktest Results:") + print(tabulate(table, headers=["Metric", "Value"], tablefmt="grid")) + + def plot_equity_curve(self): + equity = [self.initial_balance] + for trade in self.trades: + equity.append(equity[-1] + trade['pnl']) + plt.plot(equity) + plt.title('Equity Curve') + plt.xlabel('Trades') + plt.ylabel('Balance (USDT)') + plt.show() diff --git a/strategies/bol_trend/backtest.py b/strategies/bol_trend/backtest.py new file mode 100644 index 0000000..f3debca --- /dev/null +++ b/strategies/bol_trend/backtest.py @@ -0,0 +1,37 @@ +from datetime import datetime, timedelta + +from strategies.bol_trend.Backtester import Backtester + +params_coin = { + "SOL/USDT": { + "wallet_exposure": 0.99, + "bb_window": 50, + "bb_std": 2.25, + "long_ma_window": 100, + "ema_slope_tolerance": 0.0, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 100 + }, + # Ajoutez d'autres paires si nécessaire +} + +start_date = datetime.now() - timedelta(days=120) # 4 mois (~30 jours par mois) +end_date = datetime.now() + +backtester = Backtester( + params_coin=params_coin, + timeframe='1h', + start_date=start_date, + end_date=end_date, + leverage=4, + taker_fee=0.0006 # Frais de taker de 0.06% +) +backtester.fetch_data() +backtester.calculate_indicators() +backtester.run_backtest() +backtester.calculate_hold_profit() +backtester.print_results() +backtester.plot_equity_curve() \ No newline at end of file diff --git a/strategies/bol_trend/strategy_bitget.py b/strategies/bol_trend/strategy_bitget.py index ac24f4c..ff7e25f 100644 --- a/strategies/bol_trend/strategy_bitget.py +++ b/strategies/bol_trend/strategy_bitget.py @@ -23,10 +23,10 @@ production = True pair = "BTC/USDT:USDT" -timeframe = "1h" +minuteframe = 60 leverage = 1 -print(f"--- {pair} {timeframe} Leverage x {leverage} ---") +print(f"--- {pair} {minuteframe} Leverage x {leverage} ---") type = ["long", "short"] bol_window = 100 @@ -75,7 +75,7 @@ def close_short(row): ) # Get data -df = bitget.get_more_last_historical_async(pair, timeframe, 1000) +df = bitget.get_more_last_historical_async(pair, minuteframe + "m", 1000, minuteframe) # Populate indicator df.drop(columns=df.columns.difference(['open','high','low','close','volume']), inplace=True) diff --git a/strategies/bol_trend/strategy_multi_bitget.py b/strategies/bol_trend/strategy_multi_bitget.py index fffed40..25a78c1 100644 --- a/strategies/bol_trend/strategy_multi_bitget.py +++ b/strategies/bol_trend/strategy_multi_bitget.py @@ -1,297 +1,396 @@ +import logging +import os import sys +import traceback + +MINUTEFRAME = 1 +MAX_POS = 10 + sys.path.append("./live_tools") -import ccxt import ta -import pandas as pd from utilities.perp_bitget import PerpBitget -from utilities.custom_indicators import get_n_columns -from utilities.var import ValueAtRisk from datetime import datetime -import time import json -import copy +import pandas as pd + +data_directory = "./data" +if not os.path.exists(data_directory): + os.makedirs(data_directory) now = datetime.now() current_time = now.strftime("%d/%m/%Y %H:%M:%S") print("--- Start Execution Time :", current_time, "---") - +# f = open( - "./live_tools/secret.json", + "../../secret.json", ) +# f = open( +# "./live_tools/secret.json", +# ) + secret = json.load(f) f.close() account_to_select = "bitget_exemple" production = True -timeframe = "1h" -type = ["long", "short"] -leverage = 1 -max_var = 1 -max_side_exposition = 1 +timeframe = "1m" +type = ["long"] +leverage = 5 +max_var = 5 +max_side_exposition = 1.55 + +margin_mode = "fixed" # fixed or crossed +exchange_leverage = 5 params_coin = { - "BTC/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 - }, + "AAVE/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "LDO/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "KAS/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 }, - "APE/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "APT/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 + "SOL/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "BGB/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 }, "AVAX/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "AXS/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "C98/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "CRV/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 }, "DOGE/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "DOT/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "DYDX/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "ETH/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "FIL/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "FTM/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "BNB/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "GALA/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "GMT/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "GRT/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "KNC/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "KSM/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 - }, - "LRC/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "MANA/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "MASK/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "MATIC/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + + "FLOKI/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "PYTH/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "PEPE/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "SUI/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 }, "NEAR/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "ONE/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "OP/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 - }, - "SAND/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "SHIB/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "SOL/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "ICP/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "POL/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "XRP/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 }, - "STG/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "WAVES/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 - }, - "YFI/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "WOO/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 1, - "long_ma_window": 500 - }, - "EGLD/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 - }, - "ETC/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 + "APE/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "EOS/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 }, - "JASMY/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 + "GALA/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 }, - "ROSE/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 + "ETH/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "UXLINK/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "POPCAT/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "1000BONK/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 + }, + "1000000MOG/USDT:USDT": { + "wallet_exposure": 0.1, + "bb_window": 50, + "bb_std": 2, + "long_ma_window": 98, + "ma_slope_tolerance": 0.05, + "bb_width_tolerance": 5.0, + "rsi_threshold": 50, + "rsi_exit_threshold": 70, + "macd_exit": True, + "trailing_stop_percentage": 2.0 }, } -def open_long(row): - if ( - row['n1_close'] < row['n1_higher_band'] - and (row['close'] > row['higher_band']) - and (row['close'] > row['long_ma']) - ): - return True - else: - return False -def close_long(row): - if (row['close'] < row['ma_band']): - return True - else: - return False +def open_long(row, pair): + params = params_coin[pair] + ma_slope_tolerance = params["ma_slope_tolerance"] + bb_width_tolerance = params["bb_width_tolerance"] + rsi_threshold = params["rsi_threshold"] -def open_short(row): - if ( - row['n1_close'] > row['n1_lower_band'] - and (row['close'] < row['lower_band']) - and (row['close'] < row['long_ma']) + + + if (row['n1_close'] < row['n1_higher_band'] + and row['volume'] > row['volume'].rolling(20).mean() + and row['close'] > row['long_ema_20'] + and row['close'] > row['higher_band'] + and row['close'] > row['long_ma'] + and row['ma_20_slope_pct'] > -ma_slope_tolerance + and row['bb_width_pct'] < bb_width_tolerance + and row['rsi'] > rsi_threshold ): + print( + f"Check open_long for {pair}: long_ma={row['long_ma']}, close={row['close']}, " + f"n1_close={row['n1_close']}, higher_band={row['higher_band']}, " + f"n1_higher_band={row['n1_higher_band']}, ma_band={row['ma_band']}, " + f"ma_20_slope_pct={row['ma_20_slope_pct']}, bb_width_pct={row['bb_width_pct']}, rsi={row['rsi']}" + f"long_ma={row['long_ma']}" + ) + print(f"Open long on {pair}") return True else: + print(f"Skip long on {pair}") return False -def close_short(row): - if (row['close'] > row['ma_band']): + +def close_long(row, position, pair, df): + params = params_coin[pair] + rsi_exit_threshold = params.get('rsi_exit_threshold', 70) + macd_exit = params.get('macd_exit', True) + trailing_stop_percentage = params.get('trailing_stop_percentage', 2.0) + + open_time = position.get('open_time') + df_since_open = df[df.index >= open_time] + highest_price = df_since_open['high'].max() + position['highest_price'] = highest_price + trailing_stop_price = highest_price * (1 - trailing_stop_percentage / 100) + + condition1 = row['close'] < row['ma_band'] + condition2 = row['rsi'] > rsi_exit_threshold + condition3 = macd_exit and (row['macd'] < row['macd_signal']) + condition4 = row['close'] <= trailing_stop_price + + if condition1 or condition2 or condition3 or condition4: + print(f"Close long on {pair}") + + conditions_met = [] + if condition1: + conditions_met.append("Close below MA Band") + if condition2: + conditions_met.append(f"RSI above {rsi_exit_threshold}") + if condition3: + conditions_met.append("MACD crossover down") + if condition4: + conditions_met.append("Trailing stop-loss hit") + print(f"Close long on {pair} due to: {', '.join(conditions_met)}") + return True else: return False + print(f"--- Bollinger Trend on {len(params_coin)} tokens {timeframe} Leverage x{leverage} ---") bitget = PerpBitget( @@ -300,16 +399,46 @@ def close_short(row): password=secret[account_to_select]["password"], ) -# Get data df_list = {} + for pair in params_coin: - temp_data = bitget.get_more_last_historical_async(pair, timeframe, 1000) - if len(temp_data) == 990: - df_list[pair] = temp_data + filename = f"{data_directory}/{pair.replace('/', '_').replace(':', '_')}_{timeframe}.csv" + + if os.path.isfile(filename): + df_existing = pd.read_csv(filename, index_col='timestamp', parse_dates=True) + new_data = bitget.get_more_last_historical_async(pair, timeframe, 10, MINUTEFRAME) + df_new = new_data.copy() + df_new = df_new[~df_new.index.isin(df_existing.index)] + df_pair = pd.concat([df_existing, df_new]) + + + df_pair = df_pair.tail(1000) + + + df_pair.to_csv(filename, index=True) + + df_list[pair] = df_pair else: - print(f"Pair {pair} not loaded, length: {len(temp_data)}") + initial_data = bitget.get_more_last_historical_async(pair, timeframe, 100, MINUTEFRAME) + df_pair = initial_data.copy() + + df_pair.to_csv(filename, index=True) + + df_list[pair] = df_pair + print("Data OHLCV loaded 100%") + +def setExchangeLeverage(pair): + try: + print(f"Setting {margin_mode} x{exchange_leverage} on {pair} pair...") + bitget.set_margin_mode_and_leverage( + pair, margin_mode, exchange_leverage + ) + except Exception as e: + print(e) + + for pair in df_list: df = df_list[pair] params = params_coin[pair] @@ -317,108 +446,120 @@ def close_short(row): df["lower_band"] = bol_band.bollinger_lband() df["higher_band"] = bol_band.bollinger_hband() df["ma_band"] = bol_band.bollinger_mavg() - df['long_ma'] = ta.trend.sma_indicator(close=df['close'], window=params["long_ma_window"]) - + df['long_ema_20'] = ta.trend.ema_indicator(close=df['close'], window=20) df["n1_close"] = df["close"].shift(1) df["n1_lower_band"] = df["lower_band"].shift(1) df["n1_higher_band"] = df["higher_band"].shift(1) - df['iloc'] = range(len(df)) -print("Indicators loaded 100%") + df['ma_20'] = ta.trend.sma_indicator(close=df['close'], window=20) + df['ma_20_slope_pct'] = df['ma_20'].pct_change() * 100 + + df['bb_width'] = df['higher_band'] - df['lower_band'] + df['bb_width_pct'] = (df['bb_width'] / df['ma_band']) * 100 -var = ValueAtRisk(df_list=df_list.copy()) -var.update_cov(current_date=df_list["BTC/USDT:USDT"].index[-1], occurance_data=989) -print("Value At Risk loaded 100%") + df['rsi'] = ta.momentum.rsi(close=df['close'], window=14) + + macd = ta.trend.MACD(close=df['close']) + df['macd'] = macd.macd() + df['macd_signal'] = macd.macd_signal() + df['ema_20'] = ta.trend.sma_indicator(close=df['close'], window=20) + +print("Indicators loaded 100%") usd_balance = float(bitget.get_usdt_equity()) print("USD balance :", round(usd_balance, 2), "$") positions_data = bitget.get_open_position() position_list = [ - {"pair": d["symbol"], "side": d["side"], "size": float(d["contracts"]) * float(d["contractSize"]), "market_price":d["info"]["marketPrice"], "usd_size": float(d["contracts"]) * float(d["contractSize"]) * float(d["info"]["marketPrice"]), "open_price": d["entryPrice"]} - for d in positions_data if d["symbol"] in df_list] + { + "pair": d["symbol"], + "side": d["side"], + "size": float(d["contracts"]) * float(d["contractSize"]), + "market_price": d["info"]["marketPrice"], + "usd_size": float(d["contracts"]) * float(d["contractSize"]) * float(d["info"]["marketPrice"]), + "open_price": d["entryPrice"], + "highest_price": float(d["entryPrice"]), + "open_time": df_list[d["symbol"]].iloc[-1].name + } + for d in positions_data if d["symbol"] in df_list +] positions = {} for pos in position_list: - positions[pos["pair"]] = {"side": pos["side"], "size": pos["size"], "market_price": pos["market_price"], "usd_size": pos["usd_size"], "open_price": pos["open_price"]} + positions[pos["pair"]] = { + "side": pos["side"], + "size": pos["size"], + "market_price": pos["market_price"], + "usd_size": pos["usd_size"], + "open_price": pos["open_price"], + "highest_price": pos["highest_price"], + "open_time": pos["open_time"] + } print(f"{len(positions)} active positions ({list(positions.keys())})") -# Check for closing positions... positions_to_delete = [] + for pair in positions: - row = df_list[pair].iloc[-2] - last_price = float(df_list[pair].iloc[-1]["close"]) + df = df_list[pair] + row = df.iloc[-2] + last_price = float(df.iloc[-1]["close"]) position = positions[pair] - if position["side"] == "long" and close_long(row): - close_long_market_price = last_price - close_long_quantity = float( - bitget.convert_amount_to_precision(pair, position["size"]) - ) - exchange_close_long_quantity = close_long_quantity * close_long_market_price - print( - f"Place Close Long Market Order: {close_long_quantity} {pair[:-5]} at the price of {close_long_market_price}$ ~{round(exchange_close_long_quantity, 2)}$" - ) - if production: - bitget.place_market_order(pair, "sell", close_long_quantity, reduce=True) - positions_to_delete.append(pair) - - elif position["side"] == "short" and close_short(row): - close_short_market_price = last_price - close_short_quantity = float( - bitget.convert_amount_to_precision(pair, position["size"]) - ) - exchange_close_short_quantity = close_short_quantity * close_short_market_price - print( - f"Place Close Short Market Order: {close_short_quantity} {pair[:-5]} at the price of {close_short_market_price}$ ~{round(exchange_close_short_quantity, 2)}$" - ) - if production: - bitget.place_market_order(pair, "buy", close_short_quantity, reduce=True) - positions_to_delete.append(pair) + if position["side"] == "long": + if close_long(row, position, pair, df): + close_long_market_price = last_price + close_long_quantity = float( + bitget.convert_amount_to_precision(pair, position["size"]) + ) + exchange_close_long_quantity = close_long_quantity * close_long_market_price + print( + f"Place Close Long Market Order: {close_long_quantity} {pair[:-5]} at the price of {close_long_market_price}$ ~{round(exchange_close_long_quantity, 2)}$" + ) + + if production: + setExchangeLeverage(pair) + bitget.place_market_order(pair, "sell", close_long_quantity, reduce=True) + positions_to_delete.append(pair) + else: + positions[pair]['highest_price'] = position['highest_price'] for pair in positions_to_delete: del positions[pair] -# Check current VaR risk positions_exposition = {} long_exposition = 0 short_exposition = 0 for pair in df_list: - positions_exposition[pair] = {"long":0, "short":0} + positions_exposition[pair] = {"long": 0, "short": 0} positions_data = bitget.get_open_position() for pos in positions_data: if pos["symbol"] in df_list and pos["side"] == "long": - pct_exposition = (float(pos["contracts"]) * float(pos["contractSize"]) * float(pos["info"]["marketPrice"])) / usd_balance - positions_exposition[pos["symbol"]]["long"] += pct_exposition - long_exposition += pct_exposition + pct_exposition = (float(pos["contracts"]) * float(pos["contractSize"]) * float( + pos["info"]["marketPrice"])) / usd_balance + positions_exposition[pos["symbol"]]["long"] += pct_exposition + long_exposition += pct_exposition elif pos["symbol"] in df_list and pos["side"] == "short": - pct_exposition = (float(pos["contracts"]) * float(pos["contractSize"]) * float(pos["info"]["marketPrice"])) / usd_balance - positions_exposition[pos["symbol"]]["short"] += pct_exposition - short_exposition += pct_exposition - -current_var = var.get_var(positions=positions_exposition) -print(f"Current VaR rsik 1 period: - {round(current_var, 2)}%, LONG exposition {round(long_exposition * 100, 2)}%, SHORT exposition {round(short_exposition * 100, 2)}%") - -for pair in df_list: - if pair not in positions: - try: - row = df_list[pair].iloc[-2] - last_price = float(df_list[pair].iloc[-1]["close"]) - pct_sizing = params_coin[pair]["wallet_exposure"] - if open_long(row) and "long" in type: - long_market_price = float(last_price) - long_quantity_in_usd = usd_balance * pct_sizing * leverage - temp_positions = copy.deepcopy(positions_exposition) - temp_positions[pair]["long"] += (long_quantity_in_usd / usd_balance) - temp_long_exposition = long_exposition + (long_quantity_in_usd / usd_balance) - temp_var = var.get_var(positions=temp_positions) - if temp_var > max_var or temp_long_exposition > max_side_exposition: - print(f"Blocked open LONG on {pair}, because next VaR: - {round(current_var, 2)}%") - else: + pct_exposition = (float(pos["contracts"]) * float(pos["contractSize"]) * float( + pos["info"]["marketPrice"])) / usd_balance + positions_exposition[pos["symbol"]]["short"] += pct_exposition + short_exposition += pct_exposition + +pct_sizing = 1 / MAX_POS + +if len(positions) < MAX_POS: + for pair in df_list: + if pair not in positions: + try: + df = df_list[pair] + row = df.iloc[-2] + last_price = float(df.iloc[-1]["close"]) + if open_long(row, pair) and "long" in type: + long_market_price = last_price + long_quantity_in_usd = usd_balance * pct_sizing * leverage long_quantity = float(bitget.convert_amount_to_precision(pair, float( bitget.convert_amount_to_precision(pair, long_quantity_in_usd / long_market_price) ))) @@ -427,37 +568,25 @@ def close_short(row): f"Place Open Long Market Order: {long_quantity} {pair[:-5]} at the price of {long_market_price}$ ~{round(exchange_long_quantity, 2)}$" ) if production: + setExchangeLeverage(pair) bitget.place_market_order(pair, "buy", long_quantity, reduce=False) positions_exposition[pair]["long"] += (long_quantity_in_usd / usd_balance) long_exposition += (long_quantity_in_usd / usd_balance) - - elif open_short(row) and "short" in type: - short_market_price = float(last_price) - short_quantity_in_usd = usd_balance * pct_sizing * leverage - temp_positions = copy.deepcopy(positions_exposition) - temp_positions[pair]["short"] += (short_quantity_in_usd / usd_balance) - temp_short_exposition = short_exposition + (short_quantity_in_usd / usd_balance) - temp_var = var.get_var(positions=temp_positions) - if temp_var > max_var or temp_short_exposition > max_side_exposition: - print(f"Blocked open SHORT on {pair}, because next VaR: - {round(current_var, 2)}%") - else: - short_quantity = float(bitget.convert_amount_to_precision(pair, float( - bitget.convert_amount_to_precision(pair, short_quantity_in_usd / short_market_price) - ))) - exchange_short_quantity = short_quantity * short_market_price - print( - f"Place Open Short Market Order: {short_quantity} {pair[:-5]} at the price of {short_market_price}$ ~{round(exchange_short_quantity, 2)}$" - ) - if production: - bitget.place_market_order(pair, "sell", short_quantity, reduce=False) - positions_exposition[pair]["short"] += (short_quantity_in_usd / usd_balance) - short_exposition += (short_quantity_in_usd / usd_balance) - - except Exception as e: - print(f"Error on {pair} ({e}), skip {pair}") - + positions[pair] = { + "side": "long", + "size": long_quantity, + "market_price": long_market_price, + "usd_size": exchange_long_quantity, + "open_price": long_market_price, + "highest_price": long_market_price, + "open_time": df.iloc[-1].name + } + except Exception as e: + print(traceback.format_exc()) + print(f"Error on {pair} ({e}), skip {pair}") +else: + print(f"{len(positions)} positions already opened") now = datetime.now() current_time = now.strftime("%d/%m/%Y %H:%M:%S") print("--- End Execution Time :", current_time, "---") - diff --git a/strategies/grid_spot_usd/strategy.py b/strategies/grid_spot_usd/strategy.py index b086881..65f5368 100644 --- a/strategies/grid_spot_usd/strategy.py +++ b/strategies/grid_spot_usd/strategy.py @@ -1,11 +1,9 @@ import sys sys.path.append("./live_tools") -import ccxt import pandas as pd from utilities.spot_ftx import SpotFtx from datetime import datetime -import time import json f = open( diff --git a/strategies/super_reversal/strategy_bitget.py b/strategies/super_reversal/strategy_bitget.py new file mode 100644 index 0000000..6b2830b --- /dev/null +++ b/strategies/super_reversal/strategy_bitget.py @@ -0,0 +1,247 @@ +import sys + +sys.path.append("./live_tools") +import ccxt +import ta +import pandas as pd +from utilities.perp_bitget import PerpBitget +from utilities.custom_indicators import SuperTrend +from datetime import datetime +import time +import json +import requests +f = open( + "./live_tools/secret.json", +) +secret = json.load(f) +f.close() + +timeframe = "1h" +account_to_select = "bitget_exemple" + +params_coin = { + "BTCUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 20, + "long_ema_window": 400 + }, + "ETHUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 5, + "long_ema_window": 400 + }, + "ADAUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 35, + "long_ema_window": 400 + }, + "XRPUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 30, + "long_ema_window": 400 + }, + "BNBUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 30, + "long_ema_window": 400 + }, + "LINKUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 15, + "long_ema_window": 400 + }, + "LTCUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 25, + "long_ema_window": 400 + }, + "SOLUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 40, + "long_ema_window": 400 + }, + "AVAXUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 35, + "long_ema_window": 400 + }, + "DOTUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 40, + "long_ema_window": 400 + }, + "MATICUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 5, + "long_ema_window": 400 + }, + "NEARUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 5, + "long_ema_window": 400 + }, + "EGLDUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 5, + "long_ema_window": 400 + }, + "FTMUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 15, + "long_ema_window": 400 + }, + "ETCUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 5, + "long_ema_window": 400 + }, + "EOSUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 35, + "long_ema_window": 400 + }, + "FILUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 35, + "long_ema_window": 400 + }, + "SANDUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 20, + "long_ema_window": 400 + }, + "AXSUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 10, + "long_ema_window": 400 + }, + "LRCUSDT": { + "wallet_exposure": 0.05, + "st_short_atr_window": 15, + "st_short_atr_multiplier": 5, + "short_ema_window": 45, + "long_ema_window": 400 + } +} + +bitget = PerpBitget( + apiKey=secret[account_to_select]["apiKey"], + secret=secret[account_to_select]["secret"], + password=secret[account_to_select]["password"], +) + +now = datetime.now() +current_time = now.strftime("%d/%m/%Y %H:%M:%S") +print("Execution Time :", current_time) + +open_order = [] + +df_list = {} +pair_list = params_coin.keys() +symbol_list = [pair.replace("USDT", "") for pair in pair_list] +print(symbol_list) +for pair in params_coin: + params = params_coin[pair] + df = bitget.get_last_historical(pair, timeframe, 1000) + + # -- Populate indicators -- + super_trend = SuperTrend( + df["high"], + df["low"], + df["close"], + params["st_short_atr_window"], + params["st_short_atr_multiplier"], + ) + + df["super_trend_direction"] = super_trend.super_trend_direction() + df["ema_short"] = ta.trend.ema_indicator( + close=df["close"], window=params["short_ema_window"] + ) + df["ema_long"] = ta.trend.ema_indicator( + close=df["close"], window=params["long_ema_window"] + ) + + df_list[pair] = df + +all_balance = bitget.get_all_balance() +symbol_balance = {} +usdt_balance = all_balance["USDT"] +usdt_all_balance = usdt_balance +for k in all_balance: + if k in symbol_list: + if all_balance[k] > bitget.get_min_order_amount(k+"USDT"): + symbol_balance[k] = bitget.convert_amount_to_precision(k+"USDT", all_balance[k]) + usdt_all_balance = usdt_all_balance + symbol_balance[k] * df_list[k+"USDT"].iloc[-1]["close"] + else: + symbol_balance[k] = 0 +print("symbol_balance", symbol_balance) +print("usdt_all_balance", usdt_all_balance) + +for symbol in params_coin: + try: + bitget.cancel_all_orders(symbol) + except: + pass + +for symbol in symbol_balance: + pair = symbol + "USDT" + row = df_list[pair].iloc[-2] + if symbol_balance[symbol] == 0: + if row["super_trend_direction"] == True and row["ema_short"] > row["ema_long"]: + buy_limit_price = bitget.convert_price_to_precision(pair, row["ema_short"]) + buy_quantity_in_usd = usdt_all_balance * params_coin[pair]["wallet_exposure"] + buy_quantity = bitget.convert_amount_to_precision(pair, (buy_quantity_in_usd / buy_limit_price)) + print(f"Buy limit on {pair} of {buy_quantity} at the price of {buy_limit_price}$") + try: + bitget.place_limit_order(pair, "Buy", buy_quantity, buy_limit_price) + except Exception as e: + print(f" Error: {e}") + elif symbol_balance[symbol] > 0: + if row["super_trend_direction"] == False or row["ema_short"] < row["ema_long"]: + sell_limit_price = bitget.convert_price_to_precision(pair, row["ema_short"]) + sell_quantity = bitget.convert_amount_to_precision(pair, symbol_balance[symbol]) + print(f"Sell limit on {pair} of {sell_quantity} at the price of {sell_limit_price}$") + try: + bitget.place_limit_order(pair, "Sell", sell_quantity, sell_limit_price) + except Exception as e: + print(f" Error: {e}") + + diff --git a/utilities/perp_bitget.py b/utilities/perp_bitget.py index e077b7a..ff21a49 100644 --- a/utilities/perp_bitget.py +++ b/utilities/perp_bitget.py @@ -1,3 +1,5 @@ +import logging + import ccxt import pandas as pd import time @@ -42,17 +44,17 @@ def get_last_historical(self, symbol, timeframe, limit): del result['timestamp'] return result - def get_more_last_historical_async(self, symbol, timeframe, limit): + def get_more_last_historical_async(self, symbol, timeframe, limit, minuteFrame): max_threads = 4 - pool_size = round(limit/100) # your "parallelness" + # pool_size = round(limit/100) # your "parallelness" # define worker function before a Pool is instantiated full_result = [] def worker(i): - + try: return self._session.fetch_ohlcv( - symbol, timeframe, round(time.time() * 1000) - (i*1000*60*60), limit=100) + symbol, timeframe, round(time.time() * 1000) - (i*1000*60*minuteFrame), limit=100) except Exception as err: raise Exception("Error on last historical on " + symbol + ": " + str(err)) @@ -88,10 +90,10 @@ def convert_price_to_precision(self, symbol, price): def place_limit_order(self, symbol, side, amount, price, reduce=False): try: return self._session.createOrder( - symbol, - 'limit', - side, - self.convert_amount_to_precision(symbol, amount), + symbol, + 'limit', + side, + self.convert_amount_to_precision(symbol, amount), self.convert_price_to_precision(symbol, price), params={"reduceOnly": reduce} ) @@ -100,13 +102,13 @@ def place_limit_order(self, symbol, side, amount, price, reduce=False): @authentication_required def place_limit_stop_loss(self, symbol, side, amount, trigger_price, price, reduce=False): - + try: return self._session.createOrder( - symbol, - 'limit', - side, - self.convert_amount_to_precision(symbol, amount), + symbol, + 'limit', + side, + self.convert_amount_to_precision(symbol, amount), self.convert_price_to_precision(symbol, price), params = { 'stopPrice': self.convert_price_to_precision(symbol, trigger_price), # your stop price @@ -121,9 +123,9 @@ def place_limit_stop_loss(self, symbol, side, amount, trigger_price, price, redu def place_market_order(self, symbol, side, amount, reduce=False): try: return self._session.createOrder( - symbol, - 'market', - side, + symbol, + 'market', + side, self.convert_amount_to_precision(symbol, amount), None, params={"reduceOnly": reduce} @@ -133,13 +135,13 @@ def place_market_order(self, symbol, side, amount, reduce=False): @authentication_required def place_market_stop_loss(self, symbol, side, amount, trigger_price, reduce=False): - + try: return self._session.createOrder( - symbol, - 'market', - side, - self.convert_amount_to_precision(symbol, amount), + symbol, + 'market', + side, + self.convert_amount_to_precision(symbol, amount), self.convert_price_to_precision(symbol, trigger_price), params = { 'stopPrice': self.convert_price_to_precision(symbol, trigger_price), # your stop price @@ -220,7 +222,7 @@ def cancel_order_by_id(self, id, symbol, conditionnal=False): return self._session.cancel_order(id, symbol) except BaseException as err: raise Exception("An error occured in cancel_order_by_id", err) - + @authentication_required def cancel_all_open_order(self): try: @@ -231,7 +233,7 @@ def cancel_all_open_order(self): ) except BaseException as err: raise Exception("An error occured in cancel_all_open_order", err) - + @authentication_required def cancel_order_ids(self, ids=[], symbol=None): try: @@ -244,3 +246,46 @@ def cancel_order_ids(self, ids=[], symbol=None): ) except BaseException as err: raise Exception("An error occured in cancel_order_ids", err) + + @authentication_required + def set_margin_mode_and_leverage(self, pair, margin_mode, leverage): + if margin_mode not in ["crossed", "fixed"]: + raise Exception("Margin mode must be either 'crossed' or 'fixed'") + try: + self._session.set_margin_mode( + margin_mode, + pair, + params={"productType": "USDT-FUTURES", "marginCoin": "USDT"}, + ) + except Exception as Argument: + logging.exception(f"Error set_margin_mode on {pair}") + try: + if margin_mode == "fixed": + + self._session.set_leverage( + leverage, + pair, + params={ + "productType": "USDT-FUTURES", + "marginCoin": "USDT", + "holdSide": "long", + }, + ) + self._session.set_leverage( + leverage, + pair, + params={ + "productType": "USDT-FUTURES", + "marginCoin": "USDT", + "holdSide": "short", + }, + ) + + else: + self._session.set_leverage( + leverage, + pair, + params={"productType": "USDT-FUTURES", "marginCoin": "USDT"}, + ) + except Exception as Argument: + logging.exception(f"Error set_margin_mode_and_leverage on {pair}")