bitunix-automated-crypto-trading 3.1.3__py3-none-any.whl → 3.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -111,7 +111,7 @@ class BitunixSignal:
111
111
  newlist=olist+plist+list(set(symbols))
112
112
  self.tickerList=newlist[:300]
113
113
  self.tickerList.remove("STMXUSDT")
114
- #self.tickerList=['PIXELUSDT']
114
+ #self.tickerList=['POLUSDT']
115
115
 
116
116
  [await self.add_ticker_to_tickerObjects(sym) for sym in self.tickerList]
117
117
  self.notifications.add_notification(f"{len(self.tickerList)} ticker list loaded")
@@ -271,12 +271,7 @@ class BitunixSignal:
271
271
  # Function to add data to the last price deque
272
272
  async def StoreTickerData(self, message):
273
273
  if self.settings.USE_PUBLIC_WEBSOCKET and message:
274
- try:
275
- data = json.loads(message)
276
- if data.get('symbol') and data.get('ch') == 'ticker':
277
- await self.ticker_que.put(data)
278
- except json.JSONDecodeError as e:
279
- logger.warning(f"Failed to decode message: {message}. Error: {e}")
274
+ await self.ticker_que.put(message)
280
275
 
281
276
  # Function to process the last price deque
282
277
  async def ProcessTickerData(self):
@@ -285,11 +280,14 @@ class BitunixSignal:
285
280
  latest_data = {}
286
281
  reversed_items = await self.drain_queue(self.ticker_que)
287
282
  while reversed_items:
288
- data = reversed_items.popleft()
289
- symbol = data["symbol"]
290
- ts = data["ts"]
291
- if symbol not in latest_data or ts > latest_data[symbol]['ts']:
292
- latest_data[symbol] = {'ts': ts, 'last': float(data['data']['la'])}
283
+ message = reversed_items.popleft()
284
+ data = json.loads(message)
285
+ if data.get('symbol') and data.get('ch') == 'ticker':
286
+ symbol = data["symbol"]
287
+ ts = data["ts"]
288
+ if symbol not in latest_data or ts > latest_data[symbol]['ts']:
289
+ latest_data[symbol] = {'ts': ts, 'last': float(data['data']['la'])}
290
+ await asyncio.sleep(0.01)
293
291
  # Convert to DataFrame
294
292
  self.tickerdf = pd.DataFrame.from_dict(latest_data, orient="index")
295
293
  if not self.tickerdf.empty:
@@ -300,7 +298,7 @@ class BitunixSignal:
300
298
  except Exception as e:
301
299
  logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
302
300
  logger.info(traceback.print_exc())
303
- await asyncio.sleep(0.5)
301
+ await asyncio.sleep(0.01)
304
302
  logger.info(f"ProcessTickerData: exitied out of the loop, exiting app")
305
303
  os._exit(1) # Exit the program
306
304
 
@@ -308,13 +306,8 @@ class BitunixSignal:
308
306
  #websocket data to update bid and ask
309
307
  async def StoreDepthData(self, message):
310
308
  if self.settings.USE_PUBLIC_WEBSOCKET and message:
311
- try:
312
- data = json.loads(message)
313
- if data.get('symbol') and data.get('ch') == 'depth_book1':
314
- await self.depth_que.put(data)
315
- except json.JSONDecodeError as e:
316
- logger.warning(f"Failed to decode message: {message}. Error: {e}")
317
-
309
+ await self.depth_que.put(message)
310
+
318
311
  # Function to process the bid, ask
319
312
  async def ProcessDepthData(self):
320
313
  while True:
@@ -322,11 +315,14 @@ class BitunixSignal:
322
315
  latest_data = {}
323
316
  reversed_items = await self.drain_queue(self.depth_que)
324
317
  while reversed_items:
