bitunix-automated-crypto-trading 3.1.2__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.
@@ -258,6 +258,8 @@ class BitunixApi:
258
258
  url = f'{self.kline_Url}?symbol={ticker}&startTime={st}&interval={interval}&limit={lm}'
259
259
  resp = self.session.get(url)
260
260
  datajs = resp.json()
261
+ if datajs['data'] == []:
262
+ break
261
263
  data.extend(datajs['data'])
262
264
  if len(datajs['data']) < lm:
263
265
  st = int(datajs['data'][-1]['time']) + 1
@@ -110,7 +110,8 @@ class BitunixSignal:
110
110
  olist = [entry['symbol'] for entry in self.pendingOrders['orderList']]
111
111
  newlist=olist+plist+list(set(symbols))
112
112
  self.tickerList=newlist[:300]
113
- #self.tickerList=['IMXUSDT']
113
+ self.tickerList.remove("STMXUSDT")
114
+ #self.tickerList=['POLUSDT']
114
115
 
115
116
  [await self.add_ticker_to_tickerObjects(sym) for sym in self.tickerList]
116
117
  self.notifications.add_notification(f"{len(self.tickerList)} ticker list loaded")
@@ -270,12 +271,7 @@ class BitunixSignal:
270
271
  # Function to add data to the last price deque
271
272
  async def StoreTickerData(self, message):
272
273
  if self.settings.USE_PUBLIC_WEBSOCKET and message:
273
- try:
274
- data = json.loads(message)
275
- if data.get('symbol') and data.get('ch') == 'ticker':
276
- await self.ticker_que.put(data)
277
- except json.JSONDecodeError as e:
278
- logger.warning(f"Failed to decode message: {message}. Error: {e}")
274
+ await self.ticker_que.put(message)
279
275
 
280
276
  # Function to process the last price deque
281
277
  async def ProcessTickerData(self):
@@ -284,11 +280,14 @@ class BitunixSignal:
284
280
  latest_data = {}
285
281
  reversed_items = await self.drain_queue(self.ticker_que)
286
282
  while reversed_items:
287
- data = reversed_items.popleft()
288
- symbol = data["symbol"]
289
- ts = data["ts"]
290
- if symbol not in latest_data or ts > latest_data[symbol]['ts']:
291
- 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)
292
291
  # Convert to DataFrame
293
292
  self.tickerdf = pd.DataFrame.from_dict(latest_data, orient="index")
294
293
  if not self.tickerdf.empty:
@@ -299,7 +298,7 @@ class BitunixSignal:
299
298
  except Exception as e:
300
299
  logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
301
300
  logger.info(traceback.print_exc())
302
- await asyncio.sleep(0.5)
301
+ await asyncio.sleep(0.01)
303
302
  logger.info(f"ProcessTickerData: exitied out of the loop, exiting app")
304
303
  os._exit(1) # Exit the program
305
304
 
@@ -307,13 +306,8 @@ class BitunixSignal:
307
306
  #websocket data to update bid and ask
308
307
  async def StoreDepthData(self, message):
309
308
  if self.settings.USE_PUBLIC_WEBSOCKET and message:
310
- try:
311
- data = json.loads(message)
312
- if data.get('symbol') and data.get('ch') == 'depth_book1':
313
- await self.depth_que.put(data)
314
- except json.JSONDecodeError as e:
315
- logger.warning(f"Failed to decode message: {message}. Error: {e}")
316
-
309
+ await self.depth_que.put(message)
310
+
317
311
  # Function to process the bid, ask
318
312
  async def ProcessDepthData(self):
319
313
  while True:
@@ -321,11 +315,14 @@ class BitunixSignal:
321
315
  latest_data = {}
322
316
  reversed_items = await self.drain_queue(self.depth_que)
323
317
  while reversed_items:
324
- data = reversed_items.popleft()
325
- symbol = data["symbol"]
326
- ts = data["ts"]
327
- if symbol not in latest_data or ts > latest_data[symbol]['ts']:
328
- 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)
329
326
  # Convert to DataFrame
330
327
  self.depthdf = pd.DataFrame.from_dict(latest_data, orient="index")
331
328
  if not self.depthdf.empty:
@@ -334,7 +331,7 @@ class BitunixSignal:
334
331
  except Exception as e:
335
332
  logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
336
333
  logger.info(traceback.print_exc())
337
- await asyncio.sleep(0.5)
334
+ await asyncio.sleep(0.01)
338
335
  logger.info(f"ProcessDepthData: exitied out of the loop, exiting app")
339
336
  os._exit(1) # Exit the program
340
337
 
@@ -397,7 +394,7 @@ class BitunixSignal:
397
394
  self.notifications.add_notification("AutoTradeProcess or GetTickerData is not running")
398
395
  os._exit(1)
399
396
  break
400
- await asyncio.sleep(300)
397
+ await asyncio.sleep(60*30)
401
398
 
402
399
  async def GetportfolioData(self):
403
400
  start=time.time()
@@ -562,12 +559,12 @@ class BitunixSignal:
562
559
  self.signaldf_filtered = self.tickerObjects.signaldf_filtered
563
560
 
564
561
  if not self.positiondf.empty and not self.signaldf_full.empty:
565
- 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"]
566
563
  columns2=["qty", "side", "unrealizedPNL", "realizedPNL", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
567
564
  if set(columns).issubset(self.signaldf_full.columns) and set(columns2).issubset(self.positiondf.columns):
568
- 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"]
569
566
  self.positiondf2 = pd.merge(self.positiondf, self.signaldf_full[["symbol", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
570
- 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",
571
568
  f"{period}_trend",f"{period}_cb", f"{period}_barcolor"]], left_on="symbol", right_index=True, how="left")[columnOrder]
572
569
  self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf2)
573
570
  else:
@@ -581,7 +578,7 @@ class BitunixSignal:
581
578
  # Assign to self.signaldf for HTML rendering
582
579
  self.signaldf = self.signaldf_filtered[[
583
580
  "symbol", f"{period}_trend",f"{period}_cb", f"{period}_barcolor",
584
- 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",
585
582
  'lastcolor', 'bidcolor', 'askcolor', 'bid', 'last', 'ask',
586
583
  f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
587
584
  ]].sort_values(by=[f'{period}_cb'], ascending=[False])
