From 4228c335b113178f2c79f031625c4fbbd509061a Mon Sep 17 00:00:00 2001 From: morteza-koohgard Date: Wed, 1 May 2024 13:25:06 +0330 Subject: [PATCH 1/5] add apex testnet import candles --- jesse/enums/__init__.py | 1 + jesse/info.py | 24 +++++- .../drivers/Apex/ApexProMain.py | 76 +++++++++++++++++++ .../drivers/Apex/ApexProPerpetualTestnet.py | 10 +++ .../drivers/Apex/__init__.py | 0 .../drivers/Apex/apex_pro_utils.py | 65 ++++++++++++++++ .../import_candles_mode/drivers/__init__.py | 2 + 7 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py create mode 100644 jesse/modes/import_candles_mode/drivers/Apex/ApexProPerpetualTestnet.py create mode 100644 jesse/modes/import_candles_mode/drivers/Apex/__init__.py create mode 100644 jesse/modes/import_candles_mode/drivers/Apex/apex_pro_utils.py diff --git a/jesse/enums/__init__.py b/jesse/enums/__init__.py index de32becf9..0fa262f7b 100644 --- a/jesse/enums/__init__.py +++ b/jesse/enums/__init__.py @@ -76,6 +76,7 @@ class exchanges: BITGET_USDT_PERPETUAL_TESTNET = 'Bitget USDT Perpetual Testnet' DYDX_PERPETUAL = "Dydx Perpetual" DYDX_PERPETUAL_TESTNET = "Dydx Perpetual Testnet" + APEX_PRO_PERPETUAL_TESTNET = 'Apex Pro Perpetual Testnet' class migration_actions: diff --git a/jesse/info.py b/jesse/info.py index fc3057f49..173110da1 100644 --- a/jesse/info.py +++ b/jesse/info.py @@ -1,9 +1,9 @@ from jesse.enums import exchanges as exchanges_enums, timeframes -JESSE_API_URL = 'https://api1.jesse.trade/api' -# JESSE_API_URL = 'http://localhost:8040/api' -JESSE_WEBSITE_URL = 'https://jesse.trade' -# JESSE_WEBSITE_URL = 'http://localhost:8040' +# JESSE_API_URL = 'https://api1.jesse.trade/api' +JESSE_API_URL = 'http://localhost:8040/api' +# JESSE_WEBSITE_URL = 'https://jesse.trade' +JESSE_WEBSITE_URL = 'http://localhost:8040' BYBIT_TIMEFRAMES = [timeframes.MINUTE_1, timeframes.MINUTE_3, timeframes.MINUTE_5, timeframes.MINUTE_15, timeframes.MINUTE_30, timeframes.HOUR_1, timeframes.HOUR_2, timeframes.HOUR_4, timeframes.HOUR_6, timeframes.HOUR_12, timeframes.DAY_1] FTX_TIMEFRAMES = [timeframes.MINUTE_1, timeframes.MINUTE_3, timeframes.MINUTE_5, timeframes.MINUTE_15, timeframes.MINUTE_30, timeframes.HOUR_1, timeframes.HOUR_2, timeframes.HOUR_4, timeframes.HOUR_6, timeframes.HOUR_12, timeframes.DAY_1] @@ -11,6 +11,7 @@ COINBASE_TIMEFRAMES = [timeframes.MINUTE_1, timeframes.MINUTE_5, timeframes.MINUTE_15, timeframes.HOUR_1, timeframes.HOUR_6, timeframes.DAY_1] BITGET_TIMEFRAMES = [timeframes.MINUTE_1, timeframes.MINUTE_5, timeframes.MINUTE_15, timeframes.MINUTE_30, timeframes.HOUR_1, timeframes.HOUR_4, timeframes.HOUR_12, timeframes.DAY_1] DYDX_TIMEFRAMES = [timeframes.MINUTE_1, timeframes.MINUTE_5, timeframes.MINUTE_15, timeframes.MINUTE_30, timeframes.HOUR_1, timeframes.HOUR_4, timeframes.DAY_1] +APEX_PRO_TIMEFRAMES = [timeframes.MINUTE_1, timeframes.MINUTE_5, timeframes.MINUTE_15, timeframes.MINUTE_30, timeframes.HOUR_1, timeframes.HOUR_4, timeframes.DAY_1] exchange_info = { # BYBIT_USDT_PERPETUAL @@ -297,7 +298,22 @@ 'live_trading': True, }, 'required_live_plan': 'premium' + }, + + exchanges_enums.APEX_PRO_PERPETUAL_TESTNET: { + 'name': exchanges_enums.APEX_PRO_PERPETUAL_TESTNET, + 'url': 'https://testnet.pro.apex.exchange/trade/BTCUSD', + 'fee': 0.0005, + 'type': 'futures', + 'supported_leverage_modes': ['cross'], + 'supported_timeframes': APEX_PRO_TIMEFRAMES, + 'modes': { + 'backtesting': False, + 'live_trading': True, + }, + 'required_live_plan': 'premium' } + } # list of supported exchanges for backtesting diff --git a/jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py b/jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py new file mode 100644 index 000000000..1958028db --- /dev/null +++ b/jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py @@ -0,0 +1,76 @@ +import requests +import jesse.helpers as jh +from jesse.modes.import_candles_mode.drivers.interface import CandleExchange +from typing import Union +from jesse import exceptions +from .apex_pro_utils import timeframe_to_interval + + +class ApexProMain(CandleExchange): + def __init__(self, name: str, rest_endpoint: str) -> None: + from jesse.modes.import_candles_mode.drivers.Binance.BinanceSpot import BinanceSpot + + super().__init__(name=name, count=200, rate_limit_per_second=10, backup_exchange_class=BinanceSpot) + self.name = name + self.endpoint = rest_endpoint + + def get_starting_time(self, symbol: str) -> int: + dashless_symbol = jh.dashless_symbol(symbol) + payload = { + 'symbol': dashless_symbol, + 'interval': 'W', + 'limit': 200, + 'start': 1514811660 + } + + response = requests.get(self.endpoint + '/v2/klines', params=payload) + self.validate_response(response) + + if 'data' not in response.json(): + raise exceptions.ExchangeInMaintenance(response.json()['msg']) + elif response.json()['data'] == {}: + raise exceptions.InvalidSymbol('Exchange does not support the entered symbol. Please enter a valid symbol.') + + data = response.json()['data'][dashless_symbol] + # Reverse the data list + data = data[::-1] + + return int(data[1]['t']) + + def fetch(self, symbol: str, start_timestamp: int, timeframe: str = '1m') -> Union[list, None]: + dashless_symbol = jh.dashless_symbol(symbol) + interval = timeframe_to_interval(timeframe) + + payload = { + 'symbol': dashless_symbol, + 'interval': interval, + 'start': int(start_timestamp / 1000), + 'limit': self.count + } + + response = requests.get(self.endpoint + '/v2/klines', params=payload) + # check data exist in response.json + + if 'data' not in response.json(): + raise exceptions.ExchangeInMaintenance(response.json()['msg']) + elif response.json()['data'] == {}: + raise exceptions.InvalidSymbol('Exchange does not support the entered symbol. Please enter a valid symbol.') + + data = response.json()['data'][dashless_symbol] + # Reverse the data list + data = data[::-1] + + return [ + { + 'id': jh.generate_unique_id(), + 'exchange': self.name, + 'symbol': symbol, + 'timeframe': timeframe, + 'timestamp': int(d['t']), + 'open': float(d['o']), + 'close': float(d['c']), + 'high': float(d['h']), + 'low': float(d['l']), + 'volume': float(d['v']) + } for d in data + ] \ No newline at end of file diff --git a/jesse/modes/import_candles_mode/drivers/Apex/ApexProPerpetualTestnet.py b/jesse/modes/import_candles_mode/drivers/Apex/ApexProPerpetualTestnet.py new file mode 100644 index 000000000..d25c3bae7 --- /dev/null +++ b/jesse/modes/import_candles_mode/drivers/Apex/ApexProPerpetualTestnet.py @@ -0,0 +1,10 @@ +from .ApexProMain import ApexProMain +from jesse.enums import exchanges + + +class ApexProPerpetualTestnet(ApexProMain): + def __init__(self) -> None: + super().__init__( + name=exchanges.APEX_PRO_PERPETUAL_TESTNET, + rest_endpoint='https://testnet.pro.apex.exchange/api' + ) diff --git a/jesse/modes/import_candles_mode/drivers/Apex/__init__.py b/jesse/modes/import_candles_mode/drivers/Apex/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/jesse/modes/import_candles_mode/drivers/Apex/apex_pro_utils.py b/jesse/modes/import_candles_mode/drivers/Apex/apex_pro_utils.py new file mode 100644 index 000000000..4d7e02425 --- /dev/null +++ b/jesse/modes/import_candles_mode/drivers/Apex/apex_pro_utils.py @@ -0,0 +1,65 @@ +from jesse.enums import timeframes + + +def timeframe_to_interval(timeframe: str) -> str: + """ + Convert a timeframe string to an interval in seconds. + """ + if timeframe == timeframes.MINUTE_1: + return '1' + elif timeframe == timeframes.MINUTE_3: + return '3' + elif timeframe == timeframes.MINUTE_5: + return '5' + elif timeframe == timeframes.MINUTE_15: + return '15' + elif timeframe == timeframes.MINUTE_30: + return '30' + elif timeframe == timeframes.HOUR_1: + return '60' + elif timeframe == timeframes.HOUR_2: + return '120' + elif timeframe == timeframes.HOUR_4: + return '240' + elif timeframe == timeframes.HOUR_6: + return '360' + elif timeframe == timeframes.HOUR_12: + return '720' + elif timeframe == timeframes.DAY_1: + return 'D' + elif timeframe == timeframes.WEEK_1: + return 'W' + else: + raise ValueError('Invalid timeframe: {}'.format(timeframe)) + + +def interval_to_timeframe(interval: str) -> str: + """ + Convert an interval in seconds to a timeframe string. + """ + if interval == '1': + return timeframes.MINUTE_1 + elif interval == '3': + return timeframes.MINUTE_3 + elif interval == '5': + return timeframes.MINUTE_5 + elif interval == '15': + return timeframes.MINUTE_15 + elif interval == '30': + return timeframes.MINUTE_30 + elif interval == '60': + return timeframes.HOUR_1 + elif interval == '120': + return timeframes.HOUR_2 + elif interval == '240': + return timeframes.HOUR_4 + elif interval == '360': + return timeframes.HOUR_6 + elif interval == '720': + return timeframes.HOUR_12 + elif interval == 'D': + return timeframes.DAY_1 + elif interval == 'W': + return timeframes.WEEK_1 + else: + raise ValueError('Invalid interval: {}'.format(interval)) diff --git a/jesse/modes/import_candles_mode/drivers/__init__.py b/jesse/modes/import_candles_mode/drivers/__init__.py index 1ca396b06..6891fb5bf 100644 --- a/jesse/modes/import_candles_mode/drivers/__init__.py +++ b/jesse/modes/import_candles_mode/drivers/__init__.py @@ -19,6 +19,7 @@ from jesse.modes.import_candles_mode.drivers.DyDx.DydxPerpetualTestnet import DydxPerpetualTestnet from jesse.modes.import_candles_mode.drivers.Bybit.BybitSpotTestnet import BybitSpotTestnet from jesse.modes.import_candles_mode.drivers.Bybit.BybitSpot import BybitSpot +from jesse.modes.import_candles_mode.drivers.Apex.ApexProPerpetualTestnet import ApexProPerpetualTestnet drivers = { @@ -34,6 +35,7 @@ exchanges.FTX_PERPETUAL_FUTURES: FTXPerpetualFutures, exchanges.BITGET_USDT_PERPETUAL: BitgetUSDTPerpetual, exchanges.BITGET_USDT_PERPETUAL_TESTNET: BitgetUSDTPerpetualTestnet, + exchanges.APEX_PRO_PERPETUAL_TESTNET: ApexProPerpetualTestnet, # Spot exchanges.FTX_SPOT: FTXSpot, From 76e8f6a261f4e645168d0912b915a9001a8bfa4a Mon Sep 17 00:00:00 2001 From: morteza-koohgard Date: Wed, 1 May 2024 13:32:28 +0330 Subject: [PATCH 2/5] add apex mainnet import candles --- jesse/enums/__init__.py | 2 +- jesse/info.py | 14 ++++++++++++++ .../drivers/Apex/ApexProPerpetual.py | 10 ++++++++++ .../modes/import_candles_mode/drivers/__init__.py | 2 ++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 jesse/modes/import_candles_mode/drivers/Apex/ApexProPerpetual.py diff --git a/jesse/enums/__init__.py b/jesse/enums/__init__.py index 0fa262f7b..481a5370a 100644 --- a/jesse/enums/__init__.py +++ b/jesse/enums/__init__.py @@ -77,7 +77,7 @@ class exchanges: DYDX_PERPETUAL = "Dydx Perpetual" DYDX_PERPETUAL_TESTNET = "Dydx Perpetual Testnet" APEX_PRO_PERPETUAL_TESTNET = 'Apex Pro Perpetual Testnet' - + APEX_PRO_PERPETUAL = 'Apex Pro Perpetual' class migration_actions: ADD = 'add' diff --git a/jesse/info.py b/jesse/info.py index 173110da1..8f51e39ee 100644 --- a/jesse/info.py +++ b/jesse/info.py @@ -312,6 +312,20 @@ 'live_trading': True, }, 'required_live_plan': 'premium' + }, + + exchanges_enums.APEX_PRO_PERPETUAL: { + 'name': exchanges_enums.APEX_PRO_PERPETUAL, + 'url': 'https://pro.apex.exchange/trade/BTCUSD', + 'fee': 0.0005, + 'type': 'futures', + 'supported_leverage_modes': ['cross'], + 'supported_timeframes': APEX_PRO_TIMEFRAMES, + 'modes': { + 'backtesting': False, + 'live_trading': True, + }, + 'required_live_plan': 'premium' } } diff --git a/jesse/modes/import_candles_mode/drivers/Apex/ApexProPerpetual.py b/jesse/modes/import_candles_mode/drivers/Apex/ApexProPerpetual.py new file mode 100644 index 000000000..01d2ea244 --- /dev/null +++ b/jesse/modes/import_candles_mode/drivers/Apex/ApexProPerpetual.py @@ -0,0 +1,10 @@ +from .ApexProMain import ApexProMain +from jesse.enums import exchanges + + +class ApexProPerpetual(ApexProMain): + def __init__(self) -> None: + super().__init__( + name=exchanges.APEX_PRO_PERPETUAL, + rest_endpoint='https://pro.apex.exchange/api' + ) diff --git a/jesse/modes/import_candles_mode/drivers/__init__.py b/jesse/modes/import_candles_mode/drivers/__init__.py index 6891fb5bf..37a0b5107 100644 --- a/jesse/modes/import_candles_mode/drivers/__init__.py +++ b/jesse/modes/import_candles_mode/drivers/__init__.py @@ -20,6 +20,7 @@ from jesse.modes.import_candles_mode.drivers.Bybit.BybitSpotTestnet import BybitSpotTestnet from jesse.modes.import_candles_mode.drivers.Bybit.BybitSpot import BybitSpot from jesse.modes.import_candles_mode.drivers.Apex.ApexProPerpetualTestnet import ApexProPerpetualTestnet +from jesse.modes.import_candles_mode.drivers.Apex.ApexProPerpetual import ApexProPerpetual drivers = { @@ -36,6 +37,7 @@ exchanges.BITGET_USDT_PERPETUAL: BitgetUSDTPerpetual, exchanges.BITGET_USDT_PERPETUAL_TESTNET: BitgetUSDTPerpetualTestnet, exchanges.APEX_PRO_PERPETUAL_TESTNET: ApexProPerpetualTestnet, + exchanges.APEX_PRO_PERPETUAL: ApexProPerpetual, # Spot exchanges.FTX_SPOT: FTXSpot, From b6a17d9cb70196286ae448f3fa0317c83382d299 Mon Sep 17 00:00:00 2001 From: morteza-koohgard Date: Tue, 7 May 2024 13:04:31 +0330 Subject: [PATCH 3/5] Refactor ApexProMain.py to remove unnecessary code --- jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py b/jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py index 1958028db..debb874dd 100644 --- a/jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py +++ b/jesse/modes/import_candles_mode/drivers/Apex/ApexProMain.py @@ -57,8 +57,6 @@ def fetch(self, symbol: str, start_timestamp: int, timeframe: str = '1m') -> Uni raise exceptions.InvalidSymbol('Exchange does not support the entered symbol. Please enter a valid symbol.') data = response.json()['data'][dashless_symbol] - # Reverse the data list - data = data[::-1] return [ { From 28a6f972687ef587dad95beee7e9cf047ffb5a4e Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Fri, 7 Jun 2024 19:03:57 +0330 Subject: [PATCH 4/5] feat: improve waiting time for pending market exit orders The code changes in `Strategy.py` modify the waiting time for pending market exit orders. The previous waiting time was set to 10 seconds, but it has been increased to 12 seconds in order to allow more time for the exchange to respond as expected for order execution. This change aims to improve the reliability of the order execution process. Note: The recent user commits and repository commits are not relevant for generating the commit message in this case. --- jesse/strategies/Strategy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jesse/strategies/Strategy.py b/jesse/strategies/Strategy.py index ba6165b92..516325aa5 100644 --- a/jesse/strategies/Strategy.py +++ b/jesse/strategies/Strategy.py @@ -660,11 +660,12 @@ def _check(self) -> None: if jh.is_debugging(): logger.info(f'Waiting {waiting_seconds} second for pending market exit orders to be handled...') waiting_counter += 1 - sleep(1) - if waiting_counter > 10: + if waiting_counter > 12: raise exceptions.ExchangeNotResponding( 'The exchange did not respond as expected for order execution' ) + else: + sleep(1) self._simulate_market_order_execution() From 16ccf0b13e373de2df6f6ddfc4cccc34e9145bfd Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Fri, 7 Jun 2024 19:04:08 +0330 Subject: [PATCH 5/5] improve waiting time for pending market exit orders --- jesse/info.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jesse/info.py b/jesse/info.py index 8f51e39ee..ed3777d44 100644 --- a/jesse/info.py +++ b/jesse/info.py @@ -1,9 +1,9 @@ from jesse.enums import exchanges as exchanges_enums, timeframes -# JESSE_API_URL = 'https://api1.jesse.trade/api' -JESSE_API_URL = 'http://localhost:8040/api' -# JESSE_WEBSITE_URL = 'https://jesse.trade' -JESSE_WEBSITE_URL = 'http://localhost:8040' +JESSE_API_URL = 'https://api1.jesse.trade/api' +# JESSE_API_URL = 'http://localhost:8040/api' +JESSE_WEBSITE_URL = 'https://jesse.trade' +# JESSE_WEBSITE_URL = 'http://localhost:8040' BYBIT_TIMEFRAMES = [timeframes.MINUTE_1, timeframes.MINUTE_3, timeframes.MINUTE_5, timeframes.MINUTE_15, timeframes.MINUTE_30, timeframes.HOUR_1, timeframes.HOUR_2, timeframes.HOUR_4, timeframes.HOUR_6, timeframes.HOUR_12, timeframes.DAY_1] FTX_TIMEFRAMES = [timeframes.MINUTE_1, timeframes.MINUTE_3, timeframes.MINUTE_5, timeframes.MINUTE_15, timeframes.MINUTE_30, timeframes.HOUR_1, timeframes.HOUR_2, timeframes.HOUR_4, timeframes.HOUR_6, timeframes.HOUR_12, timeframes.DAY_1]