325
- data = reversed_items.popleft()
326
- symbol = data["symbol"]
327
- ts = data["ts"]
328
- if symbol not in latest_data or ts > latest_data[symbol]['ts']:
329
- latest_data[symbol] = {'ts': ts, 'bid': float(data['data']['b'][0][0]), 'ask': float(data['data']['a'][0][0])}
318
+ message = reversed_items.popleft()
319
+ data = json.loads(message)
320
+ if data.get('symbol') and data.get('ch') == 'depth_book1':
321
+ symbol = data["symbol"]
322
+ ts = data["ts"]
323
+ if symbol not in latest_data or ts > latest_data[symbol]['ts']:
324
+ latest_data[symbol] = {'ts': ts, 'bid': float(data['data']['b'][0][0]), 'ask': float(data['data']['a'][0][0])}
325
+ await asyncio.sleep(0.01)
330
326
  # Convert to DataFrame
331
327
  self.depthdf = pd.DataFrame.from_dict(latest_data, orient="index")
332
328
  if not self.depthdf.empty:
@@ -335,7 +331,7 @@ class BitunixSignal:
335
331
  except Exception as e:
336
332
  logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
337
333
  logger.info(traceback.print_exc())
338
- await asyncio.sleep(0.5)
334
+ await asyncio.sleep(0.01)
339
335
  logger.info(f"ProcessDepthData: exitied out of the loop, exiting app")
340
336
  os._exit(1) # Exit the program
341
337
 
@@ -563,12 +559,12 @@ class BitunixSignal:
563
559
  self.signaldf_filtered = self.tickerObjects.signaldf_filtered
564
560
 
565
561
  if not self.positiondf.empty and not self.signaldf_full.empty:
566
- columns=['symbol', f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_candle_trend", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low"]
562
+ columns=['symbol', f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_trendline", f"{period}_candle_trend", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low"]
567
563
  columns2=["qty", "side", "unrealizedPNL", "realizedPNL", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
568
564
  if set(columns).issubset(self.signaldf_full.columns) and set(columns2).issubset(self.positiondf.columns):
569
- columnOrder= ['symbol', "side", "unrealizedPNL", "realizedPNL", f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_adx", f"{period}_candle_trend", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low", "qty", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
565
+ columnOrder= ['symbol', "side", "unrealizedPNL", "realizedPNL", f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_trendline", f"{period}_adx", f"{period}_candle_trend", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low", "qty", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
570
566
  self.positiondf2 = pd.merge(self.positiondf, self.signaldf_full[["symbol", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
571
- f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_adx", f"{period}_candle_trend",
567
+ f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_trendline", f"{period}_adx", f"{period}_candle_trend",
572
568
  f"{period}_trend",f"{period}_cb", f"{period}_barcolor"]], left_on="symbol", right_index=True, how="left")[columnOrder]
573
569
  self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf2)
574
570
  else:
@@ -582,7 +578,7 @@ class BitunixSignal:
582
578
  # Assign to self.signaldf for HTML rendering
583
579
  self.signaldf = self.signaldf_filtered[[
584
580
  "symbol", f"{period}_trend",f"{period}_cb", f"{period}_barcolor",
585
- f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi",f"{period}_adx",f"{period}_candle_trend",
581
+ f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_trendline",f"{period}_adx",f"{period}_candle_trend",
586
582
  'lastcolor', 'bidcolor', 'askcolor', 'bid', 'last', 'ask',
587
583
  f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
588
584
  ]].sort_values(by=[f'{period}_cb'], ascending=[False])
@@ -693,7 +689,7 @@ class BitunixSignal:
693
689
  total_pnl = unrealized_pnl + realized_pnl
694
690
  side=row['side']
695
691
 
696
- requiredCols=[f'{period}_open', f'{period}_close', f'{period}_high', f'{period}_low', f'{period}_ema_open', f"{period}_ema_close", f'{period}_macd', f'{period}_bbm', f'{period}_rsi', f'{period}_candle_trend', f'{period}_trend', f'{period}_cb', f'{period}_barcolor']
692
+ requiredCols=[f'{period}_open', f'{period}_close', f'{period}_high', f'{period}_low', f'{period}_ema_open', f"{period}_ema_close", f'{period}_macd', f'{period}_bbm', f'{period}_rsi', f'{period}_trendline', f'{period}_candle_trend', f'{period}_trend', f'{period}_cb', f'{period}_barcolor']
697
693
  required_cols = set(requiredCols)
698
694
 
699
695
  # Close position that fall the below criteria
@@ -879,6 +875,41 @@ class BitunixSignal:
879
875
  )
880
876
  continue
881
877
 
878
+ # TrendLine
879
+ if self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_CLOSE:
880
+ if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_trendline'] == "SELL" and total_pnl>0:
881
+ last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
882
+ price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
883
+
884
+ self.notifications.add_notification(
885
+ f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to {period} trendline for {row.symbol} with {row.qty} qty @ {price})'
886
+ )
887
+ datajs = await self.bitunixApi.PlaceOrder(
888
+ positionId=row.positionId,
889
+ ticker=row.symbol,
890
+ qty=row.qty,
891
+ price=price,
892
+ side=row.side,
893
+ tradeSide="CLOSE"
894
+ )
895
+ continue
896
+
897
+ if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_trendline'] == "BUY" and total_pnl>0:
898
+ last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
899
+ price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
900
+ self.notifications.add_notification(
901
+ f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to {period} trendline for {row.symbol} with {row.qty} qty @ {price})'
902
+ )
903
+ datajs = await self.bitunixApi.PlaceOrder(
904
+ positionId=row.positionId,
905
+ ticker=row.symbol,
906
+ qty=row.qty,
907
+ price=price,
908
+ side=row.side,
909
+ tradeSide="CLOSE"
910
+ )
911
+ continue
912
+
882
913
  # Close on weak trend after open