@@ -692,7 +689,7 @@ class BitunixSignal:
692
689
  total_pnl = unrealized_pnl + realized_pnl
693
690
  side=row['side']
694
691
 
695
- 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']
696
693
  required_cols = set(requiredCols)
697
694
 
698
695
  # Close position that fall the below criteria
@@ -878,6 +875,41 @@ class BitunixSignal:
878
875
  )
879
876
  continue
880
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
+
881
913
  # Close on weak trend after open
882
914
  if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_CLOSE:
883
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"
@@ -36,11 +41,19 @@ class Interval:
36
41
  return self._data
37
42
 
38
43
  def set_data(self, new_value):
39
- self._data = self.calculate_study(new_value)
40
-
44
+ study = self.calculate_study(new_value)
45
+ self._data = study
46
+
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
+
41
54
  def calculate_study(self, new_value):
42
55
  df = pd.DataFrame(new_value)
43
- if not df.empty and df.shape[0] >= int(self.settings.BARS):
56
+ if not df.empty: #and df.shape[0] >= int(self.settings.BARS):
44
57
 
45
58
  try:
46
59
  #consecutive same color candle
@@ -53,54 +66,56 @@ class Interval:
53
66
  # Calculate the Moving Averages
54
67
  if self.settings.EMA_STUDY:
55
68
  df['ma_fast'] = talib.EMA(df['close'], timeperiod=self.settings.MA_FAST)
56
- df.dropna(subset=['ma_fast'], inplace=True)
69
+ df.fillna({'ma_fast':0}, inplace=True)
57
70
  df['ma_fast_slope'] = df['ma_fast'].diff()
58
71
  df['ma_fast_angle'] = np.degrees(np.arctan(df['ma_fast_slope']))
59
72
  df.fillna({'ma_fast_slope':0}, inplace=True)
60
73
  df.fillna({'ma_fast_angle':0}, inplace=True)
61
74
 
62
75
  df['ma_medium'] = talib.EMA(df['close'], timeperiod=self.settings.MA_MEDIUM)
63
- df.dropna(subset=['ma_medium'], inplace=True)
76
+ df.fillna({'ma_medium':0}, inplace=True)
64
77
  df['ma_medium_slope'] = df['ma_medium'].diff()
65
78
  df['ma_medium_angle'] = np.degrees(np.arctan(df['ma_medium_slope']))
66
79
  df.fillna({'ma_medium_slope':0}, inplace=True)
67
80
  df.fillna({'ma_medium_angle':0}, inplace=True)
68
81
 
69
82
  df['ma_slow'] = talib.EMA(df['close'], timeperiod=self.settings.MA_SLOW)
70
- df.dropna(subset=['ma_slow'],inplace=True)
83
+ df.fillna({'ma_slow':0}, inplace=True)
71
84
  df['ma_slow_slope'] = df['ma_slow'].diff()
72
85
  df['ma_slow_angle'] = np.degrees(np.arctan(df['ma_slow_slope']))
73
86
  df.fillna({'ma_slow_slope':0}, inplace=True)
74
87
  df.fillna({'ma_slow_angle':0}, inplace=True)
75
88
 
76
89
  if self.settings.EMA_CROSSING:
77
- if df['ma_medium'].iloc[-2] <= df['ma_slow'].iloc[-2] and df['ma_medium'].iloc[-1] > df['ma_slow'].iloc[-1]:
78
- self.ema_open_signal = "BUY"
79
- self.ema_close_signal = "BUY"
80
- elif df['ma_medium'].iloc[-2] >= df['ma_slow'].iloc[-2] and df['ma_medium'].iloc[-1] < df['ma_slow'].iloc[-1]:
81
- self.ema_open_signal = "SELL"
82
- self.ema_close_signal = "SELL"
83
- else:
84
- self.ema_open_signal = "HOLD"
85
- self.ema_close_signal = "HOLD"
86
-
87
- if self.settings.EMA_CLOSE_ON_FAST_MEDIUM:
88
- if df['ma_fast'].iloc[-2] <= df['ma_medium'].iloc[-2] and df['ma_fast'].iloc[-1] > df['ma_medium'].iloc[-1]:
90
+ if df is not None and len(df) >= 2:
91
+ if df['ma_medium'].iloc[-2] <= df['ma_slow'].iloc[-2] and df['ma_medium'].iloc[-1] > df['ma_slow'].iloc[-1]:
92
+ self.ema_open_signal = "BUY"
89
93
  self.ema_close_signal = "BUY"
90
- elif df['ma_fast'].iloc[-2] >= df['ma_medium'].iloc[-2] and df['ma_fast'].iloc[-1] < df['ma_medium'].iloc[-1]:
94
+ elif df['ma_medium'].iloc[-2] >= df['ma_slow'].iloc[-2] and df['ma_medium'].iloc[-1] < df['ma_slow'].iloc[-1]:
95
+ self.ema_open_signal = "SELL"
91
96
  self.ema_close_signal = "SELL"
92
97
  else:
98
+ self.ema_open_signal = "HOLD"
93
99
  self.ema_close_signal = "HOLD"
100
+
101
+ if self.settings.EMA_CLOSE_ON_FAST_MEDIUM:
102
+ if df['ma_fast'].iloc[-2] <= df['ma_medium'].iloc[-2] and df['ma_fast'].iloc[-1] > df['ma_medium'].iloc[-1]:
103
+ self.ema_close_signal = "BUY"
104
+ elif df['ma_fast'].iloc[-2] >= df['ma_medium'].iloc[-2] and df['ma_fast'].iloc[-1] < df['ma_medium'].iloc[-1]:
105
+ self.ema_close_signal = "SELL"
106
+ else:
107
+ self.ema_close_signal = "HOLD"
94
108
  else:
