From 0aa7c8ec1a64bf1c9efac19d6be42c4aab405868 Mon Sep 17 00:00:00 2001 From: Grousseau Date: Sun, 25 Feb 2024 18:00:25 +0100 Subject: [PATCH 1/3] Set leverage and margin_mode to the exchange to avoid issue with default values Removed YFI/USDT not listed !! --- strategies/bol_trend/strategy_multi_bitget.py | 99 +++++++++++++------ utilities/perp_bitget.py | 85 ++++++++++++---- 2 files changed, 135 insertions(+), 49 deletions(-) diff --git a/strategies/bol_trend/strategy_multi_bitget.py b/strategies/bol_trend/strategy_multi_bitget.py index fffed40..1c27e4d 100644 --- a/strategies/bol_trend/strategy_multi_bitget.py +++ b/strategies/bol_trend/strategy_multi_bitget.py @@ -1,4 +1,6 @@ +import logging import sys + sys.path.append("./live_tools") import ccxt import ta @@ -29,6 +31,9 @@ max_var = 1 max_side_exposition = 1 +margin_mode = "fixed" # fixed or crossed +exchange_leverage = 1 + params_coin = { "BTC/USDT:USDT": { "wallet_exposure": 0.05, @@ -222,12 +227,6 @@ "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, @@ -260,38 +259,43 @@ }, } + def open_long(row): if ( - row['n1_close'] < row['n1_higher_band'] - and (row['close'] > row['higher_band']) + 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_short(row): if ( - row['n1_close'] > row['n1_lower_band'] - and (row['close'] < row['lower_band']) - and (row['close'] < row['long_ma']) + row['n1_close'] > row['n1_lower_band'] + and (row['close'] < row['lower_band']) + and (row['close'] < row['long_ma']) ): return True else: return False + def close_short(row): if (row['close'] > row['ma_band']): return True else: return False + print(f"--- Bollinger Trend on {len(params_coin)} tokens {timeframe} Leverage x{leverage} ---") bitget = PerpBitget( @@ -304,12 +308,26 @@ def close_short(row): 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 - else: - print(f"Pair {pair} not loaded, length: {len(temp_data)}") + try: + if len(temp_data) == 990: + df_list[pair] = temp_data + else: + print(f"Pair {pair} not loaded, length: {len(temp_data)}") + except Exception as Argument: + logging.exception(f"Error while getting history of pair {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] @@ -319,7 +337,7 @@ def close_short(row): df["ma_band"] = bol_band.bollinger_mavg() df['long_ma'] = ta.trend.sma_indicator(close=df['close'], window=params["long_ma_window"]) - + 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) @@ -337,17 +355,25 @@ def close_short(row): 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"]} + {"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] 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"]} 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"]) @@ -362,7 +388,10 @@ def close_short(row): 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) @@ -375,7 +404,15 @@ def close_short(row): 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)}$" ) + 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) if production: + setExchangeLeverage(pair) bitget.place_market_order(pair, "buy", close_short_quantity, reduce=True) positions_to_delete.append(pair) @@ -387,21 +424,24 @@ def close_short(row): 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 + 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)}%") +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: @@ -427,6 +467,7 @@ 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) @@ -449,13 +490,13 @@ def close_short(row): f"Place Open Short Market Order: {short_quantity} {pair[:-5]} at the price of {short_market_price}$ ~{round(exchange_short_quantity, 2)}$" ) if production: + setExchangeLeverage(pair) 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}") + except Exception as e: + print(f"Error on {pair} ({e}), skip {pair}") now = datetime.now() current_time = now.strftime("%d/%m/%Y %H:%M:%S") diff --git a/utilities/perp_bitget.py b/utilities/perp_bitget.py index e077b7a..379ed84 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 @@ -49,7 +51,7 @@ def get_more_last_historical_async(self, symbol, timeframe, limit): # 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) @@ -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}") From 30e8c157b5b575e726497e6e2e98330b4965ffa3 Mon Sep 17 00:00:00 2001 From: Grousseau Date: Sun, 17 Nov 2024 14:47:55 +0100 Subject: [PATCH 2/3] update --- strategies/bol_trend/strategy_bitget.py | 6 +- strategies/bol_trend/strategy_multi_bitget.py | 834 ++++++++++-------- strategies/grid_spot_usd/strategy.py | 2 - strategies/super_reversal/strategy_bitget.py | 247 ++++++ utilities/perp_bitget.py | 6 +- 5 files changed, 730 insertions(+), 365 deletions(-) create mode 100644 strategies/super_reversal/strategy_bitget.py 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 1c27e4d..8549752 100644 --- a/strategies/bol_trend/strategy_multi_bitget.py +++ b/strategies/bol_trend/strategy_multi_bitget.py @@ -1,296 +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( +# "../../secret.json", +# ) f = open( "./live_tools/secret.json", ) + +# Ajustez le chemin si nécessaire 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"] # Spécifiez "short" si vous voulez également prendre des positions courtes +leverage = 5 +max_var = 5 +max_side_exposition = 1.55 margin_mode = "fixed" # fixed or crossed -exchange_leverage = 1 +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 - }, - "APE/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, # Tolérance pour la pente de la MA 20 + "bb_width_tolerance": 5.0, # Tolérance pour la largeur des BB en % + "rsi_threshold": 50, # Seuil RSI pour l'entrée en position + "rsi_exit_threshold": 70, # Seuil RSI pour la sortie de position + "macd_exit": True, # Activation de la sortie basée sur le MACD + "trailing_stop_percentage": 2.0 # Pourcentage pour le trailing stop-loss + }, + "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 }, - "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 - }, - "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 - }, - "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 + "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 }, - "ETC/USDT:USDT": { - "wallet_exposure": 0.05, - "bb_window": 100, - "bb_std": 2.25, - "long_ma_window": 500 - }, - "JASMY/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 }, - "ROSE/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 }, + "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 + }, + # Ajoutez d'autres actifs avec les mêmes paramètres } -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 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 close_long(row): - if (row['close'] < row['ma_band']): - return True - else: - return False -def open_short(row): if ( - row['n1_close'] > row['n1_lower_band'] - and (row['close'] < row['lower_band']) - and (row['close'] < row['long_ma']) + row['n1_close'] < row['n1_higher_band'] + and row['close'] > row['long_ma'] + and row['close'] > row['higher_band'] + and row['close'] > row['long_ma'] + and abs(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) + + # Mise à jour du plus haut atteint pour le trailing stop-loss en se basant sur l'historique + 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) + + + # Conditions de sortie + 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 @@ -304,17 +404,52 @@ def close_short(row): password=secret[account_to_select]["password"], ) -# Get data +# Récupération des données df_list = {} + for pair in params_coin: - temp_data = bitget.get_more_last_historical_async(pair, timeframe, 1000) - try: - if len(temp_data) == 990: - df_list[pair] = temp_data - else: - print(f"Pair {pair} not loaded, length: {len(temp_data)}") - except Exception as Argument: - logging.exception(f"Error while getting history of pair {pair}") + filename = f"{data_directory}/{pair.replace('/', '_').replace(':', '_')}_{timeframe}.csv" + + # Vérifier si le fichier de données existe + if os.path.isfile(filename): + # Charger les données existantes depuis le fichier + df_existing = pd.read_csv(filename, index_col='timestamp', parse_dates=True) + + # Récupérer le dernier timestamp dans les données existantes + # last_timestamp = df_existing.index[-1] + + # Calculer le nombre de nouvelles bougies à récupérer + # Supposons que le timeframe est en minutes, vous pouvez ajuster en fonction + # time_diff = datetime.utcnow() - last_timestamp + # minutes_diff = int(time_diff.total_seconds() / 60) + # limit = max(1, minutes_diff) + + # Récupérer les nouvelles données manquantes + new_data = bitget.get_more_last_historical_async(pair, timeframe, 10, MINUTEFRAME) + + # Fusionner les nouvelles données avec les données existantes + df_new = new_data.copy() + df_new = df_new[~df_new.index.isin(df_existing.index)] # Éviter les doublons + df_pair = pd.concat([df_existing, df_new]) + + # Limiter le DataFrame aux 1000 dernières lignes + df_pair = df_pair.tail(1000) + + # Sauvegarder le DataFrame mis à jour dans le fichier + df_pair.to_csv(filename, index=True) + + # Ajouter le DataFrame au dictionnaire + df_list[pair] = df_pair + else: + # Le fichier n'existe pas, récupérer les données initiales (par exemple, 1000 bougies) + initial_data = bitget.get_more_last_historical_async(pair, timeframe, 100, MINUTEFRAME) + df_pair = initial_data.copy() + + # Sauvegarder le DataFrame dans le fichier + df_pair.to_csv(filename, index=True) + + # Ajouter le DataFrame au dictionnaire + df_list[pair] = df_pair print("Data OHLCV loaded 100%") @@ -328,6 +463,7 @@ def setExchangeLeverage(pair): except Exception as e: print(e) + for pair in df_list: df = df_list[pair] params = params_coin[pair] @@ -335,91 +471,94 @@ def setExchangeLeverage(pair): 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["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%") + # Calcul de la MA 20 et de sa pente en pourcentage + df['ma_20'] = ta.trend.sma_indicator(close=df['close'], window=20) + df['ma_20_slope_pct'] = df['ma_20'].pct_change() * 100 + + # Calcul de la largeur des BB en pourcentage + df['bb_width'] = df['higher_band'] - df['lower_band'] + df['bb_width_pct'] = (df['bb_width'] / df['ma_band']) * 100 + + # 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() -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%") +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"]), # Initialisation du highest_price + "open_time": df_list[d["symbol"]].iloc[-1].name # En supposant que la position vient d'être ouverte + } + 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... +# Vérification des positions à fermer 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: - setExchangeLeverage(pair) - 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)}$" - ) - try: - print(f"Setting {margin_mode} x{exchange_leverage} on {pair} pair...") - bitget.set_margin_mode_and_leverage( - pair, margin_mode, exchange_leverage + 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"]) ) - except Exception as e: - print(e) - if production: - setExchangeLeverage(pair) - bitget.place_market_order(pair, "buy", close_short_quantity, reduce=True) - positions_to_delete.append(pair) + 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: + # Mise à jour du plus haut prix atteint + positions[pair]['highest_price'] = position['highest_price'] for pair in positions_to_delete: del positions[pair] -# Check current VaR risk +# Vérification de l'exposition actuelle positions_exposition = {} long_exposition = 0 short_exposition = 0 @@ -439,26 +578,18 @@ def setExchangeLeverage(pair): 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_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) ))) @@ -471,34 +602,23 @@ def 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: - setExchangeLeverage(pair) - 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}") + # Initialiser le plus haut prix atteint et l'horodatage d'ouverture + 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 # Horodatage d'ouverture + } + # Ajoutez la gestion des positions courtes si nécessaire + 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 379ed84..ff21a49 100644 --- a/utilities/perp_bitget.py +++ b/utilities/perp_bitget.py @@ -44,9 +44,9 @@ 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 = [] @@ -54,7 +54,7 @@ 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)) From 475d60ec92f34ab0c47ae0d843839bed3cf2ab44 Mon Sep 17 00:00:00 2001 From: Grousseau Date: Sun, 17 Nov 2024 17:39:26 +0100 Subject: [PATCH 3/3] update --- strategies/bol_trend/Backtester.py | 355 ++++++++++++++++++ strategies/bol_trend/backtest.py | 37 ++ strategies/bol_trend/strategy_multi_bitget.py | 78 ++-- 3 files changed, 415 insertions(+), 55 deletions(-) create mode 100644 strategies/bol_trend/Backtester.py create mode 100644 strategies/bol_trend/backtest.py 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_multi_bitget.py b/strategies/bol_trend/strategy_multi_bitget.py index 8549752..25a78c1 100644 --- a/strategies/bol_trend/strategy_multi_bitget.py +++ b/strategies/bol_trend/strategy_multi_bitget.py @@ -21,21 +21,20 @@ current_time = now.strftime("%d/%m/%Y %H:%M:%S") print("--- Start Execution Time :", current_time, "---") # -# f = open( -# "../../secret.json", -# ) f = open( - "./live_tools/secret.json", + "../../secret.json", ) +# f = open( +# "./live_tools/secret.json", +# ) -# Ajustez le chemin si nécessaire secret = json.load(f) f.close() account_to_select = "bitget_exemple" production = True timeframe = "1m" -type = ["long"] # Spécifiez "short" si vous voulez également prendre des positions courtes +type = ["long"] leverage = 5 max_var = 5 max_side_exposition = 1.55 @@ -50,12 +49,12 @@ "bb_window": 50, "bb_std": 2, "long_ma_window": 98, - "ma_slope_tolerance": 0.05, # Tolérance pour la pente de la MA 20 - "bb_width_tolerance": 5.0, # Tolérance pour la largeur des BB en % - "rsi_threshold": 50, # Seuil RSI pour l'entrée en position - "rsi_exit_threshold": 70, # Seuil RSI pour la sortie de position - "macd_exit": True, # Activation de la sortie basée sur le MACD - "trailing_stop_percentage": 2.0 # Pourcentage pour le trailing stop-loss + "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, @@ -322,7 +321,6 @@ "macd_exit": True, "trailing_stop_percentage": 2.0 }, - # Ajoutez d'autres actifs avec les mêmes paramètres } @@ -334,12 +332,12 @@ def open_long(row, pair): - if ( - row['n1_close'] < row['n1_higher_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 abs(row['ma_20_slope_pct']) < ma_slope_tolerance + and row['ma_20_slope_pct'] > -ma_slope_tolerance and row['bb_width_pct'] < bb_width_tolerance and row['rsi'] > rsi_threshold ): @@ -363,15 +361,12 @@ def close_long(row, position, pair, df): 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 en se basant sur l'historique 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) - - # Conditions de sortie condition1 = row['close'] < row['ma_band'] condition2 = row['rsi'] > rsi_exit_threshold condition3 = macd_exit and (row['macd'] < row['macd_signal']) @@ -404,51 +399,31 @@ def close_long(row, position, pair, df): password=secret[account_to_select]["password"], ) -# Récupération des données df_list = {} for pair in params_coin: filename = f"{data_directory}/{pair.replace('/', '_').replace(':', '_')}_{timeframe}.csv" - # Vérifier si le fichier de données existe if os.path.isfile(filename): - # Charger les données existantes depuis le fichier df_existing = pd.read_csv(filename, index_col='timestamp', parse_dates=True) - - # Récupérer le dernier timestamp dans les données existantes - # last_timestamp = df_existing.index[-1] - - # Calculer le nombre de nouvelles bougies à récupérer - # Supposons que le timeframe est en minutes, vous pouvez ajuster en fonction - # time_diff = datetime.utcnow() - last_timestamp - # minutes_diff = int(time_diff.total_seconds() / 60) - # limit = max(1, minutes_diff) - - # Récupérer les nouvelles données manquantes new_data = bitget.get_more_last_historical_async(pair, timeframe, 10, MINUTEFRAME) - - # Fusionner les nouvelles données avec les données existantes df_new = new_data.copy() - df_new = df_new[~df_new.index.isin(df_existing.index)] # Éviter les doublons + df_new = df_new[~df_new.index.isin(df_existing.index)] df_pair = pd.concat([df_existing, df_new]) - # Limiter le DataFrame aux 1000 dernières lignes + df_pair = df_pair.tail(1000) - # Sauvegarder le DataFrame mis à jour dans le fichier + df_pair.to_csv(filename, index=True) - # Ajouter le DataFrame au dictionnaire df_list[pair] = df_pair else: - # Le fichier n'existe pas, récupérer les données initiales (par exemple, 1000 bougies) initial_data = bitget.get_more_last_historical_async(pair, timeframe, 100, MINUTEFRAME) df_pair = initial_data.copy() - # Sauvegarder le DataFrame dans le fichier df_pair.to_csv(filename, index=True) - # Ajouter le DataFrame au dictionnaire df_list[pair] = df_pair print("Data OHLCV loaded 100%") @@ -472,26 +447,24 @@ def setExchangeLeverage(pair): 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)) - # Calcul de la MA 20 et de sa pente en pourcentage df['ma_20'] = ta.trend.sma_indicator(close=df['close'], window=20) df['ma_20_slope_pct'] = df['ma_20'].pct_change() * 100 - # Calcul de la largeur des BB en pourcentage df['bb_width'] = df['higher_band'] - df['lower_band'] df['bb_width_pct'] = (df['bb_width'] / df['ma_band']) * 100 - # 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() + df['ema_20'] = ta.trend.sma_indicator(close=df['close'], window=20) print("Indicators loaded 100%") @@ -507,8 +480,8 @@ def setExchangeLeverage(pair): "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"]), # Initialisation du highest_price - "open_time": df_list[d["symbol"]].iloc[-1].name # En supposant que la position vient d'être ouverte + "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 ] @@ -527,7 +500,6 @@ def setExchangeLeverage(pair): print(f"{len(positions)} active positions ({list(positions.keys())})") -# Vérification des positions à fermer positions_to_delete = [] for pair in positions: @@ -552,13 +524,11 @@ def setExchangeLeverage(pair): bitget.place_market_order(pair, "sell", close_long_quantity, reduce=True) positions_to_delete.append(pair) else: - # Mise à jour du plus haut prix atteint positions[pair]['highest_price'] = position['highest_price'] for pair in positions_to_delete: del positions[pair] -# Vérification de l'exposition actuelle positions_exposition = {} long_exposition = 0 short_exposition = 0 @@ -602,7 +572,6 @@ def 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) - # Initialiser le plus haut prix atteint et l'horodatage d'ouverture positions[pair] = { "side": "long", "size": long_quantity, @@ -610,9 +579,8 @@ def setExchangeLeverage(pair): "usd_size": exchange_long_quantity, "open_price": long_market_price, "highest_price": long_market_price, - "open_time": df.iloc[-1].name # Horodatage d'ouverture + "open_time": df.iloc[-1].name } - # Ajoutez la gestion des positions courtes si nécessaire except Exception as e: print(traceback.format_exc()) print(f"Error on {pair} ({e}), skip {pair}")