883
914
  if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_CLOSE:
884
915
  if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_adx'] == "WEAK":
@@ -0,0 +1,89 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ from scipy.signal import find_peaks
4
+
5
+ class SupportResistance:
6
+ import pandas as pd
7
+ import numpy as np
8
+
9
+ class SupportResistance:
10
+ def _find_peaks(self, prices, prominence=None, distance=None):
11
+ """Finds local peaks using scipy.signal.find_peaks."""
12
+ peaks, _ = find_peaks(prices, prominence=prominence, distance=distance)
13
+ return peaks
14
+
15
+ def _find_troughs(self, prices, prominence=None, distance=None):
16
+ """Finds local troughs by finding peaks in the inverted prices."""
17
+ troughs, _ = find_peaks(-prices, prominence=prominence, distance=distance)
18
+ return troughs
19
+
20
+ def _get_n_most_recent_prominent(self, indices, prices, n=2):
21
+ """Gets the indices of the n most recent prominent peaks or troughs."""
22
+ if not indices.size:
23
+ return np.array([])
24
+ # Sort indices by time (which is the index itself in our case) in descending order
25
+ sorted_indices_by_time = np.sort(indices)[::-1]
26
+ return sorted_indices_by_time[:n]
27
+
28
+ def _get_line(self, index1, price1, index2, price2, length):
29
+ """Calculates the line equation (y = mx + c)."""
30
+ if index1 == index2:
31
+ return np.full(length, price1) # Horizontal line
32
+ slope = (price2 - price1) / (index2 - index1)
33
+ intercept = price1 - slope * index1
34
+ return slope * np.arange(length) + intercept
35
+
36
+ def support_resistance_trend_lines(self, data, lookback=20, prominence=None, distance=None):
37
+ if data is None or len(data) < lookback:
38
+ return pd.DataFrame()
39
+ try:
40
+ recent_data = data.iloc[-lookback:].copy()
41
+ high_prices = recent_data['high'].values
42
+ low_prices = recent_data['low'].values
43
+ ts = recent_data['time'].values
44
+
45
+ # Find all local peaks and troughs
46
+ peak_indices_all = self._find_peaks(high_prices, prominence=prominence, distance=distance)
47
+ if len(peak_indices_all) > 0 and peak_indices_all[-1] < lookback - 2 and high_prices[-2] > high_prices[lookback - peak_indices_all[-1]]:
48
+ peak_indices_all = np.append(peak_indices_all,lookback - 2)
49
+ trough_indices_all = self._find_troughs(low_prices, prominence=prominence, distance=distance)
50
+ if len(trough_indices_all) > 0 and trough_indices_all[-1] < lookback - 2 and low_prices[-2] < low_prices[lookback - trough_indices_all[-1]]:
51
+ trough_indices_all = np.append(trough_indices_all, lookback - 2)
52
+ # Get the two most recent prominent peaks
53
+ most_recent_peaks_indices = self._get_n_most_recent_prominent(peak_indices_all, high_prices, n=2)
54
+
55
+ # Get the two most recent prominent troughs
56
+ most_recent_troughs_indices = self._get_n_most_recent_prominent(trough_indices_all, low_prices, n=2)
57
+
58
+ support_line = np.full(lookback, np.nan)
59
+ resistance_line = np.full(lookback, np.nan)
60
+
61
+ if len(most_recent_peaks_indices) >= 2:
62
+ # Sort by index (time) to ensure correct order
63
+ sorted_peak_indices = most_recent_peaks_indices #np.sort(most_recent_peaks_indices)
64
+ resistance_line_full = self._get_line(sorted_peak_indices[0], high_prices[sorted_peak_indices[0]],
65
+ sorted_peak_indices[1], high_prices[sorted_peak_indices[1]], lookback)
66
+ # Set values before the second peak to NaN
67
+ resistance_line[sorted_peak_indices[1]:] = resistance_line_full[sorted_peak_indices[1]:]
68
+ elif len(most_recent_peaks_indices) == 1:
69
+ resistance_line[most_recent_peaks_indices[0]:] = high_prices[most_recent_peaks_indices[0]] # Horizontal line from the peak onwards
70
+
71
+ if len(most_recent_troughs_indices) >= 2:
72
+ # Sort by index (time) to ensure correct order
73
+ sorted_trough_indices = most_recent_troughs_indices #np.sort(most_recent_troughs_indices)
74
+ support_line_full = self._get_line(sorted_trough_indices[0], low_prices[sorted_trough_indices[0]],
75
+ sorted_trough_indices[1], low_prices[sorted_trough_indices[1]], lookback)
76
+ # Set values before the second trough to NaN
77
+ support_line[sorted_trough_indices[1]:] = support_line_full[sorted_trough_indices[1]:]
78
+ elif len(most_recent_troughs_indices) == 1:
79
+ support_line[most_recent_troughs_indices[0]:] = low_prices[most_recent_troughs_indices[0]] # Horizontal line from the trough onwards
80
+
81
+ results_df = pd.DataFrame({
82
+ 'time': ts,
83
+ 'support_line': support_line,
84
+ 'resistance_line': resistance_line
85
+ })
86
+ return results_df
87
+ except Exception as e:
88
+ print(f"An error occurred: {e}")
89
+ return pd.DataFrame()
@@ -1,5 +1,7 @@
1
1
  import pandas as pd