95
- if df['close'].iloc[-1] > df['ma_medium'].iloc[-1] and df['ma_medium'].iloc[-1] > df['ma_slow'].iloc[-1]:
96
- self.ema_open_signal = "BUY"
97
- self.ema_close_signal = "BUY"
98
- elif df['close'].iloc[-1] < df['ma_medium'].iloc[-1] and df['ma_medium'].iloc[-1] < df['ma_slow'].iloc[-1]:
99
- self.ema_open_signal = "SELL"
100
- self.ema_close_signal = "SELL"
101
- else:
102
- self.ema_open_signal = "HOLD"
103
- self.ema_close_signal = "HOLD"
109
+ if df is not None and len(df) >= 1:
110
+ if df['close'].iloc[-1] > df['ma_medium'].iloc[-1] and df['ma_medium'].iloc[-1] > df['ma_slow'].iloc[-1]:
111
+ self.ema_open_signal = "BUY"
112
+ self.ema_close_signal = "BUY"
113
+ elif df['close'].iloc[-1] < df['ma_medium'].iloc[-1] and df['ma_medium'].iloc[-1] < df['ma_slow'].iloc[-1]:
114
+ self.ema_open_signal = "SELL"
115
+ self.ema_close_signal = "SELL"
116
+ else:
117
+ self.ema_open_signal = "HOLD"
118
+ self.ema_close_signal = "HOLD"
104
119
  else:
105
120
  # Drop EMA columns if not used
106
121
  df.drop(['ma_fast', 'ma_medium', 'ma_slow', 'ma_slope', 'ma_angle'], axis=1, inplace=True, errors='ignore')
@@ -111,7 +126,7 @@ class Interval:
111
126
  df['MACD_Signal'] = 0.0
112
127
  df['MACD_Histogram'] = 0.0
113
128
  df['MACD_Line'], df['MACD_Signal'], df['MACD_Histogram'] = talib.MACD(df['close'], fastperiod=self.settings.MACD_SHORT, slowperiod=self.settings.MACD_LONG, signalperiod=self.settings.MACD_PERIOD)
114
- df.dropna(subset=['MACD_Line', 'MACD_Signal', 'MACD_Histogram'], inplace=True)
129
+ df.fillna({'MACD_Line':0, 'MACD_Signal':0, 'MACD_Histogram':0}, inplace=True)
115
130
  df['MACD_Line_slope'] = df['MACD_Line'].diff()
116
131
  df['MACD_Line_angle'] = np.degrees(np.arctan(df['MACD_Line_slope']))
117
132
  df.fillna({'MACD_Line_slope':0}, inplace=True)
@@ -119,19 +134,21 @@ class Interval:
119
134
 
120
135
 
121
136
  if self.settings.MACD_CROSSING:
122
- if df['MACD_Line'].iloc[-2] <= df['MACD_Signal'].iloc[-2] and df['MACD_Line'].iloc[-1] > df['MACD_Signal'].iloc[-1]:
123
- self.macd_signal = "BUY"
124
- elif df['MACD_Line'].iloc[-2] >= df['MACD_Signal'].iloc[-2] and df['MACD_Line'].iloc[-1] < df['MACD_Signal'].iloc[-1]:
125
- self.macd_signal = "SELL"
126
- else:
127
- self.macd_signal = "HOLD"
137
+ if df is not None and len(df) >= 2:
138
+ if df['MACD_Line'].iloc[-2] <= df['MACD_Signal'].iloc[-2] and df['MACD_Line'].iloc[-1] > df['MACD_Signal'].iloc[-1]:
139
+ self.macd_signal = "BUY"
140
+ elif df['MACD_Line'].iloc[-2] >= df['MACD_Signal'].iloc[-2] and df['MACD_Line'].iloc[-1] < df['MACD_Signal'].iloc[-1]:
141
+ self.macd_signal = "SELL"
142
+ else:
143
+ self.macd_signal = "HOLD"
128
144
  else:
129
- if df['MACD_Line'].iloc[-1] > df['MACD_Signal'].iloc[-1]:
130
- self.macd_signal = "BUY"
131
- elif df['MACD_Line'].iloc[-1] < df['MACD_Signal'].iloc[-1]:
132
- self.macd_signal = "SELL"
133
- else:
134
- self.macd_signal = "HOLD"
145
+ if df is not None and len(df) >= 1:
146
+ if df['MACD_Line'].iloc[-1] > df['MACD_Signal'].iloc[-1]:
147
+ self.macd_signal = "BUY"
148
+ elif df['MACD_Line'].iloc[-1] < df['MACD_Signal'].iloc[-1]:
149
+ self.macd_signal = "SELL"
150
+ else:
151
+ self.macd_signal = "HOLD"
135
152
  else:
136
153
  # Drop MACD columns if not used
137
154
  df.drop(['MACD_Line', 'MACD_Signal', 'MACD_Histogram', 'MACD_slope', 'MACD_angle'], axis=1, inplace=True, errors='ignore')
@@ -142,7 +159,7 @@ class Interval:
142
159
  df['BBM'] = 0.0
143
160
  df['BBU'] = 0.0
144
161
  df['BBU'], df['BBM'], df['BBL'] = talib.BBANDS(df['close'], timeperiod=self.settings.BBM_PERIOD, nbdevup=self.settings.BBM_STD, nbdevdn=self.settings.BBM_STD, )
145
- df.dropna(subset=['BBM', 'BBU', 'BBL'], inplace=True)
162
+ df.fillna({'BBM':0, 'BBU':0, 'BBL':0}, inplace=True)
146
163
 
147
164
  df['BBM_slope'] = df['BBM'].diff()
148
165
  df['BBM_angle'] = np.degrees(np.arctan(df['BBM_slope']))
@@ -150,19 +167,21 @@ class Interval:
150
167
  df.fillna({'BBM_angle':0}, inplace=True)
151
168
 
152
169
  if self.settings.BBM_CROSSING:
