From 4228c335b113178f2c79f031625c4fbbd509061a Mon Sep 17 00:00:00 2001 From: morteza-koohgard Date: Wed, 1 May 2024 13:25:06 +0330 Subject: [PATCH 01/15] 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 02/15] 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 03/15] 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 04/15] 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 05/15] 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] From a4171c305be88e824791e8e7d90b8272971bdd0a Mon Sep 17 00:00:00 2001 From: morteza-koohgard Date: Sat, 15 Jun 2024 17:26:39 +0330 Subject: [PATCH 06/15] add ttmSqueeze --- jesse/indicators/TTMSqueeze.py | 39 ++++++++++++++++++++++++++++++++++ jesse/indicators/__init__.py | 1 + tests/test_indicators.py | 7 ++++++ 3 files changed, 47 insertions(+) create mode 100644 jesse/indicators/TTMSqueeze.py diff --git a/jesse/indicators/TTMSqueeze.py b/jesse/indicators/TTMSqueeze.py new file mode 100644 index 000000000..879b822e3 --- /dev/null +++ b/jesse/indicators/TTMSqueeze.py @@ -0,0 +1,39 @@ +from collections import namedtuple + +from .bollinger_bands import bollinger_bands +from .sma import sma +from .trange import trange + +import numpy as np + +TTMSqueezeTuple = namedtuple('TTMSqueezeTuple', ['sqz_signal']) + + +def TTMSqueeze(candles: np.ndarray, length_ttms: int = 20, bb_mult_ttms: float = 2.0, kc_mult_low_ttms: float = 2.0, source_type: str = "close") -> TTMSqueezeTuple: + """ + @author daviddtech + credits: https://www.tradingview.com/script/Mh3EmxF5-TTM-Squeeze-DaviddTech/ + + TTMSQUEEZE - TTMSqueeze + + :param candles: np.ndarray + :param length_ttms: int - default: 20 + :param bb_mult_ttms: float - default: 2.0 + :param kc_mult_low_ttms: float - default: 2.0 + :param source_type: str - default: "close" + + :return: TTMSqueezeTuple(sqz_signal) + """ + bb_data = bollinger_bands(candles, length_ttms, bb_mult_ttms) + + kc_basis_ttms = sma(candles, length_ttms) + devkc_ttms = sma(trange(candles, sequential=True), period=length_ttms) + + NoSqz_ttms = bb_data.lowerband < kc_basis_ttms - devkc_ttms * \ + kc_mult_low_ttms or bb_data.upperband > kc_basis_ttms + devkc_ttms * kc_mult_low_ttms + + sqz_signal = False + if NoSqz_ttms: + sqz_signal = True + + return TTMSqueezeTuple(sqz_signal) diff --git a/jesse/indicators/__init__.py b/jesse/indicators/__init__.py index 6b211802a..570707c0e 100644 --- a/jesse/indicators/__init__.py +++ b/jesse/indicators/__init__.py @@ -177,3 +177,4 @@ from .zscore import zscore from .waddah_attr_explosion import waddah_attar_explosion from .stiffness import stiffness +from .TTMSqueeze import TTMSqueeze diff --git a/tests/test_indicators.py b/tests/test_indicators.py index 050a1ce3c..0757f4695 100644 --- a/tests/test_indicators.py +++ b/tests/test_indicators.py @@ -2351,3 +2351,10 @@ def test_stiffness(): assert round(single.stiffness) == 96 assert round(single.threshold) == 90 + + +def test_TTMSqueeze(): + candles = np.array(test_candles_19) + resule = ta.TTMSqueeze(candles) + + assert resule.sqz_signal == True From d4fc81ef21494674554f6505d637803ba135bf2a Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Wed, 26 Jun 2024 23:49:50 +0330 Subject: [PATCH 07/15] refactor: rename TTMSqueeze to ttm_squeeze and update import statements The code changes in this commit refactor the name of the `TTMSqueeze` indicator to `ttm_squeeze` to follow the Python naming conventions. The corresponding import statements have also been updated to reflect the new name. This change improves code readability and maintainability. --- jesse/indicators/__init__.py | 2 +- .../indicators/{TTMSqueeze.py => ttm_squeeze.py} | 16 +++++----------- tests/test_indicators.py | 6 +++--- 3 files changed, 9 insertions(+), 15 deletions(-) rename jesse/indicators/{TTMSqueeze.py => ttm_squeeze.py} (61%) diff --git a/jesse/indicators/__init__.py b/jesse/indicators/__init__.py index 570707c0e..6fd59d77c 100644 --- a/jesse/indicators/__init__.py +++ b/jesse/indicators/__init__.py @@ -177,4 +177,4 @@ from .zscore import zscore from .waddah_attr_explosion import waddah_attar_explosion from .stiffness import stiffness -from .TTMSqueeze import TTMSqueeze +from .ttm_squeeze import ttm_squeeze diff --git a/jesse/indicators/TTMSqueeze.py b/jesse/indicators/ttm_squeeze.py similarity index 61% rename from jesse/indicators/TTMSqueeze.py rename to jesse/indicators/ttm_squeeze.py index 879b822e3..68fb98a50 100644 --- a/jesse/indicators/TTMSqueeze.py +++ b/jesse/indicators/ttm_squeeze.py @@ -1,15 +1,10 @@ -from collections import namedtuple - from .bollinger_bands import bollinger_bands from .sma import sma from .trange import trange - import numpy as np -TTMSqueezeTuple = namedtuple('TTMSqueezeTuple', ['sqz_signal']) - -def TTMSqueeze(candles: np.ndarray, length_ttms: int = 20, bb_mult_ttms: float = 2.0, kc_mult_low_ttms: float = 2.0, source_type: str = "close") -> TTMSqueezeTuple: +def ttm_squeeze(candles: np.ndarray, length_ttms: int = 20, bb_mult_ttms: float = 2.0, kc_mult_low_ttms: float = 2.0) -> bool: """ @author daviddtech credits: https://www.tradingview.com/script/Mh3EmxF5-TTM-Squeeze-DaviddTech/ @@ -20,20 +15,19 @@ def TTMSqueeze(candles: np.ndarray, length_ttms: int = 20, bb_mult_ttms: float = :param length_ttms: int - default: 20 :param bb_mult_ttms: float - default: 2.0 :param kc_mult_low_ttms: float - default: 2.0 - :param source_type: str - default: "close" - :return: TTMSqueezeTuple(sqz_signal) + :return: TTMSqueeze(sqz_signal) """ bb_data = bollinger_bands(candles, length_ttms, bb_mult_ttms) kc_basis_ttms = sma(candles, length_ttms) devkc_ttms = sma(trange(candles, sequential=True), period=length_ttms) - NoSqz_ttms = bb_data.lowerband < kc_basis_ttms - devkc_ttms * \ + no_sqz_ttms = bb_data.lowerband < kc_basis_ttms - devkc_ttms * \ kc_mult_low_ttms or bb_data.upperband > kc_basis_ttms + devkc_ttms * kc_mult_low_ttms sqz_signal = False - if NoSqz_ttms: + if no_sqz_ttms: sqz_signal = True - return TTMSqueezeTuple(sqz_signal) + return sqz_signal diff --git a/tests/test_indicators.py b/tests/test_indicators.py index 0757f4695..02bdaa6e3 100644 --- a/tests/test_indicators.py +++ b/tests/test_indicators.py @@ -2353,8 +2353,8 @@ def test_stiffness(): assert round(single.threshold) == 90 -def test_TTMSqueeze(): +def test_ttm_squeeze(): candles = np.array(test_candles_19) - resule = ta.TTMSqueeze(candles) + result = ta.ttm_squeeze(candles) - assert resule.sqz_signal == True + assert result == True From 710d0011ea808565cc0c02a2ba2f7887b1791a64 Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Thu, 27 Jun 2024 17:22:31 +0330 Subject: [PATCH 08/15] chore: update TA-Lib dependency to version 0.4.31 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3fbd29f8..dfdb50a61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ quantstats==0.0.47 requests==2.32.0 scipy statsmodels==0.14.0 -TA-Lib==0.4.28 +TA-Lib==0.4.31 tabulate==0.8.9 timeloop==1.0.2 websocket-client==1.8.0 From d3bd0134b8fca378c05644958e3f586845908d4a Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Thu, 27 Jun 2024 17:24:04 +0330 Subject: [PATCH 09/15] chore: bump version to 0.48.6 --- jesse/version.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jesse/version.py b/jesse/version.py index 1082c00e6..21d1ea7b7 100644 --- a/jesse/version.py +++ b/jesse/version.py @@ -1 +1 @@ -__version__ = '0.48.5' +__version__ = '0.48.6' diff --git a/setup.py b/setup.py index 89cea5d8f..c6000c1a7 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages # also change in version.py -VERSION = '0.48.5' +VERSION = '0.48.6' DESCRIPTION = "A trading framework for cryptocurrencies" with open("requirements.txt", "r", encoding="utf-8") as f: REQUIRED_PACKAGES = f.read().splitlines() From 56889daa93839046d6f43886cd464ab66bdc987f Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Sat, 29 Jun 2024 20:48:11 +0330 Subject: [PATCH 10/15] remove file --- addNewDriverGuide.md | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 addNewDriverGuide.md diff --git a/addNewDriverGuide.md b/addNewDriverGuide.md deleted file mode 100644 index ba48a6f66..000000000 --- a/addNewDriverGuide.md +++ /dev/null @@ -1,18 +0,0 @@ -## Steps for adding a new driver for BACKTEST (import candles): - -1. Add it to enum -2. Add it to info.py -3. Create the driver for import-candles -4. Add it to modes/import_candles_mode/drivers/__init__.py - -================================================ - -## Steps for adding a new driver for LIVE: -- Implement the driver class -- Add it to jesse_live/exchanges/__init__.py -- Add it to config['app']['live_drivers'] in live plugin's config.py -- Add .env values for the driver -- Update EXCLUDE_FILES in jesse-live's setup.py - -## Steps for adding a new driver in Laravel project: -- add driver name without dashes in the validateLicense method in ApiController file From c2721c7d2a789b98612c825399ce27cfb9c23fe1 Mon Sep 17 00:00:00 2001 From: morteza-koohgard Date: Sun, 30 Jun 2024 00:56:52 +0330 Subject: [PATCH 11/15] add supprt resistance with break indicator --- jesse/indicators/__init__.py | 1 + .../support_resistance_with_break.py | 117 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 jesse/indicators/support_resistance_with_break.py diff --git a/jesse/indicators/__init__.py b/jesse/indicators/__init__.py index 6fd59d77c..b87928e76 100644 --- a/jesse/indicators/__init__.py +++ b/jesse/indicators/__init__.py @@ -178,3 +178,4 @@ from .waddah_attr_explosion import waddah_attar_explosion from .stiffness import stiffness from .ttm_squeeze import ttm_squeeze +from .support_resistance_with_break import support_resistance_with_break diff --git a/jesse/indicators/support_resistance_with_break.py b/jesse/indicators/support_resistance_with_break.py new file mode 100644 index 000000000..8f2cec29f --- /dev/null +++ b/jesse/indicators/support_resistance_with_break.py @@ -0,0 +1,117 @@ +from collections import namedtuple + +import numpy as np +from .ema import ema + +SupportResistance = namedtuple('SupportResistance', ['support', 'resistance', 'red_break', 'green_break', 'bear_wick', 'bull_wick']) + + +def support_resistance_with_break(candles: np.ndarray, left_bars: int = 15, right_bars: int = 15, vol_threshold: int = 20) -> SupportResistance: + """ + SupportResistance + + @author LuxAlgo + credits: https://www.tradingview.com/script/JDFoWQbL-Support-and-Resistance-Levels-with-Breaks-LuxAlgo + + :param candles: np.ndarray + :param left_bars: int - default: 15 + :param right_bars: int - default: 15 + :param vol_threshold: int - default: 20 + :return: SupportResistance(support, resistance, red_break, green_break, bear_wick, bull_wick) + """ + resistance = _resistance(candles[:, 3], left_bars, right_bars) + support = _support(candles[:, 4], left_bars, right_bars) + + short = ema(candles[:, 5], 5) + long = ema(candles[:, 5], 10) + osc = 100 * (short - long) / long + + last_candles = candles[0] + + red_break = True if last_candles[2] < support and not abs( + last_candles[1] - last_candles[2]) < abs(last_candles[1] - last_candles[3]) and osc > vol_threshold else False + green_break = True if last_candles[2] > resistance and abs( + last_candles[1] - last_candles[4]) > abs(last_candles[1] - last_candles[2]) and osc > vol_threshold else False + + bull_wick = True if last_candles[2] > resistance and abs(last_candles[1] - last_candles[4]) > abs(last_candles[1] - last_candles[2]) else False + bear_wick = True if last_candles[2] < support and abs(last_candles[1] - last_candles[2]) < abs(last_candles[1] - last_candles[3]) else False + + return SupportResistance(support, resistance, red_break, green_break, bear_wick, bull_wick) + + +def _resistance(source, leftBars, rightBars): + pivot_highs = [None] * len(source) # Initialize result list with None + + for i in range(leftBars, len(source) - rightBars): + is_pivot_high = True + + # Check left bars for higher high + for j in range(1, leftBars + 1): + if source[i] <= source[i - j]: + is_pivot_high = False + break + + # Check right bars for higher high + if is_pivot_high: + for j in range(1, rightBars + 1): + if source[i] <= source[i + j]: + is_pivot_high = False + break + + if is_pivot_high: + is_pivot_high = source[i] + + pivot_highs[i] = is_pivot_high + + next_valid = None + first_value = None + for i in range(len(pivot_highs)): + if pivot_highs[i] is False: + pivot_highs[i] = next_valid + elif pivot_highs[i] is not None: # Update next_valid if it's not False or None + next_valid = pivot_highs[i] + first_value = i if first_value is None else first_value + + pivot_highs[:first_value - 1] = [pivot_highs[first_value]] * len(pivot_highs[:first_value - 1]) + pivot_highs[-rightBars:] = [pivot_highs[-rightBars - 1]] * len(pivot_highs[-rightBars:]) + + return pivot_highs[-1] + + +def _support(source, leftBars, rightBars): + pivot_lows = [None] * len(source) # Initialize result list with None + + for i in range(leftBars, len(source) - rightBars): + is_pivot_low = True + + # Check left bars for lower low + for j in range(1, leftBars + 1): + if source[i] >= source[i - j]: + is_pivot_low = False + break + + # Check right bars for lower low + if is_pivot_low: + for j in range(1, rightBars + 1): + if source[i] >= source[i + j]: + is_pivot_low = False + break + + if is_pivot_low: + is_pivot_low = source[i] + + pivot_lows[i] = is_pivot_low + + next_valid = None + first_value = None + for i in range(len(pivot_lows)): + if pivot_lows[i] is False: + pivot_lows[i] = next_valid + elif pivot_lows[i] is not None: # Update next_valid if it's not False or None + next_valid = pivot_lows[i] + first_value = i if first_value is None else first_value + + pivot_lows[:first_value - 1] = [pivot_lows[first_value]] * len(pivot_lows[:first_value - 1]) + pivot_lows[-rightBars:] = [pivot_lows[-rightBars - 1]] * len(pivot_lows[-rightBars:]) + + return pivot_lows[-1] From c28829bb793a3f1d8e55c6d9d642604225b96278 Mon Sep 17 00:00:00 2001 From: morteza-koohgard Date: Sun, 30 Jun 2024 00:59:57 +0330 Subject: [PATCH 12/15] add test for support resistance indicator --- tests/test_indicators.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_indicators.py b/tests/test_indicators.py index 02bdaa6e3..670ae44a6 100644 --- a/tests/test_indicators.py +++ b/tests/test_indicators.py @@ -2358,3 +2358,15 @@ def test_ttm_squeeze(): result = ta.ttm_squeeze(candles) assert result == True + + +def test_support_resistance_with_breack(): + candles = np.array(test_candles_19) + result = ta.support_resistance_with_break(candles) + + assert result.support == 116.26 + assert result.resistance == 288.41 + assert result.red_break == False + assert result.green_break == False + assert result.bear_wick == False + assert result.bull_wick == False From 0187ff271f560c6e1dbf3562024d3b8accff3ccf Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Sun, 30 Jun 2024 16:26:04 +0330 Subject: [PATCH 13/15] refactor: remove unused import statement --- jesse/modes/optimize_mode/Optimize.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jesse/modes/optimize_mode/Optimize.py b/jesse/modes/optimize_mode/Optimize.py index 876f46503..e2cb143a9 100644 --- a/jesse/modes/optimize_mode/Optimize.py +++ b/jesse/modes/optimize_mode/Optimize.py @@ -7,7 +7,6 @@ import click import numpy as np import pydash -from numpy import ndarray from pandas import json_normalize from timeloop import Timeloop From f0e8b425144d681ebbab94129e870fdc121785c3 Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Sun, 30 Jun 2024 16:26:14 +0330 Subject: [PATCH 14/15] refactor: update fast_mode parameter in get_fitness function --- jesse/modes/optimize_mode/fitness.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jesse/modes/optimize_mode/fitness.py b/jesse/modes/optimize_mode/fitness.py index d12f826fc..dcd5d89e0 100644 --- a/jesse/modes/optimize_mode/fitness.py +++ b/jesse/modes/optimize_mode/fitness.py @@ -43,7 +43,7 @@ def get_fitness( candles=training_candles, warmup_candles=training_warmup_candles, hyperparameters=hp, - fast_mode=True + fast_mode=False )['metrics'] except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() @@ -121,7 +121,7 @@ def get_fitness( candles=testing_candles, warmup_candles=testing_warmup_candles, hyperparameters=hp, - fast_mode=True + fast_mode=False )['metrics'] # log for debugging/monitoring From 3bd634e1e774054c754f58acb346f12579861eb9 Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Sun, 30 Jun 2024 16:33:00 +0330 Subject: [PATCH 15/15] chore: Update support_resistance_with_break indicator to use SupportResistanceWithBreaks namedtuple The code changes in this commit update the `support_resistance_with_break` indicator to use the `SupportResistanceWithBreaks` namedtuple instead of the deprecated `SupportResistance` namedtuple. This change ensures consistency and clarity in the codebase. --- jesse/indicators/__init__.py | 2 +- .../support_resistance_with_break.py | 30 +++++++++---------- tests/test_indicators.py | 4 +-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/jesse/indicators/__init__.py b/jesse/indicators/__init__.py index b87928e76..290fd2fae 100644 --- a/jesse/indicators/__init__.py +++ b/jesse/indicators/__init__.py @@ -178,4 +178,4 @@ from .waddah_attr_explosion import waddah_attar_explosion from .stiffness import stiffness from .ttm_squeeze import ttm_squeeze -from .support_resistance_with_break import support_resistance_with_break +from .support_resistance_with_break import support_resistance_with_breaks diff --git a/jesse/indicators/support_resistance_with_break.py b/jesse/indicators/support_resistance_with_break.py index 8f2cec29f..ab746266c 100644 --- a/jesse/indicators/support_resistance_with_break.py +++ b/jesse/indicators/support_resistance_with_break.py @@ -3,12 +3,12 @@ import numpy as np from .ema import ema -SupportResistance = namedtuple('SupportResistance', ['support', 'resistance', 'red_break', 'green_break', 'bear_wick', 'bull_wick']) +SupportResistanceWithBreaks = namedtuple('SupportResistanceWithBreaks', ['support', 'resistance', 'red_break', 'green_break', 'bear_wick', 'bull_wick']) -def support_resistance_with_break(candles: np.ndarray, left_bars: int = 15, right_bars: int = 15, vol_threshold: int = 20) -> SupportResistance: +def support_resistance_with_breaks(candles: np.ndarray, left_bars: int = 15, right_bars: int = 15, vol_threshold: int = 20) -> SupportResistanceWithBreaks: """ - SupportResistance + support_resistance_with_breaks @author LuxAlgo credits: https://www.tradingview.com/script/JDFoWQbL-Support-and-Resistance-Levels-with-Breaks-LuxAlgo @@ -17,7 +17,7 @@ def support_resistance_with_break(candles: np.ndarray, left_bars: int = 15, righ :param left_bars: int - default: 15 :param right_bars: int - default: 15 :param vol_threshold: int - default: 20 - :return: SupportResistance(support, resistance, red_break, green_break, bear_wick, bull_wick) + :return: SupportResistanceWithBreaks(support, resistance, red_break, green_break, bear_wick, bull_wick) """ resistance = _resistance(candles[:, 3], left_bars, right_bars) support = _support(candles[:, 4], left_bars, right_bars) @@ -36,24 +36,24 @@ def support_resistance_with_break(candles: np.ndarray, left_bars: int = 15, righ bull_wick = True if last_candles[2] > resistance and abs(last_candles[1] - last_candles[4]) > abs(last_candles[1] - last_candles[2]) else False bear_wick = True if last_candles[2] < support and abs(last_candles[1] - last_candles[2]) < abs(last_candles[1] - last_candles[3]) else False - return SupportResistance(support, resistance, red_break, green_break, bear_wick, bull_wick) + return SupportResistanceWithBreaks(support, resistance, red_break, green_break, bear_wick, bull_wick) -def _resistance(source, leftBars, rightBars): +def _resistance(source, left_bars, right_bars): pivot_highs = [None] * len(source) # Initialize result list with None - for i in range(leftBars, len(source) - rightBars): + for i in range(left_bars, len(source) - right_bars): is_pivot_high = True # Check left bars for higher high - for j in range(1, leftBars + 1): + for j in range(1, left_bars + 1): if source[i] <= source[i - j]: is_pivot_high = False break # Check right bars for higher high if is_pivot_high: - for j in range(1, rightBars + 1): + for j in range(1, right_bars + 1): if source[i] <= source[i + j]: is_pivot_high = False break @@ -73,26 +73,26 @@ def _resistance(source, leftBars, rightBars): first_value = i if first_value is None else first_value pivot_highs[:first_value - 1] = [pivot_highs[first_value]] * len(pivot_highs[:first_value - 1]) - pivot_highs[-rightBars:] = [pivot_highs[-rightBars - 1]] * len(pivot_highs[-rightBars:]) + pivot_highs[-right_bars:] = [pivot_highs[-right_bars - 1]] * len(pivot_highs[-right_bars:]) return pivot_highs[-1] -def _support(source, leftBars, rightBars): +def _support(source, left_bars, right_bars): pivot_lows = [None] * len(source) # Initialize result list with None - for i in range(leftBars, len(source) - rightBars): + for i in range(left_bars, len(source) - right_bars): is_pivot_low = True # Check left bars for lower low - for j in range(1, leftBars + 1): + for j in range(1, left_bars + 1): if source[i] >= source[i - j]: is_pivot_low = False break # Check right bars for lower low if is_pivot_low: - for j in range(1, rightBars + 1): + for j in range(1, right_bars + 1): if source[i] >= source[i + j]: is_pivot_low = False break @@ -112,6 +112,6 @@ def _support(source, leftBars, rightBars): first_value = i if first_value is None else first_value pivot_lows[:first_value - 1] = [pivot_lows[first_value]] * len(pivot_lows[:first_value - 1]) - pivot_lows[-rightBars:] = [pivot_lows[-rightBars - 1]] * len(pivot_lows[-rightBars:]) + pivot_lows[-right_bars:] = [pivot_lows[-right_bars - 1]] * len(pivot_lows[-right_bars:]) return pivot_lows[-1] diff --git a/tests/test_indicators.py b/tests/test_indicators.py index 670ae44a6..7ad866afe 100644 --- a/tests/test_indicators.py +++ b/tests/test_indicators.py @@ -2360,9 +2360,9 @@ def test_ttm_squeeze(): assert result == True -def test_support_resistance_with_breack(): +def test_support_resistance_with_breaks(): candles = np.array(test_candles_19) - result = ta.support_resistance_with_break(candles) + result = ta.support_resistance_with_breaks(candles) assert result.support == 116.26 assert result.resistance == 288.41