2
- #pd.set_option('future.no_silent_downcasting', True)
2
+ pd.set_option('future.no_silent_downcasting', True)
3
+ import warnings
4
+ warnings.filterwarnings("ignore", category=RuntimeWarning)
3
5
  import numpy as np
4
6
  import asyncio
5
7
  import talib
@@ -8,6 +10,8 @@ from logger import Logger
8
10
  logger = Logger(__name__).get_logger()
9
11
  import gc
10
12
  from concurrent.futures import ProcessPoolExecutor
13
+ from SupportResistance import SupportResistance
14
+ sr = SupportResistance()
11
15
 
12
16
  class Interval:
13
17
  def __init__(self, symbol, intervalId, delta, data, settings):
@@ -21,6 +25,7 @@ class Interval:
21
25
  self.current_signal="HOLD"
22
26
  self.ema_open_signal="HOLD"
23
27
  self.ema_close_signal="HOLD"
28
+ self.trendline_signal = "HOLD"
24
29
  self.macd_signal="HOLD"
25
30
  self.bbm_signal="HOLD"
26
31
  self.rsi_signal="HOLD"
@@ -39,51 +44,13 @@ class Interval:
39
44
  study = self.calculate_study(new_value)
40
45
  self._data = study
41
46
 
42
- def support_resistance_trend_lines(self, data, lookback=20):
43
- recent_data = data[-lookback:]
44
- high_prices = recent_data['high'].values
45
- low_prices = recent_data['low'].values
46
- candle_indices = np.arange(lookback)
47
- ts = recent_data['time']
48
-
49
- # 1. Identify highest high and lowest low candle
50
- highest_high_index = np.argmax(high_prices)
51
- lowest_low_index = np.argmin(low_prices)
52
- highest_high_price = high_prices[highest_high_index]
53
- lowest_low_price = low_prices[lowest_low_index]
54
-
55
- # 2. Top line: Tilt down from highest high to touch a subsequent high (if any)
56
- slope_top = -float('inf') # Initialize with a very steep negative slope
57
- intercept_top = highest_high_price - slope_top * highest_high_index
58
-
59
- for i in range(highest_high_index + 1, lookback):
60
- current_slope = (high_prices[i] - highest_high_price) / (i - highest_high_index) if (i - highest_high_index) != 0 else 0
61
- if current_slope > slope_top:
62
- slope_top = current_slope
63
- intercept_top = highest_high_price - slope_top * highest_high_index
64
-
65
- top_line = slope_top * candle_indices + intercept_top
66
-
67
- # 3. Bottom line: Tilt up from lowest low to touch a subsequent low (if any)
68
- slope_bottom = float('inf') # Initialize with a very steep positive slope
69
- intercept_bottom = lowest_low_price - slope_bottom * lowest_low_index
70
-
71
- for i in range(lowest_low_index + 1, lookback):
72
- current_slope = (low_prices[i] - lowest_low_price) / (i - lowest_low_index) if (i - lowest_low_index) != 0 else 0
73
- if current_slope < slope_bottom:
74
- slope_bottom = current_slope
75
- intercept_bottom = lowest_low_price - slope_bottom * lowest_low_index
76
-
77
- bottom_line = slope_bottom * candle_indices + intercept_bottom
78
-
79
- results_df = pd.DataFrame({
80
- 'time': ts,
81
- 'top_line': top_line,
82
- 'bottom_line': bottom_line
83
- })
84
-
85
- return results_df
86
-
47
+ def get_line(self, lookback, candle_indices, index1, price1, index2, price2):
48
+ if index1 == index2:
49
+ return np.full(lookback, price1) # Horizontal line
50
+ slope = (price2 - price1) / (index2 - index1)
51
+ intercept = price1 - slope * index1
52
+ return slope * candle_indices + intercept
53
+
87
54
  def calculate_study(self, new_value):