153
- if df['close'].iloc[-2] <= df['BBM'].iloc[-2] and df['close'].iloc[-1] > df['BBM'].iloc[-1]:
154
- self.bbm_signal = "BUY"
155
- elif df['close'].iloc[-2] >= df['BBM'].iloc[-2] and df['close'].iloc[-1] < df['BBM'].iloc[-1]:
156
- self.bbm_signal = "SELL"
157
- else:
158
- self.bbm_signal = "HOLD"
170
+ if df is not None and len(df) >= 2:
171
+ if df['close'].iloc[-2] <= df['BBM'].iloc[-2] and df['close'].iloc[-1] > df['BBM'].iloc[-1]:
172
+ self.bbm_signal = "BUY"
173
+ elif df['close'].iloc[-2] >= df['BBM'].iloc[-2] and df['close'].iloc[-1] < df['BBM'].iloc[-1]:
174
+ self.bbm_signal = "SELL"
175
+ else:
176
+ self.bbm_signal = "HOLD"
159
177
  else:
160
- if df['close'].iloc[-1] > df['BBM'].iloc[-1] and df['close'].iloc[-2] > df['BBM'].iloc[-2]:
161
- self.bbm_signal = "BUY"
162
- elif df['close'].iloc[-1] < df['BBM'].iloc[-1] and df['close'].iloc[-2] < df['BBM'].iloc[-2]:
163
- self.bbm_signal = "SELL"
164
- else:
165
- self.bbm_signal = "HOLD"
178
+ if df is not None and len(df) >= 1:
179
+ if df['close'].iloc[-1] > df['BBM'].iloc[-1]:
180
+ self.bbm_signal = "BUY"
181
+ elif df['close'].iloc[-1] < df['BBM'].iloc[-1]:
182
+ self.bbm_signal = "SELL"
183
+ else:
184
+ self.bbm_signal = "HOLD"
166
185
  else:
167
186
  # Drop BBM columns if not used
168
187
  df.drop(['BBL', 'BBM', 'BBU', 'BBM_slope', 'BBM_angle'], axis=1, inplace=True, errors='ignore')
@@ -170,14 +189,14 @@ class Interval:
170
189
  # Calculate the RSI
171
190
  if self.settings.RSI_STUDY:
172
191
  df['rsi_fast'] = talib.RSI(df['close'],timeperiod=self.settings.RSI_FAST)
173
- df.dropna(subset=['rsi_fast'], inplace=True)
192
+ df.fillna({'rsi_fast':0}, inplace=True)
174
193
  df['rsi_fast_slope'] = df['rsi_fast'].diff()
175
194
  df['rsi_fast_angle'] = np.degrees(np.arctan(df['rsi_fast_slope']))
176
195
  df.fillna({'rsi_fast_slope':0}, inplace=True)
177
196
  df.fillna({'rsi_fast_angle':0}, inplace=True)
178
197
 
179
198
  df['rsi_slow'] = talib.RSI(df['close'],timeperiod=self.settings.RSI_SLOW)
180
- df.dropna(subset=['rsi_slow'], inplace=True)
199
+ df.fillna({'rsi_slow':0}, inplace=True)
181
200
  df['rsi_slow_slope'] = df['rsi_slow'].diff()
182
201
  df['rsi_slow_angle'] = np.degrees(np.arctan(df['rsi_slow_slope']))
183
202
  df.fillna({'rsi_slow_slope':0}, inplace=True)
@@ -185,54 +204,79 @@ class Interval:
185
204
 
186
205
 
187
206
  if self.settings.RSI_CROSSING:
188
- if df['rsi_fast'].iloc[-2] <= df['rsi_slow'].iloc[-2] and df['rsi_fast'].iloc[-1] > df['rsi_slow'].iloc[-1]:
189
- self.rsi_signal = "BUY"
190
- elif df['rsi_fast'].iloc[-2] >= df['rsi_slow'].iloc[-2] and df['rsi_fast'].iloc[-1] < df['rsi_slow'].iloc[-1]:
191
- self.rsi_signal = "SELL"
192
- else:
193
- self.rsi_signal = "HOLD"
207
+ if df is not None and len(df) >= 2:
208
+ if df['rsi_fast'].iloc[-2] <= df['rsi_slow'].iloc[-2] and df['rsi_fast'].iloc[-1] > df['rsi_slow'].iloc[-1]:
209
+ self.rsi_signal = "BUY"
210
+ elif df['rsi_fast'].iloc[-2] >= df['rsi_slow'].iloc[-2] and df['rsi_fast'].iloc[-1] < df['rsi_slow'].iloc[-1]:
211
+ self.rsi_signal = "SELL"
212
+ else:
213
+ self.rsi_signal = "HOLD"
194
214
  else:
195
- if df['rsi_fast'].iloc[-1] > df['rsi_slow'].iloc[-1]:
196
- self.rsi_signal = "BUY"
197
- elif df['rsi_fast'].iloc[-1] < df['rsi_slow'].iloc[-1]:
198
- self.rsi_signal = "SELL"
199
- else:
200
- self.rsi_signal = "HOLD"
215
+ if df is not None and len(df) >= 1:
216
+ if df['rsi_fast'].iloc[-1] > df['rsi_slow'].iloc[-1]:
217
+ self.rsi_signal = "BUY"
218
+ elif df['rsi_fast'].iloc[-1] < df['rsi_slow'].iloc[-1]:
219
+ self.rsi_signal = "SELL"
220
+ else:
221
+ self.rsi_signal = "HOLD"
201
222
  else:
202
223
  # Drop RSI columns if not used
203
224
  df.drop(['rsi_fast', 'rsi_slow', 'rsi_slope', 'rsi_angle'], axis=1, inplace=True, errors='ignore')
204
225
 
226
+ #Trendline
227
+ if self.settings.TRENDLINE_STUDY:
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"
246
+
205
247
  # Calculate the ADX
206
248
  if self.settings.ADX_STUDY:
207
- df['ADX'] = talib.ADX(df['high'], df['low'], df['close'], timeperiod=self.settings.ADX_PERIOD)
208
- df.fillna({'ADX':0}, inplace=True)
209
- if df['ADX'].iloc[-1] > 25:
210
- self.adx_signal = "STRONG"
211
- else:
212
- self.adx_signal = "WEAK"
249
+ if df is not None and len(df) >= 1:
250
+ df['ADX'] = talib.ADX(df['high'], df['low'], df['close'], timeperiod=self.settings.ADX_PERIOD)
251
+ df.fillna({'ADX':0}, inplace=True)
252
+ if df['ADX'].iloc[-1] > 25:
253
+ self.adx_signal = "STRONG"
254
+ else:
255
+ self.adx_signal = "WEAK"
213
256
  else:
