From bfc238a65d131360bdaf60cb0cb73eb268c48afe Mon Sep 17 00:00:00 2001 From: yakir Date: Fri, 24 May 2024 08:39:21 +0300 Subject: [PATCH 01/13] feat(performance): delete orders from future exchange instead of creating a vector of 100s of (0,0) --- jesse/libs/dynamic_numpy_array/__init__.py | 10 ++++++ jesse/models/FuturesExchange.py | 40 ++++++++++------------ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/jesse/libs/dynamic_numpy_array/__init__.py b/jesse/libs/dynamic_numpy_array/__init__.py index 828dae639..d3ab946e0 100644 --- a/jesse/libs/dynamic_numpy_array/__init__.py +++ b/jesse/libs/dynamic_numpy_array/__init__.py @@ -133,3 +133,13 @@ def append_multiple(self, items: np.ndarray) -> None: self.array = np_shift(self.array, -shift_num) self.array[self.index - len(items) + 1 : self.index + 1] = items + + def delete(self, index: int, axis=None) -> None: + self.array = np.delete(self.array, index, axis=axis) + self.index -= 1 + if self.index == 0: + new_bucket = np.zeros(self.shape) + self.array = np.concatenate((self.array, new_bucket), axis=0) + + + diff --git a/jesse/models/FuturesExchange.py b/jesse/models/FuturesExchange.py index 6b53cb058..5b55b515e 100644 --- a/jesse/models/FuturesExchange.py +++ b/jesse/models/FuturesExchange.py @@ -131,18 +131,17 @@ def on_order_execution(self, order: Order) -> None: base_asset = jh.base_asset(order.symbol) if not order.reduce_only: + order_array = np.array([order.qty, order.price]) if order.side == sides.BUY: - # find and set order to [0, 0] (same as removing it) - for index, item in enumerate(self.buy_orders[base_asset]): - if item[0] == order.qty and item[1] == order.price: - self.buy_orders[base_asset][index] = np.array([0, 0]) - break + item_index = np.where(np.all(self.buy_orders[base_asset].array == order_array, axis=1))[0] + if len(item_index) > 0: + index = item_index[0] + self.buy_orders[base_asset].delete(index, axis=0) else: - # find and set order to [0, 0] (same as removing it) - for index, item in enumerate(self.sell_orders[base_asset]): - if item[0] == order.qty and item[1] == order.price: - self.sell_orders[base_asset][index] = np.array([0, 0]) - break + item_index = np.where(np.all(self.sell_orders[base_asset].array == order_array, axis=1))[0] + if len(item_index) > 0: + index = item_index[0] + self.sell_orders[base_asset].delete(index, axis=0) def on_order_cancellation(self, order: Order) -> None: if jh.is_livetrading(): @@ -153,20 +152,17 @@ def on_order_cancellation(self, order: Order) -> None: self.available_assets[base_asset] -= order.qty # self.available_assets[quote_asset] += order.qty * order.price if not order.reduce_only: + order_array = np.array([order.qty, order.price]) if order.side == sides.BUY: - # find and set order to [0, 0] (same as removing it) - for index in range(len(self.buy_orders[base_asset]) - 1, -1, -1): - item = self.buy_orders[base_asset][index] - if item[0] == order.qty and item[1] == order.price: - self.buy_orders[base_asset][index] = np.array([0, 0]) - break + item_index = np.where(np.all(self.buy_orders[base_asset].array == order_array, axis=1))[0] + if len(item_index) > 0: + index = item_index[0] + self.buy_orders[base_asset].delete(index, axis=0) else: - # find and set order to [0, 0] (same as removing it) - for index in range(len(self.sell_orders[base_asset]) - 1, -1, -1): - item = self.sell_orders[base_asset][index] - if item[0] == order.qty and item[1] == order.price: - self.sell_orders[base_asset][index] = np.array([0, 0]) - break + item_index = np.where(np.all(self.sell_orders[base_asset].array == order_array, axis=1))[0] + if len(item_index) > 0: + index = item_index[0] + self.sell_orders[base_asset].delete(index, axis=0) def update_from_stream(self, data: dict) -> None: """ From f6ec45f364ab71b1c19070596a45fd6be2e51a07 Mon Sep 17 00:00:00 2001 From: yakir Date: Fri, 24 May 2024 08:47:31 +0300 Subject: [PATCH 02/13] feat(performance): remove from store the orders that isnt active or is canceled because it takes a lot of time! --- jesse/modes/backtest_mode.py | 2 ++ jesse/store/state_orders.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/jesse/modes/backtest_mode.py b/jesse/modes/backtest_mode.py index 523b356ca..05e202f0a 100644 --- a/jesse/modes/backtest_mode.py +++ b/jesse/modes/backtest_mode.py @@ -626,6 +626,8 @@ def _skip_simulator( ) r.strategy._execute() + store.orders.move_executed_orders(r.exchange, r.symbol) + # now check to see if there's any MARKET orders waiting to be executed store.orders.execute_pending_market_orders() diff --git a/jesse/store/state_orders.py b/jesse/store/state_orders.py index 5ff47b43b..fdccaebd5 100644 --- a/jesse/store/state_orders.py +++ b/jesse/store/state_orders.py @@ -14,11 +14,13 @@ def __init__(self) -> None: self.to_execute = [] self.storage = {} + self.executed_storage = {} for exchange in config['app']['trading_exchanges']: for symbol in config['app']['trading_symbols']: key = f'{exchange}-{symbol}' self.storage[key] = [] + self.executed_storage[key] = [] def reset(self) -> None: """ @@ -123,4 +125,20 @@ def get_exit_orders(self, exchange: str, symbol: str) -> List[Order]: return exit_orders + def move_executed_orders(self, exchange: str, symbol: str): + executed_orders = [] + active_orders = [] + + key = f'{exchange}-{symbol}' + for order in self.storage.get(key, []): + if order.is_canceled or order.is_executed: + executed_orders.append(order) + else: + active_orders.append(order) + + self.storage[key] = active_orders + if key not in self.executed_storage: + self.executed_storage[key] = [] + self.executed_storage[key] += executed_orders + From c1a00126ea45069550d2f49ec19105f5f66c7857 Mon Sep 17 00:00:00 2001 From: yakir Date: Fri, 24 May 2024 09:26:38 +0300 Subject: [PATCH 03/13] feat(performance): remove from store the orders that isnt active or is canceled because it takes a lot of time! --- jesse/modes/backtest_mode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jesse/modes/backtest_mode.py b/jesse/modes/backtest_mode.py index 05e202f0a..2b332296b 100644 --- a/jesse/modes/backtest_mode.py +++ b/jesse/modes/backtest_mode.py @@ -336,6 +336,8 @@ def _step_simulator( r.symbol) r.strategy._execute() + store.orders.move_executed_orders(r.exchange, r.symbol) + # now check to see if there's any MARKET orders waiting to be executed store.orders.execute_pending_market_orders() From 3b588d39b8f71da0252fcdb55c8738367967996f Mon Sep 17 00:00:00 2001 From: yakir Date: Fri, 24 May 2024 09:34:09 +0300 Subject: [PATCH 04/13] feat(performance): remove from store the orders that isnt active or is canceled because it takes a lot of time! --- jesse/libs/dynamic_numpy_array/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jesse/libs/dynamic_numpy_array/__init__.py b/jesse/libs/dynamic_numpy_array/__init__.py index d3ab946e0..3d0e266e8 100644 --- a/jesse/libs/dynamic_numpy_array/__init__.py +++ b/jesse/libs/dynamic_numpy_array/__init__.py @@ -137,7 +137,7 @@ def append_multiple(self, items: np.ndarray) -> None: def delete(self, index: int, axis=None) -> None: self.array = np.delete(self.array, index, axis=axis) self.index -= 1 - if self.index == 0: + if self.index == -1: new_bucket = np.zeros(self.shape) self.array = np.concatenate((self.array, new_bucket), axis=0) From bcf34fe179964de4a18427f086ff27a64d0e0661 Mon Sep 17 00:00:00 2001 From: Yakir Date: Fri, 24 May 2024 16:04:51 +0300 Subject: [PATCH 05/13] feat(orders): strategy function get_orders now use sorted to recreate orders. --- jesse/store/state_orders.py | 8 ++++++++ jesse/strategies/Strategy.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/jesse/store/state_orders.py b/jesse/store/state_orders.py index fdccaebd5..ec5f06bae 100644 --- a/jesse/store/state_orders.py +++ b/jesse/store/state_orders.py @@ -62,6 +62,14 @@ def get_orders(self, exchange, symbol) -> List[Order]: key = f'{exchange}-{symbol}' return self.storage.get(key, []) + def get_active_orders(self) -> List[Order]: + key = f'{exchange}-{symbol}' + return self.storage.get(key, []) + + def get_executed_orders(self, exchange, symbol) -> List[Order]: + key = f'{exchange}-{symbol}' + return self.executed_storage.get(key, []) + def get_all_orders(self, exchange: str) -> List[Order]: return [o for key in self.storage for o in self.storage[key] if o.exchange == exchange] diff --git a/jesse/strategies/Strategy.py b/jesse/strategies/Strategy.py index a78ef9785..12057ce66 100644 --- a/jesse/strategies/Strategy.py +++ b/jesse/strategies/Strategy.py @@ -1245,7 +1245,9 @@ def orders(self) -> List[Order]: """ Returns all the orders submitted by for this strategy. """ - return store.orders.get_orders(self.exchange, self.symbol) + executed_orders = store.orders.get_executed_orders(self.exchange, self.symbol) + active_orders = store.orders.get_orders(self.exchange, self.symbol) + return sorted(executed_orders + active_orders, key=lambda o: o.created_at) @property def entry_orders(self): From b772cf29c065712b74c0f479b1da822068ac05d9 Mon Sep 17 00:00:00 2001 From: Yakir Date: Fri, 24 May 2024 16:30:37 +0300 Subject: [PATCH 06/13] feat(orders): make a list of active orders instead of unactive orders --- jesse/modes/backtest_mode.py | 4 +-- jesse/store/state_orders.py | 62 ++++++++++++++++++------------------ jesse/strategies/Strategy.py | 4 +-- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/jesse/modes/backtest_mode.py b/jesse/modes/backtest_mode.py index 2b332296b..f75352408 100644 --- a/jesse/modes/backtest_mode.py +++ b/jesse/modes/backtest_mode.py @@ -336,7 +336,7 @@ def _step_simulator( r.symbol) r.strategy._execute() - store.orders.move_executed_orders(r.exchange, r.symbol) + store.orders.update_active_orders(r.exchange, r.symbol) # now check to see if there's any MARKET orders waiting to be executed store.orders.execute_pending_market_orders() @@ -628,7 +628,7 @@ def _skip_simulator( ) r.strategy._execute() - store.orders.move_executed_orders(r.exchange, r.symbol) + store.orders.update_active_orders(r.exchange, r.symbol) # now check to see if there's any MARKET orders waiting to be executed store.orders.execute_pending_market_orders() diff --git a/jesse/store/state_orders.py b/jesse/store/state_orders.py index ec5f06bae..70b08c208 100644 --- a/jesse/store/state_orders.py +++ b/jesse/store/state_orders.py @@ -14,13 +14,13 @@ def __init__(self) -> None: self.to_execute = [] self.storage = {} - self.executed_storage = {} + self.active_storage = {} for exchange in config['app']['trading_exchanges']: for symbol in config['app']['trading_symbols']: key = f'{exchange}-{symbol}' self.storage[key] = [] - self.executed_storage[key] = [] + self.active_storage[key] = [] def reset(self) -> None: """ @@ -28,6 +28,7 @@ def reset(self) -> None: """ for key in self.storage: self.storage[key].clear() + self.active_storage[key].clear() def reset_trade_orders(self, exchange: str, symbol: str) -> None: """ @@ -35,16 +36,21 @@ def reset_trade_orders(self, exchange: str, symbol: str) -> None: """ key = f'{exchange}-{symbol}' self.storage[key] = [] + self.active_storage[key] = [] def add_order(self, order: Order) -> None: key = f'{order.exchange}-{order.symbol}' self.storage[key].append(order) + self.active_storage[key].append(order) def remove_order(self, order: Order) -> None: key = f'{order.exchange}-{order.symbol}' self.storage[key] = [ o for o in self.storage[key] if o.id != order.id ] + self.active_storage[key] = [ + o for o in self.active_storage[key] if o.id != order.id + ] def execute_pending_market_orders(self) -> None: if not self.to_execute: @@ -62,28 +68,31 @@ def get_orders(self, exchange, symbol) -> List[Order]: key = f'{exchange}-{symbol}' return self.storage.get(key, []) - def get_active_orders(self) -> List[Order]: - key = f'{exchange}-{symbol}' - return self.storage.get(key, []) - - def get_executed_orders(self, exchange, symbol) -> List[Order]: + def get_active_orders(self, exchange, symbol) -> List[Order]: key = f'{exchange}-{symbol}' - return self.executed_storage.get(key, []) + return self.active_storage.get(key, []) def get_all_orders(self, exchange: str) -> List[Order]: - return [o for key in self.storage for o in self.storage[key] if o.exchange == exchange] + return [ + o + for key in self.storage + for o in self.storage[key] + if o.exchange == exchange + ] def count_all_active_orders(self) -> int: c = 0 - for key in self.storage: - if len(self.storage[key]): - for o in self.storage[key]: - if o.is_active: - c += 1 + for key in self.active_storage: + if len(self.active_storage[key]) == 0: + continue + + for o in self.active_storage[key]: + if o.is_active: + c += 1 return c def count_active_orders(self, exchange: str, symbol: str) -> int: - orders = self.get_orders(exchange, symbol) + orders = self.get_active_orders(exchange, symbol) return sum(bool(o.is_active) for o in orders) @@ -133,20 +142,11 @@ def get_exit_orders(self, exchange: str, symbol: str) -> List[Order]: return exit_orders - def move_executed_orders(self, exchange: str, symbol: str): - executed_orders = [] - active_orders = [] - + def update_active_orders(self, exchange: str, symbol: str): key = f'{exchange}-{symbol}' - for order in self.storage.get(key, []): - if order.is_canceled or order.is_executed: - executed_orders.append(order) - else: - active_orders.append(order) - - self.storage[key] = active_orders - if key not in self.executed_storage: - self.executed_storage[key] = [] - self.executed_storage[key] += executed_orders - - + active_orders = [ + order + for order in self.get_active_orders(exchange, symbol) + if not order.is_canceled and not order.is_executed + ] + self.active_storage[key] = active_orders diff --git a/jesse/strategies/Strategy.py b/jesse/strategies/Strategy.py index 12057ce66..a78ef9785 100644 --- a/jesse/strategies/Strategy.py +++ b/jesse/strategies/Strategy.py @@ -1245,9 +1245,7 @@ def orders(self) -> List[Order]: """ Returns all the orders submitted by for this strategy. """ - executed_orders = store.orders.get_executed_orders(self.exchange, self.symbol) - active_orders = store.orders.get_orders(self.exchange, self.symbol) - return sorted(executed_orders + active_orders, key=lambda o: o.created_at) + return store.orders.get_orders(self.exchange, self.symbol) @property def entry_orders(self): From 644cb1ef57b15fa199323d40ebbec3d20257d290 Mon Sep 17 00:00:00 2001 From: Yakir Date: Fri, 24 May 2024 16:40:59 +0300 Subject: [PATCH 07/13] feat(orders): update every function that needs active orders instead of all orders --- jesse/exchanges/sandbox/Sandbox.py | 2 +- jesse/modes/backtest_mode.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jesse/exchanges/sandbox/Sandbox.py b/jesse/exchanges/sandbox/Sandbox.py index 7dcf4f9c3..cccd34bf9 100644 --- a/jesse/exchanges/sandbox/Sandbox.py +++ b/jesse/exchanges/sandbox/Sandbox.py @@ -63,7 +63,7 @@ def stop_order(self, symbol: str, qty: float, price: float, side: str, reduce_on def cancel_all_orders(self, symbol: str) -> None: orders = filter(lambda o: o.is_new, - store.orders.get_orders(self.name, symbol)) + store.orders.get_active_orders(self.name, symbol)) for o in orders: o.cancel() diff --git a/jesse/modes/backtest_mode.py b/jesse/modes/backtest_mode.py index f75352408..32e00fbfa 100644 --- a/jesse/modes/backtest_mode.py +++ b/jesse/modes/backtest_mode.py @@ -398,7 +398,7 @@ def _get_fixed_jumped_candle(previous_candle: np.ndarray, candle: np.ndarray) -> def _simulate_price_change_effect(real_candle: np.ndarray, exchange: str, symbol: str) -> None: - orders = store.orders.get_orders(exchange, symbol) + orders = store.orders.get_active_orders(exchange, symbol) current_temp_candle = real_candle.copy() executed_order = False @@ -771,7 +771,7 @@ def _simulate_price_change_effect_multiple_candles( def _get_executing_orders(exchange, symbol, real_candle): - orders = store.orders.get_orders(exchange, symbol) + orders = store.orders.get_active_orders(exchange, symbol) active_orders = [order for order in orders if order.is_active] return [ order From 69ab4d6feb720b76480cc72c72cd0deed8452354 Mon Sep 17 00:00:00 2001 From: Yakir Date: Sat, 25 May 2024 01:48:36 +0300 Subject: [PATCH 08/13] feat(state-orders): get active orders on entry orders --- jesse/helpers.py | 1 + jesse/store/state_orders.py | 13 +++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/jesse/helpers.py b/jesse/helpers.py index 86fcc8990..9eb668cba 100644 --- a/jesse/helpers.py +++ b/jesse/helpers.py @@ -832,6 +832,7 @@ def today_to_timestamp() -> int: return arrow.utcnow().floor('day').int_timestamp * 1000 +@lru_cache def type_to_side(t: str) -> str: from jesse.enums import trade_types, sides diff --git a/jesse/store/state_orders.py b/jesse/store/state_orders.py index 70b08c208..200c26b1a 100644 --- a/jesse/store/state_orders.py +++ b/jesse/store/state_orders.py @@ -108,17 +108,14 @@ def get_order_by_id(self, exchange: str, symbol: str, id: str, use_exchange_id: return fnc.find(lambda o: o.id == id, reversed(self.storage[key])) def get_entry_orders(self, exchange: str, symbol: str) -> List[Order]: - all_orders = self.get_orders(exchange, symbol) - # return empty if no orders - if len(all_orders) == 0: - return [] # return all orders if position is not opened yet p = selectors.get_position(exchange, symbol) if p.is_close: - entry_orders = all_orders.copy() - else: - p_side = jh.type_to_side(p.type) - entry_orders = [o for o in all_orders if (o.side == p_side and not o.is_canceled)] + return self.get_orders(exchange, symbol).copy() + + all_orders = self.get_active_orders(exchange, symbol) + p_side = jh.type_to_side(p.type) + entry_orders = [o for o in all_orders if (o.side == p_side and not o.is_canceled)] return entry_orders From 5ed48de2b8f63ae8fc0169e537eb8e18c56ca90e Mon Sep 17 00:00:00 2001 From: Yakir Date: Sat, 25 May 2024 14:23:58 +0300 Subject: [PATCH 09/13] feat(order-map): cancel exit_orders only on active orders --- jesse/store/state_orders.py | 20 ++++++++++++++++++++ jesse/strategies/Strategy.py | 11 +++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/jesse/store/state_orders.py b/jesse/store/state_orders.py index 200c26b1a..cf458c309 100644 --- a/jesse/store/state_orders.py +++ b/jesse/store/state_orders.py @@ -139,6 +139,26 @@ def get_exit_orders(self, exchange: str, symbol: str) -> List[Order]: return exit_orders + def get_active_exit_orders(self, exchange: str, symbol: str) -> List[Order]: + """ + excludes cancel orders but includes executed orders + """ + all_orders = self.get_active_orders(exchange, symbol) + # return empty if no orders + if len(all_orders) == 0: + return [] + # return empty if position is not opened yet + p = selectors.get_position(exchange, symbol) + if p.is_close: + return [] + else: + exit_orders = [o for o in all_orders if o.side != jh.type_to_side(p.type)] + + # exclude cancelled orders + exit_orders = [o for o in exit_orders if not o.is_canceled] + + return exit_orders + def update_active_orders(self, exchange: str, symbol: str): key = f'{exchange}-{symbol}' active_orders = [ diff --git a/jesse/strategies/Strategy.py b/jesse/strategies/Strategy.py index a78ef9785..c6e171b28 100644 --- a/jesse/strategies/Strategy.py +++ b/jesse/strategies/Strategy.py @@ -521,7 +521,7 @@ def _detect_and_handle_entry_and_exit_modifications(self) -> None: temp_current_price = None # CANCEL previous orders - for o in self.exit_orders: + for o in self.active_exit_orders: if o.is_take_profit and (o.is_active or o.is_queued): self.broker.cancel_order(o.id) @@ -562,7 +562,7 @@ def _detect_and_handle_entry_and_exit_modifications(self) -> None: temp_current_price = None # CANCEL previous orders - for o in self.exit_orders: + for o in self.active_exit_orders: if o.is_stop_loss and (o.is_active or o.is_queued): self.broker.cancel_order(o.id) @@ -1261,6 +1261,13 @@ def exit_orders(self): """ return store.orders.get_exit_orders(self.exchange, self.symbol) + @property + def active_exit_orders(self): + """ + Returns all the exit orders for this position. + """ + return store.orders.get_active_exit_orders(self.exchange, self.symbol) + @property def exchange_type(self): return selectors.get_exchange(self.exchange).type From f07587e4c2ea3539baa971847588e0c40f3e7823 Mon Sep 17 00:00:00 2001 From: Yakir Date: Sat, 8 Jun 2024 15:11:33 +0300 Subject: [PATCH 10/13] feat(store.orders): better sort heuristic on what order the orders should execute. --- jesse/helpers.py | 2 +- jesse/modes/backtest_mode.py | 52 ++++++++++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/jesse/helpers.py b/jesse/helpers.py index 9eb668cba..066529ce2 100644 --- a/jesse/helpers.py +++ b/jesse/helpers.py @@ -1052,5 +1052,5 @@ def is_price_near(order_price, price_to_compare, percentage_threshold=0.0001): We calculate percentage difference between the two prices rounded to 4 decimal places, so low-priced orders can be properly compared within 0.01% range. """ - return round(abs(1 - (order_price / price_to_compare)), 4) <= percentage_threshold + return abs(1 - (order_price / price_to_compare)) <= percentage_threshold diff --git a/jesse/modes/backtest_mode.py b/jesse/modes/backtest_mode.py index b2957dcab..a6f808df6 100644 --- a/jesse/modes/backtest_mode.py +++ b/jesse/modes/backtest_mode.py @@ -431,6 +431,8 @@ def _simulate_price_change_effect(real_candle: np.ndarray, exchange: str, symbol order.execute() executing_orders = _get_executing_orders(exchange, symbol, real_candle) + if len(executing_orders) > 1: + executing_orders = _sort_execution_orders(executing_orders, current_temp_candle[None, :]) # break from the for loop, we'll try again inside the while # loop with the new current_temp_candle @@ -742,6 +744,8 @@ def _simulate_price_change_effect_multiple_candles( executing_orders = _get_executing_orders( exchange, symbol, real_candle ) + if len(executing_orders) > 1: + executing_orders = _sort_execution_orders(executing_orders, short_timeframes_candles) # break from the for loop, we'll try again inside the while # loop with the new current_temp_candle @@ -787,8 +791,9 @@ def _get_executing_orders(exchange, symbol, real_candle): ] -def _sort_execution_orders(orders: List[Order], short_candles: np.ndarray): +def _sort_execution_orders(orders: List[Order], short_candles: np.ndarray) -> list[Order]: sorted_orders = [] + n_orders = len(orders) for i in range(len(short_candles)): included_orders = [ order @@ -796,16 +801,47 @@ def _sort_execution_orders(orders: List[Order], short_candles: np.ndarray): if candle_includes_price(short_candles[i], order.price) ] if len(included_orders) == 1: + included_price = included_orders[0].price sorted_orders.append(included_orders[0]) + orders = [o for o in orders if o.price != included_price] elif len(included_orders) > 1: # in case that the orders are above - # note: check the first is enough because I can assume all the orders in the same direction of the price, - # in case it doesn't than i cant really know how the price react in this 1 minute candle.. - if short_candles[i, 3] > included_orders[0].price > short_candles[i, 1]: + # the activation of the order is probably because we meet a new low or high, + # check what is the new price that we reach + # i do it by look backward when was the first time that i didnt find this price + is_determined_direction = False + for j in range(i -1, -1, -1): + is_new_high = short_candles[i, 3] > short_candles[j, 4] + is_new_low = short_candles[i, 4] < short_candles[j, 4] + if is_new_low and is_new_high: + # we didnt find what is so special in this candle, maybe previous candle + continue + if is_new_high: + # found new high, we came from below. sort it from lower to higher prices + sorted_orders += sorted(included_orders, key=lambda o: o.price) + is_determined_direction = True + + elif is_new_low: + # found new low, we came from above. sort it from higher to lower prices + sorted_orders += sorted(included_orders, key=lambda o: o.price, reverse=True) + is_determined_direction = True + + # in case we didnt find new high and new low, thats means the orders are new! + # we cant determine what is the real order by its prices + + # todo: sort the order in these cases by position side! + # if its long, buy orders sort from higher to lower, if its take-profit than from lower to higher. + # opposite on short + # if the type of the orders is different than better heuristic needs to be made.. like how the price move until now and from now on.. + # these casesare so rare that i leave it for now to sort it from low to high because crypto tend to go up so better chances it will be correct + break + + if not is_determined_direction: sorted_orders += sorted(included_orders, key=lambda o: o.price) - else: - sorted_orders += sorted(included_orders, key=lambda o: o.price, reverse=True) - if len(sorted_orders) == len(orders): + + included_prices = set([o.price for o in included_orders]) + orders = [o for o in orders if o.price not in included_prices] + if len(sorted_orders) == n_orders: break - return sorted_orders \ No newline at end of file + return sorted_orders From 96b2b20a3d7dedad79c666b27ab4b4412facc452 Mon Sep 17 00:00:00 2001 From: morteza-koohgard Date: Sat, 15 Jun 2024 17:26:39 +0330 Subject: [PATCH 11/13] 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 d1d105f45da31a9aba96439a2369962a80db59e2 Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Wed, 26 Jun 2024 23:49:50 +0330 Subject: [PATCH 12/13] 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 8672ba750d6bf8645e85cf9a289345ef9adb19fe Mon Sep 17 00:00:00 2001 From: Saleh Mir Date: Thu, 27 Jun 2024 13:26:13 +0330 Subject: [PATCH 13/13] fix is_price_near to pass new testrs --- jesse/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jesse/helpers.py b/jesse/helpers.py index 066529ce2..9eb668cba 100644 --- a/jesse/helpers.py +++ b/jesse/helpers.py @@ -1052,5 +1052,5 @@ def is_price_near(order_price, price_to_compare, percentage_threshold=0.0001): We calculate percentage difference between the two prices rounded to 4 decimal places, so low-priced orders can be properly compared within 0.01% range. """ - return abs(1 - (order_price / price_to_compare)) <= percentage_threshold + return round(abs(1 - (order_price / price_to_compare)), 4) <= percentage_threshold