88
55
  df = pd.DataFrame(new_value)
89
56
  if not df.empty: #and df.shape[0] >= int(self.settings.BARS):
@@ -258,14 +225,24 @@ class Interval:
258
225
 
259
226
  #Trendline
260
227
  if self.settings.TRENDLINE_STUDY:
261
- lookback = self.settings.TRENDLINE_LOOKBACK
262
- trend_df = self.support_resistance_trend_lines(df, lookback)
263
-
264
- df['support_line'] = trend_df['top_line']
265
- df.fillna({'support_line':0}, inplace=True)
266
-
267
- df['resistance_line'] = trend_df['bottom_line']
268
- df.fillna({'resistance_line':0}, inplace=True)
228
+ loopback = len(df)
229
+ df_sr= sr.support_resistance_trend_lines(df[['time','open', 'high', 'low', 'close']], loopback, prominence=None, distance=None if self.settings.TRENDLINE_PEAK_DISTANCE==0 else self.settings.TRENDLINE_PEAK_DISTANCE)
230
+ if df_sr is not None and len(df_sr) >= 1:
231
+ df['support_line'] = df_sr['support_line']
232
+ df['resistance_line'] = df_sr['resistance_line']
233
+ df.fillna({'support_line':0}, inplace=True)
234
+ df.fillna({'resistance_line':0}, inplace=True)
235
+ if df is not None and len(df) >= 2:
236
+ if df['high'].iloc[-3] >= df['support_line'].iloc[-3] and df['close'].iloc[-2] < df['support_line'].iloc[-2] and df['close'].iloc[-1] < df['open'].iloc[-1]:
237
+ self.trendline_signal = "SELL"
238
+ elif df['low'].iloc[-3] <= df['resistance_line'].iloc[-3] and df['close'].iloc[-2] > df['resistance_line'].iloc[-2] and df['close'].iloc[-1] > df['open'].iloc[-1]:
239
+ self.trendline_signal = "BUY"
240
+ elif df['close'].iloc[-3] > df['support_line'].iloc[-3] and df['close'].iloc[-2] > df['support_line'].iloc[-2] and df['close'].iloc[-1] > df['open'].iloc[-1]:
241
+ self.trendline_signal = "BUY"
242
+ elif df['close'].iloc[-3] < df['resistance_line'].iloc[-3] and df['close'].iloc[-2] < df['resistance_line'].iloc[-2] and df['close'].iloc[-1] < df['open'].iloc[-1]:
243
+ self.trendline_signal = "SELL"
244
+ else:
245
+ self.trendline_signal = "HOLD"
269
246
 