214
257
  # Drop ADX columns if not used
215
258
  df.drop(['ADX'], axis=1, inplace=True, errors='ignore')
216
259
 
217
260
  # Calculate the close proximity
218
261
  if self.settings.CANDLE_TREND_STUDY:
219
- df['range'] = df['high'] - df['low']
220
- df['candle_trend'] = ((df['close'] - df['low'])/df['range'])*100
221
- df.fillna({'candle_trend':0}, inplace=True)
222
- df.fillna({'range':0}, inplace=True)
223
-
224
- #open and close criteria
225
- if df['candle_trend'].iloc[-1] > 70:
226
- self.candle_trend = 'BULLISH'
227
- elif df['candle_trend'].iloc[-1] < 30:
228
- self.candle_trend = 'BEARISH'
229
- else:
230
- self.candle_trend = 'HOLD'
262
+ if df is not None and len(df) >= 1:
263
+ df['range'] = df['high'] - df['low']
264
+ df['candle_trend'] = ((df['close'] - df['low'])/df['range'])*100
265
+ df.fillna({'candle_trend':0}, inplace=True)
266
+ df.fillna({'range':0}, inplace=True)
267
+
268
+ #open and close criteria
269
+ if df['candle_trend'].iloc[-1] > 70:
270
+ self.candle_trend = 'BULLISH'
271
+ elif df['candle_trend'].iloc[-1] < 30:
272
+ self.candle_trend = 'BEARISH'
273
+ else:
274
+ self.candle_trend = 'HOLD'
231
275
  else:
232
276
  # Drop candle trend columns if not used
233
277
  df.drop(['candle_trend', 'range'], axis=1, inplace=True, errors='ignore')
234
278
 
235
-
279
+
236
280
  #replace infinity
237
281
  df.replace([np.inf, -np.inf], 0, inplace=True)
238
282
 
@@ -247,7 +291,8 @@ class Interval:
247
291
  (self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY") or
248
292
  (self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY") or
249
293
  (self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY") or
250
- (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")
251
296
  )
252
297
  additional_buy_conditions = (
253
298
  (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") or
@@ -259,7 +304,8 @@ class Interval:
259
304
  (self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL") or
260
305
  (self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "SELL") or
261
306
  (self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "SELL") or
262
- (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")
263
309
  )
264
310
  additional_sell_conditions = (
265
311
  (self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") or
@@ -282,7 +328,8 @@ class Interval:
282
328
  (not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_open_signal == "BUY") and
283
329
  (not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "BUY") and
284
330
  (not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "BUY") and
285
- (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")
286
333
  )
287
334
  additional_buy_conditions = (
288
335
  (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
@@ -294,7 +341,8 @@ class Interval:
294
341
  (not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_open_signal == "SELL") and
295
342
  (not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "SELL") and
296
343
  (not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "SELL") and
297
- (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")
298
346
  )
299
347
  additional_sell_conditions = (
300
348
  (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
@@ -312,7 +360,7 @@ class Interval:
312
360
 
313
361
  except Exception as e:
314
362
  logger.info(f"Function: calculate_study, {e}, {e.args}, {type(e).__name__}")
315
- logger.info(traceback.logger.info_exc())
363
+ logger.info(traceback.print_exc())
316
364
  retval = df.to_dict('records')
317
365
  del df
318
366
  gc.collect()
@@ -404,7 +452,8 @@ class Ticker:
404
452
  self._intervals = intervals
405
453
 
406
454
  def get_interval_ticks(self, intervalId):
407
- return self._intervals.get(intervalId, None)
455
+ interval = self._intervals.get(intervalId, None)
456
+ return interval
408
457
 
409
458
  def set_interval_ticks(self, intervalId, new_value):
410
459
  self._intervals[intervalId] = new_value
@@ -443,6 +492,7 @@ class Ticker:
443
492
  ticks_interval=intervalObj.get_data()
444
493
  if ticks_interval is None:
445
494
  return
495
+ #print('create_bar_with_last_and_ts', self.symbol, intervalId, len(ticks_interval))
446
496
  if len(ticks_interval)==0 or self._ts - ticks_interval[-1]['time'] >= intervalObj.delta:
447
497
  ticks_interval.append(new_item)
448
498
  current_bar = ticks_interval[-1]
@@ -457,11 +507,12 @@ class Ticker:
457
507
  current_bar['barcolor'] = self.red if current_bar['close'] <= current_bar['open'] else self.green
458
508
  #print(f'{self.symbol} {self._last}, {self._ts} {intervalId} {ticks_interval[-1]['time']} c' )
459
509
 
510
+ #print ('create_bar_with_last_and_ts', ticks_interval[-1])
460
511
  intervalObj.set_data(ticks_interval)
461
512
 
462
513
  except Exception as e:
463
514
  logger.info(f"Function: create_bar_with_last_and_ts, {e}, {e.args}, {type(e).__name__}")
464
- logger.info(traceback.logger.info_exc())
515
+ logger.info(traceback.print_exc())
465
516
 
466
517
 
467
518
  class Tickers:
@@ -560,24 +611,30 @@ class Tickers:
560
611
  # since the calulation is done at a different thread and uses a different memory space,
561
612
  # the caluated value has to be reassigned to the original ticker class instance from the caluated ticker instance
562
613
  def form_candle(self, tuples_list):
563
- with ProcessPoolExecutor() as executor:
564
- results = executor.map(self.process_ticker_candle, tuples_list, chunksize=10)
565
- for args, result in zip(tuples_list, results):
566
- for intervalId, interval in result[3].items():
567
- if not interval._data is None:
568
- args[0].get_interval_ticks(intervalId)._data = interval._data
569
- args[0].get_interval_ticks(intervalId).current_signal = interval.current_signal
570
- args[0].get_interval_ticks(intervalId).ema_open_signal = interval.ema_open_signal
571
- args[0].get_interval_ticks(intervalId).ema_close_signal = interval.ema_close_signal
572
- args[0].get_interval_ticks(intervalId).macd_signal = interval.macd_signal
573
- args[0].get_interval_ticks(intervalId).bbm_signal = interval.bbm_signal
574
- args[0].get_interval_ticks(intervalId).rsi_signal = interval.rsi_signal
575
- args[0].get_interval_ticks(intervalId).candle_trend = interval.candle_trend
576
- args[0].get_interval_ticks(intervalId).adx_signal = interval.adx_signal
577
- args[0].get_interval_ticks(intervalId).signal_strength = interval.signal_strength
578
- args[0]._last = result[0]
579
- args[0].lastcolor = result[1]
580
- args[0]._ts = result[2]
614
+ if not self.settings.CPU_PROCCESING:
615
+ for args in tuples_list:
616
+ ticker_obj, last, ts = args
617
+ result = self.process_ticker_candle(args)
618
+ else:
619
+ with ProcessPoolExecutor() as executor:
620
+ results = executor.map(self.process_ticker_candle, tuples_list, chunksize=10)
621
+ for args, result in zip(tuples_list, results):
622
+ for intervalId, interval in result[3].items():
623
+ if not interval._data is None and len(interval._data) >= self.settings.BARS:
624
+ args[0].get_interval_ticks(intervalId)._data = interval._data
625
+ args[0].get_interval_ticks(intervalId).current_signal = interval.current_signal
626
+ args[0].get_interval_ticks(intervalId).ema_open_signal = interval.ema_open_signal
627
+ args[0].get_interval_ticks(intervalId).ema_close_signal = interval.ema_close_signal
628
+ args[0].get_interval_ticks(intervalId).macd_signal = interval.macd_signal
629
+ args[0].get_interval_ticks(intervalId).bbm_signal = interval.bbm_signal
630
+ args[0].get_interval_ticks(intervalId).rsi_signal = interval.rsi_signal
631
+ args[0].get_interval_ticks(intervalId).trendline_signal, = interval.trendline_signal,
632
+ args[0].get_interval_ticks(intervalId).candle_trend = interval.candle_trend
633
+ args[0].get_interval_ticks(intervalId).adx_signal = interval.adx_signal
634
+ args[0].get_interval_ticks(intervalId).signal_strength = interval.signal_strength
635
+ args[0]._last = result[0]
636
+ args[0].lastcolor = result[1]
637
+ args[0]._ts = result[2]
581
638
 
582
639
  def getCurrentData(self, period):
583
640
  current_data = []
@@ -596,33 +653,33 @@ class Tickers:
596
653
  ticks = intervalObj.get_data()
597
654
  if not ticks:
598
655
  continue
599
- if len(ticks)>=int(self.settings.BARS):
600
- lastcandle = intervalObj.get_data()[-1]
601
- if len(lastcandle) >= 20:
602
- new_row = {
603
- 'symbol' : symbol,
604
- f"{period}_trend": intervalObj.current_signal,
605
- f"{period}_cb": intervalObj.signal_strength,
606
- f"{period}_barcolor": lastcandle['barcolor'],
607
- f"{period}_ema_open": intervalObj.ema_open_signal,
608
- f"{period}_ema_close": intervalObj.ema_close_signal,
609
- f"{period}_macd":intervalObj.macd_signal,
610
- f"{period}_bbm":intervalObj.bbm_signal,
611
- f"{period}_rsi":intervalObj.rsi_signal,
612
- f"{period}_adx":intervalObj.adx_signal,
613
- f"{period}_candle_trend":intervalObj.candle_trend,
614
- 'bid' : bid,
615
- 'bidcolor' : bidcolor,
616
- 'last' : last,
617
- 'lastcolor' : lastcolor,
618
- 'ask' : ask,
619
- 'askcolor' : askcolor,
620
- f"{period}_open": lastcandle['open'],
621
- f"{period}_close": lastcandle['close'],
622
- f"{period}_high": lastcandle['high'],
623
- f"{period}_low": lastcandle['low'],
624
- }
625
- current_data.append(new_row)
656
+ lastcandle = intervalObj.get_data()[-1]
657
+ if len(lastcandle) >= 20:
658
+ new_row = {
659
+ 'symbol' : symbol,
660
+ f"{period}_trend": intervalObj.current_signal,
661
+ f"{period}_cb": intervalObj.signal_strength,
662
+ f"{period}_barcolor": lastcandle['barcolor'],
663
+ f"{period}_ema_open": intervalObj.ema_open_signal,
664
+ f"{period}_ema_close": intervalObj.ema_close_signal,
665
+ f"{period}_macd":intervalObj.macd_signal,
666
+ f"{period}_bbm":intervalObj.bbm_signal,
667
+ f"{period}_rsi":intervalObj.rsi_signal,
668
+ f"{period}_trendline": intervalObj.trendline_signal,
669
+ f"{period}_adx":intervalObj.adx_signal,
670
+ f"{period}_candle_trend":intervalObj.candle_trend,
671
+ 'bid' : bid,
672
+ 'bidcolor' : bidcolor,
673
+ 'last' : last,
674
+ 'lastcolor' : lastcolor,
675
+ 'ask' : ask,
676
+ 'askcolor' : askcolor,
677
+ f"{period}_open": lastcandle['open'],
678
+ f"{period}_close": lastcandle['close'],
679
+ f"{period}_high": lastcandle['high'],
680
+ f"{period}_low": lastcandle['low'],
681
+ }
682
+ current_data.append(new_row)
626
683
 
627
684
  df = pd.DataFrame(current_data)
628
685
  if not df.empty:
@@ -630,7 +687,7 @@ class Tickers:
630
687
  'lastcolor': "", 'bidcolor': "", 'askcolor': "", 'bid': 0.0, 'ask': 0.0, 'last': 0.0,
631
688
  f"{period}_cb":0, f"{period}_barcolor": "", f"{period}_trend": "",
632
689
  f"{period}_open": 0.0, f"{period}_close": 0.0, f"{period}_high": 0.0, f"{period}_low": 0.0,
633
- 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": ""
634
691
  }
635
692
  df.fillna(fill_values, inplace=True)
636
693
  df.set_index("symbol", inplace=True, drop=False)
@@ -639,19 +696,21 @@ class Tickers:
639
696
  trending_conditions = [
640
697
  (
641
698
  (df[f'{period}_trend']=='BUY') &
642
- (df[f'{period}_cb']>1) &
643
- (df[f'{period}_barcolor']==self.green)
699
+ (df[f'{period}_cb']>1)
700
+ #&
701
+ #(df[f'{period}_barcolor']==self.green)
644
702
  ),
645
703
  (
646
704
  (df[f'{period}_trend']=='SELL') &
647
- (df[f'{period}_cb']>1) &
648
- (df[f'{period}_barcolor']==self.red)
705
+ (df[f'{period}_cb']>1)
706
+ #&
707
+ #(df[f'{period}_barcolor']==self.red)
649
708
  )
650
709
  ]
651
710
  self.signaldf_filtered = df[np.any(trending_conditions, axis=0)].copy()
652
711
  except Exception as e:
653
712
  logger.info(f"Function: getCurrentData, {e}, {e.args}, {type(e).__name__}")
654
- logger.info(traceback.logger.info_exc())
713
+ logger.info(traceback.print_exc())
655
714
  finally:
656
715
  del df, current_data
657
716
  gc.collect()
@@ -70,76 +70,6 @@ class bitunix():
70
70
  #create benchmark table
71
71
  self.cursor.execute("CREATE TABLE IF NOT EXISTS benchmark (id INTEGER PRIMARY KEY, process_name TEXT, time INTEGER)")
72
72
 
73
- #create settings table
74
- self.cursor.execute("CREATE TABLE IF NOT EXISTS settings (param TEXT PRIMARY KEY, value TEXT)")
75
- self.cursor.execute(f"SELECT param, value FROM settings")
76
- rows = self.cursor.fetchall()
77
- if len(rows) == 0:
78
- # fille value from confix.txt file to db
79
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("AUTOTRADE","True"))
80
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("OPTION_MOVING_AVERAGE","1h"))
81
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MAX_AUTO_TRADES","10"))
82
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("PROFIT_AMOUNT","3"))
83
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("LOSS_AMOUNT","0"))
84
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("LEVERAGE","20"))
85
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("THRESHOLD","5"))
86
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MIN_VOLUME","10000000"))
87
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("ORDER_AMOUNT_PERCENTAGE","5"))
88
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BARS","100"))
89
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MA_FAST","10"))
90
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MA_MEDIUM","20"))
91
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MA_SLOW","50"))
92
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("RSI_FAST","6"))
93
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("RSI_SLOW","24"))
94
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BBM_PERIOD","20"))
95
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BBM_STD","2"))
96
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MACD_PERIOD","9"))
97
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MACD_SHORT","12"))
98
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MACD_LONG","26"))
99
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("ADX_PERIOD","14"))
100
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("OPEN_ON_ANY_SIGNAL","True"))
101
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("EMA_STUDY","True"))
102
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("EMA_CHART","True"))
103
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("EMA_CROSSING","True"))
104
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("EMA_CHECK_ON_OPEN","True"))
105
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("EMA_CHECK_ON_CLOSE","True"))
106
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("EMA_CLOSE_ON_FAST_MEDIUM","True"))
107
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MACD_STUDY","True"))
108
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MACD_CHART","True"))
109
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MACD_CROSSING","True"))
110
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MACD_CHECK_ON_OPEN","True"))
111
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("MACD_CHECK_ON_CLOSE","True"))
112
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BBM_STUDY","True"))
113
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BBM_CHART","False"))
114
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BBM_CROSSING","False"))
115
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BBM_CHECK_ON_OPEN","False"))
116
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BBM_CHECK_ON_CLOSE","False"))
117
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("RSI_STUDY","True"))
118
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("RSI_CHART","True"))
119
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("RSI_CROSSING","False"))
120
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("RSI_CHECK_ON_OPEN","False"))
121
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("RSI_CHECK_ON_CLOSE","False"))
122
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("ADX_STUDY","True"))
123
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("ADX_CHECK_ON_OPEN","True"))
124
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("ADX_CHECK_ON_CLOSE","False"))
125
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("CANDLE_TREND_STUDY","False"))
126
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("CANDLE_TREND_CHECK_ON_OPEN","False"))
127
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("CANDLE_TREND_CHECK_ON_CLOSE","False"))
128
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("SCREEN_REFRESH_INTERVAL","1"))
129
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("SIGNAL_CHECK_INTERVAL","15"))
130
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("PORTFOLIO_API_INTERVAL","3"))
131
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("PENDING_POSITIONS_API_INTERVAL","3"))
132
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("PENDING_ORDERS_API_INTERVAL","3"))
133
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("TRADE_HISTORY_API_INTERVAL","3"))
134
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("POSITION_HISTORY_API_INTERVAL","3"))
135
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("TICKER_DATA_API_INTERVAL","120"))
136
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BATCH_PROCESS_SIZE","1000"))
137
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("USE_PUBLIC_WEBSOCKET","True"))
138
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("VERBOSE_LOGGING","False"))
139
- self.cursor.execute("INSERT INTO settings (param, value) VALUES (?, ?)", ("BENCHMARK","False"))
140
- self.connection.commit()
141
-
142
-
143
73
  async def update_settings(self, settings):