270
247
  # Calculate the ADX
271
248
  if self.settings.ADX_STUDY:
@@ -314,7 +291,8 @@ class Interval:
314
291
  (self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY") or
315
292
  (self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY") or
316
293
  (self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY") or
317
- (self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY")
294
+ (self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY") or
295
+ (self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "BUY")
318
296
  )
319
297
  additional_buy_conditions = (
320
298
  (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") or
@@ -326,7 +304,8 @@ class Interval:
326
304
  (self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL") or
327
305
  (self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "SELL") or
328
306
  (self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "SELL") or
329
- (self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "SELL")
307
+ (self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "SELL") or
308
+ (self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "SELL")
330
309
  )
331
310
  additional_sell_conditions = (
332
311
  (self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") or
@@ -349,7 +328,8 @@ class Interval:
349
328
  (not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_open_signal == "BUY") and
350
329
  (not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "BUY") and
351
330
  (not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "BUY") and
352
- (not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "BUY")
331
+ (not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "BUY") and
332
+ (not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_OPEN or self.trendline_signal == "BUY")
353
333
  )
354
334
  additional_buy_conditions = (
355
335
  (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
@@ -361,7 +341,8 @@ class Interval:
361
341
  (not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_open_signal == "SELL") and
362
342
  (not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "SELL") and
363
343
  (not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "SELL") and
364
- (not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "SELL")
344
+ (not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "SELL") and
345
+ (not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_OPEN or self.trendline_signal == "SELL")
365
346
  )
366
347
  additional_sell_conditions = (
367
348
  (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
@@ -379,7 +360,7 @@ class Interval:
379
360
 
380
361
  except Exception as e:
381
362
  logger.info(f"Function: calculate_study, {e}, {e.args}, {type(e).__name__}")
382
- logger.info(traceback.logger.info_exc())
363
+ logger.info(traceback.print_exc())
383
364
  retval = df.to_dict('records')
384
365
  del df
385
366
  gc.collect()
@@ -531,7 +512,7 @@ class Ticker:
531
512
 
532
513
  except Exception as e:
533
514
  logger.info(f"Function: create_bar_with_last_and_ts, {e}, {e.args}, {type(e).__name__}")
534
- logger.info(traceback.logger.info_exc())
515
+ logger.info(traceback.print_exc())
535
516
 
536
517
 
537
518
  class Tickers:
@@ -639,7 +620,7 @@ class Tickers:
639
620
  results = executor.map(self.process_ticker_candle, tuples_list, chunksize=10)
640
621
  for args, result in zip(tuples_list, results):
641
622
  for intervalId, interval in result[3].items():
642
- if not interval._data is None:
623
+ if not interval._data is None and len(interval._data) >= self.settings.BARS:
643
624
  args[0].get_interval_ticks(intervalId)._data = interval._data
644
625
  args[0].get_interval_ticks(intervalId).current_signal = interval.current_signal
645
626
  args[0].get_interval_ticks(intervalId).ema_open_signal = interval.ema_open_signal
@@ -647,6 +628,7 @@ class Tickers:
647
628
  args[0].get_interval_ticks(intervalId).macd_signal = interval.macd_signal
648
629
  args[0].get_interval_ticks(intervalId).bbm_signal = interval.bbm_signal
649
630
  args[0].get_interval_ticks(intervalId).rsi_signal = interval.rsi_signal
631
+ args[0].get_interval_ticks(intervalId).trendline_signal, = interval.trendline_signal,
650
632
  args[0].get_interval_ticks(intervalId).candle_trend = interval.candle_trend
651
633
  args[0].get_interval_ticks(intervalId).adx_signal = interval.adx_signal
652
634
  args[0].get_interval_ticks(intervalId).signal_strength = interval.signal_strength
@@ -683,6 +665,7 @@ class Tickers:
683
665
  f"{period}_macd":intervalObj.macd_signal,
684
666
  f"{period}_bbm":intervalObj.bbm_signal,
685
667
  f"{period}_rsi":intervalObj.rsi_signal,
668
+ f"{period}_trendline": intervalObj.trendline_signal,
686
669
  f"{period}_adx":intervalObj.adx_signal,
687
670
  f"{period}_candle_trend":intervalObj.candle_trend,
688
671
  'bid' : bid,
@@ -704,7 +687,7 @@ class Tickers:
704
687
  'lastcolor': "", 'bidcolor': "", 'askcolor': "", 'bid': 0.0, 'ask': 0.0, 'last': 0.0,
705
688
  f"{period}_cb":0, f"{period}_barcolor": "", f"{period}_trend": "",
706
689
  f"{period}_open": 0.0, f"{period}_close": 0.0, f"{period}_high": 0.0, f"{period}_low": 0.0,
707
- f"{period}_ema": "", f"{period}_macd": "", f"{period}_bbm": "", f"{period}_rsi": "", f"{period}_candle_trend": "", f"{period}_adx": ""
690
+ f"{period}_ema": "", f"{period}_macd": "", f"{period}_bbm": "", f"{period}_rsi": "", f"{period}_trendline": "", f"{period}_candle_trend": "", f"{period}_adx": ""
708
691
  }
709
692
  df.fillna(fill_values, inplace=True)
710
693
  df.set_index("symbol", inplace=True, drop=False)
@@ -713,19 +696,21 @@ class Tickers:
713
696
  trending_conditions = [
714
697
  (
715
698
  (df[f'{period}_trend']=='BUY') &
716
- (df[f'{period}_cb']>1) &
717
- (df[f'{period}_barcolor']==self.green)
699
+ (df[f'{period}_cb']>1)
700
+ #&
701
+ #(df[f'{period}_barcolor']==self.green)
718
702
  ),
719
703
  (
720
704
  (df[f'{period}_trend']=='SELL') &
721
- (df[f'{period}_cb']>1) &
722
- (df[f'{period}_barcolor']==self.red)
705
+ (df[f'{period}_cb']>1)
706
+ #&
707
+ #(df[f'{period}_barcolor']==self.red)
723
708
  )
724
709
  ]
725
710
  self.signaldf_filtered = df[np.any(trending_conditions, axis=0)].copy()
726
711
  except Exception as e:
727
712
  logger.info(f"Function: getCurrentData, {e}, {e.args}, {type(e).__name__}")
728
- logger.info(traceback.logger.info_exc())
713
+ logger.info(traceback.print_exc())
729
714
  finally:
730
715
  del df, current_data
731
716
  gc.collect()
@@ -61,7 +61,8 @@ class Settings(BaseSettings):
61
61
 
62
62
  TRENDLINE_CHART: bool = Field(default=True)
63
63
  TRENDLINE_STUDY: bool = Field(default=True)
64
- TRENDLINE_LOOKBACK: int = Field(default=20, ge=10, le=100)
64
+ TRENDLINE_PEAK_DISTANCE: int = Field(default=1, ge=0, le=30)
65
+ TRENDLINE_LOOKBACK: int = Field(default=20, ge=10, le=200)
65
66
  TRENDLINE_CHECK_ON_OPEN: bool = Field(default=False)
66
67
  TRENDLINE_CHECK_ON_CLOSE: bool = Field(default=False)
67
68
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bitunix_automated_crypto_trading
3
- Version: 3.1.3
3
+ Version: 3.1.5
4
4
  Summary: Bitunix Futures Auto Trading Platform
5
5
  Home-page: https://github.com/tcj2001/bitunix-automated-crypto-trading
6
6
  Author: tcj2001
@@ -1,17 +1,18 @@
1
1
  bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=LJ6ny1KSCVoIbfeNypsY4aHYcbkGME9M3epQ9e8B1O8,3224
2
2
  bitunix_automated_crypto_trading/BitunixApi.py,sha256=W0uem1wIs1uM-jaGtXXzL_JQfnCDb7imyTZ2Tqjk8e8,11230
3
- bitunix_automated_crypto_trading/BitunixSignal.py,sha256=17_VPwagjax-vFJ-A20cMApcyBtPCGdzis8EhAVoNrk,64734
3
+ bitunix_automated_crypto_trading/BitunixSignal.py,sha256=TOseCeTNpCwVcReXRvHoq1sQybhoiIkFvATbu05wQ8c,67215
4
4
  bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=mbuvk8UFWKgv4KLV07TgLgxLVTRJnOKuf02mLB-VoCY,11143
5
5
  bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
6
6
  bitunix_automated_crypto_trading/NotificationManager.py,sha256=pqDquEe-oujD2v8B543524vo62aRMjfB4YW-3DMhVGQ,795
7
+ bitunix_automated_crypto_trading/SupportResistance.py,sha256=Dx4uBC4On-GO1t2EFk02jzSmZzaMD48mUJI9rG2rgMo,4946
7
8
  bitunix_automated_crypto_trading/ThreadManager.py,sha256=WmYRQu8aNrgp5HwSqpBqX1WESNSEpbD2CznPFpd9HuI,2330
8
- bitunix_automated_crypto_trading/TickerManager.py,sha256=W4UuW1RyU_4w_6IV0eDOlJ0PUFNorQOY9Ust4bI-69s,36785
9
+ bitunix_automated_crypto_trading/TickerManager.py,sha256=JTlXUL9GadRjloK6Gw2HhacjvIe31CiiIFl1KGkWztA,37444
9
10
  bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
10
11
  bitunix_automated_crypto_trading/bitunix.py,sha256=Zyrf8P-xi7DJogX_bcc6uDW66J7FAxdS-0jbuHRPFfM,25845
11
- bitunix_automated_crypto_trading/config.py,sha256=Jj1AXFU9c3RHKRnxnAS7lJ6UIyfl6qRByoVbOVrEz1k,4857
12
+ bitunix_automated_crypto_trading/config.py,sha256=zeNmV1KK8Yv5cHzBxaly0lmcjwSZEBAMUm_OvFSBCK4,4922
12
13
  bitunix_automated_crypto_trading/logger.py,sha256=tAaTQcv5r--zupk_Fhfe1USfBAzSiXzVa4QRnaOFIFY,2933
13
- bitunix_automated_crypto_trading-3.1.3.dist-info/METADATA,sha256=UoUpv4RK83MLzcKnh8A6R3p0wltVYqLTQEpR6_ZzzCY,996
14
- bitunix_automated_crypto_trading-3.1.3.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
15
- bitunix_automated_crypto_trading-3.1.3.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
16
- bitunix_automated_crypto_trading-3.1.3.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
17
- bitunix_automated_crypto_trading-3.1.3.dist-info/RECORD,,
14
+ bitunix_automated_crypto_trading-3.1.5.dist-info/METADATA,sha256=YJdYd5cjlCI7XA-Ef9WMVDCnblI22sSxyX--HmMcK-Q,996
15
+ bitunix_automated_crypto_trading-3.1.5.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
16
+ bitunix_automated_crypto_trading-3.1.5.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
17
+ bitunix_automated_crypto_trading-3.1.5.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
18
+ bitunix_automated_crypto_trading-3.1.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.0)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5