144
74
  self.settings = settings
145
75
  await self.bitunixSignal.update_settings(settings)
@@ -470,6 +400,8 @@ async def wscharts(websocket):
470
400
  "buysell": buysell,
471
401
  "ema_study": settings.EMA_STUDY,
472
402
  "ema_chart": settings.EMA_CHART,
403
+ "trendline_study": settings.TRENDLINE_STUDY,
404
+ "trendline_chart": settings.TRENDLINE_CHART,
473
405
  "macd_study": settings.MACD_STUDY,
474
406
  "macd_chart": settings.MACD_CHART,
475
407
  "bbm_study": settings.BBM_STUDY,
@@ -557,6 +489,8 @@ async def wschart(websocket):
557
489
  "period": period,
558
490
  "ema_study": settings.EMA_STUDY,
559
491
  "ema_chart": settings.EMA_CHART,
492
+ "trendline_study": settings.TRENDLINE_STUDY,
493
+ "trendline_chart": settings.TRENDLINE_CHART,
560
494
  "macd_study": settings.MACD_STUDY,
561
495
  "macd_chart": settings.MACD_CHART,
562
496
  "bbm_study": settings.BBM_STUDY,
@@ -59,6 +59,13 @@ class Settings(BaseSettings):
59
59
  RSI_CHECK_ON_OPEN: bool = Field(default=False)
60
60
  RSI_CHECK_ON_CLOSE: bool = Field(default=False)
61
61
 
62
+ TRENDLINE_CHART: bool = Field(default=True)
63
+ TRENDLINE_STUDY: bool = Field(default=True)
64
+ TRENDLINE_PEAK_DISTANCE: int = Field(default=1, ge=0, le=30)
65
+ TRENDLINE_LOOKBACK: int = Field(default=20, ge=10, le=200)
66
+ TRENDLINE_CHECK_ON_OPEN: bool = Field(default=False)
67
+ TRENDLINE_CHECK_ON_CLOSE: bool = Field(default=False)
68
+
62
69
  ADX_STUDY: bool = Field(default=True)
63
70
  ADX_CHECK_ON_OPEN: bool = Field(default=False)
64
71
  ADX_CHECK_ON_CLOSE: bool = Field(default=False)
@@ -84,6 +91,9 @@ class Settings(BaseSettings):
84
91
  # Logger
85
92
  VERBOSE_LOGGING: bool = Field(default=False)
86
93
 
94
+ #use CPU
95
+ CPU_PROCCESING: bool = Field(default=True)
96
+
87
97
  # Benchmark
88
98
  BENCHMARK: bool = Field(default=False)
89
99
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bitunix_automated_crypto_trading
3
- Version: 3.1.2
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
@@ -0,0 +1,18 @@
1
+ bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=LJ6ny1KSCVoIbfeNypsY4aHYcbkGME9M3epQ9e8B1O8,3224
2
+ bitunix_automated_crypto_trading/BitunixApi.py,sha256=W0uem1wIs1uM-jaGtXXzL_JQfnCDb7imyTZ2Tqjk8e8,11230
3
+ bitunix_automated_crypto_trading/BitunixSignal.py,sha256=TOseCeTNpCwVcReXRvHoq1sQybhoiIkFvATbu05wQ8c,67215
4
+ bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=mbuvk8UFWKgv4KLV07TgLgxLVTRJnOKuf02mLB-VoCY,11143
5
+ bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
6
+ bitunix_automated_crypto_trading/NotificationManager.py,sha256=pqDquEe-oujD2v8B543524vo62aRMjfB4YW-3DMhVGQ,795
7
+ bitunix_automated_crypto_trading/SupportResistance.py,sha256=Dx4uBC4On-GO1t2EFk02jzSmZzaMD48mUJI9rG2rgMo,4946
8
+ bitunix_automated_crypto_trading/ThreadManager.py,sha256=WmYRQu8aNrgp5HwSqpBqX1WESNSEpbD2CznPFpd9HuI,2330
9
+ bitunix_automated_crypto_trading/TickerManager.py,sha256=JTlXUL9GadRjloK6Gw2HhacjvIe31CiiIFl1KGkWztA,37444
10
+ bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
11
+ bitunix_automated_crypto_trading/bitunix.py,sha256=Zyrf8P-xi7DJogX_bcc6uDW66J7FAxdS-0jbuHRPFfM,25845
12
+ bitunix_automated_crypto_trading/config.py,sha256=zeNmV1KK8Yv5cHzBxaly0lmcjwSZEBAMUm_OvFSBCK4,4922
13
+ bitunix_automated_crypto_trading/logger.py,sha256=tAaTQcv5r--zupk_Fhfe1USfBAzSiXzVa4QRnaOFIFY,2933
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 (78.1.0)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,17 +0,0 @@
1
- bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=LJ6ny1KSCVoIbfeNypsY4aHYcbkGME9M3epQ9e8B1O8,3224
2
- bitunix_automated_crypto_trading/BitunixApi.py,sha256=gmhWi83k4EHOMIY7ZPa3x2NuIgVyzH13Sjk9AlVKjtQ,11163
3
- bitunix_automated_crypto_trading/BitunixSignal.py,sha256=z8-mvwZOR1VAAmrgy9HjgFyv-IRx1GxogcXjwCPJ4oY,64687
4
- bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=mbuvk8UFWKgv4KLV07TgLgxLVTRJnOKuf02mLB-VoCY,11143
5
- bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
6
- bitunix_automated_crypto_trading/NotificationManager.py,sha256=pqDquEe-oujD2v8B543524vo62aRMjfB4YW-3DMhVGQ,795
7
- bitunix_automated_crypto_trading/ThreadManager.py,sha256=WmYRQu8aNrgp5HwSqpBqX1WESNSEpbD2CznPFpd9HuI,2330
8
- bitunix_automated_crypto_trading/TickerManager.py,sha256=g9Imr3uJijWeLqJbmCDrcDBwCHaBXV6P6sse73akmTA,33082
9
- bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
10
- bitunix_automated_crypto_trading/bitunix.py,sha256=c5vKRuN1-UaNz_ZB5Txu4-2UM6PsnRhs-ztvhRtpEzc,32779
11
- bitunix_automated_crypto_trading/config.py,sha256=QWAe5Ruq8A5anaFS-CamVm-3t1wMJjGG1DWaPIIWfiM,4521
12
- bitunix_automated_crypto_trading/logger.py,sha256=tAaTQcv5r--zupk_Fhfe1USfBAzSiXzVa4QRnaOFIFY,2933
13
- bitunix_automated_crypto_trading-3.1.2.dist-info/METADATA,sha256=5hhgVdOaJ-kqG0zdmen-OXVGpvBtXJxXjoU-tg_8-WI,996
14
- bitunix_automated_crypto_trading-3.1.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
15
- bitunix_automated_crypto_trading-3.1.2.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
16
- bitunix_automated_crypto_trading-3.1.2.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
17
- bitunix_automated_crypto_trading-3.1.2.dist-info/RECORD,,