bitunix-automated-crypto-trading 3.3.8__py3-none-any.whl → 3.4.0__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.
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  import threading
3
- from logger import Logger
4
- import os
3
+ import time
4
+ import logging # Using standard logging for demonstration
5
5
 
6
6
  class AsyncThreadRunner:
7
7
  def __init__(self, async_func, logger, interval, *args, **kwargs):
@@ -13,24 +13,37 @@ class AsyncThreadRunner:
13
13
  self._stop_event = threading.Event()
14
14
  self.thread = threading.Thread(target=self.thread_function)
15
15
  self.task = None
16
- self.logger = logger
16
+ self.logger = logger
17
+ self.exception_occurred = False # New flag to indicate if an unhandled exception occurred
17
18
 
18
19
  def thread_function(self):
20
+ """This function runs in the separate thread."""
19
21
  asyncio.set_event_loop(self.loop)
22
+ self.exception_occurred = False # Reset on thread start
23
+
20
24
  try:
21
25
  if self.interval == 0:
26
+ # For one-off tasks, run it and then stop the loop
22
27
  self.task = asyncio.run_coroutine_threadsafe(
23
- self.async_func(*self.args, **self.kwargs), self.loop
24
- )
28
+ self.run_once(), self.loop
29
+ )
25
30
  else:
31
+ # For periodic tasks, schedule the periodic_run
26
32
  self.task = asyncio.run_coroutine_threadsafe(
27
- self.periodic_run(), self.loop
28
- )
33
+ self.periodic_run(), self.loop
34
+ )
35
+
36
+ # This will run until loop.stop() is called
37
+ self.logger.info(f"Thread '{self.thread.name}' event loop started.")
29
38
  self.loop.run_forever()
30
39
  except Exception as e:
31
- self.logger.info(f"Async Thread function error: {e}, exiting app")
32
- os._exit(1) # Exit the program if the thread is stopped
40
+ # This catches exceptions that bubble up from run_forever,
41
+ # which usually means a serious issue or the loop being stopped unexpectedly.
42
+ self.logger.error(f"Async Thread '{self.thread.name}' main function error: {type(e).__name__} - {e}", exc_info=True)
43
+ self.exception_occurred = True # Mark as failed
33
44
  finally:
45
+ self.logger.info(f"Thread '{self.thread.name}' event loop stopping.")
46
+ # Clean up pending tasks
34
47
  pending = asyncio.all_tasks(self.loop)
35
48
  for task in pending:
36
49
  task.cancel()
@@ -38,45 +51,82 @@ class AsyncThreadRunner:
38
51
  self.loop.run_until_complete(task)
39
52
  except asyncio.CancelledError:
40
53
  pass
54
+ except Exception as e:
55
+ self.logger.error(f"Error during task cancellation in thread '{self.thread.name}': {e}")
56
+
57
+ # Shutdown async generators and close the loop
41
58
  self.loop.run_until_complete(self.loop.shutdown_asyncgens())
42
59
  self.loop.close()
60
+ self.logger.info(f"Thread '{self.thread.name}' event loop closed.")
61
+
62
+ async def run_once(self):
63
+ """Helper for one-off tasks."""
64
+ try:
65
+ await self.async_func(*self.args, **self.kwargs)
66
+ self.logger.info(f"Thread '{self.thread.name}' one-off task completed.")
67
+ except Exception as e:
68
+ self.logger.error(f"Error in one-off task for thread '{self.thread.name}': {type(e).__name__} - {e}", exc_info=True)
69
+ self.exception_occurred = True # Mark as failed if the one-off task itself fails
70
+ finally:
71
+ # Stop the event loop after a one-off task completes or fails
72
+ self.loop.call_soon_threadsafe(self.loop.stop)
73
+
43
74
 
44
75
  async def periodic_run(self):
76
+ """Runs the async function periodically."""
45
77
  try:
46
78
  while not self._stop_event.is_set():
47
79
  try:
48
80
  await self.async_func(*self.args, **self.kwargs)
49
81
  except Exception as e:
50
- self.logger.info(f"error in periodic_run async thread {self.thread.name} {e}")
51
- os._exit(1) # Exit the program if the thread is stopped
52
- await asyncio.sleep(self.interval)
53
- self.logger.info(f"periodic {self.thread.name} Thread stopped, exiting app.")
54
- os._exit(1) # Exit the program if the thread is stopped
82
+ # Log the error but continue the loop for periodic tasks,
83
+ # assuming the next run might succeed.
84
+ self.logger.error(f"Error in periodic_run for thread '{self.thread.name}': {type(e).__name__} - {e}", exc_info=True)
85
+ # If you want a critical failure here to stop the thread entirely for restart,
86
+ # you would set self.exception_occurred = True and then break the loop.
87
+ # For now, we'll just log and continue, allowing the manager to detect if the thread dies from other reasons.
88
+
89
+ # Check stop event again before sleeping to allow immediate stopping
90
+ #if self._stop_event.is_set():
91
+ # break
92
+
93
+ # Use asyncio.sleep to allow graceful cancellation
94
+ try:
95
+ await asyncio.sleep(self.interval)
96
+ except asyncio.CancelledError:
97
+ self.logger.info(f"Periodic sleep for '{self.thread.name}' cancelled.")
98
+ break # Exit loop if sleep is cancelled
99
+
100
+ self.logger.info(f"Periodic run for '{self.thread.name}' loop stopped.")
55
101
  except asyncio.CancelledError:
56
- pass
102
+ self.logger.info(f"Periodic run for '{self.thread.name}' task cancelled.")
103
+ except Exception as e:
104
+ self.logger.error(f"Unexpected error in periodic_run for thread '{self.thread.name}': {type(e).__name__} - {e}", exc_info=True)
105
+ self.exception_occurred = True # Mark as failed if periodic_run itself has an unhandled error
57
106
 
58
107
  def start_thread(self, thread_name=None):
59
- self.thread.name = thread_name
108
+ """Starts the underlying threading.Thread."""
109
+ self.thread.name = thread_name if thread_name else f"AsyncRunner-{id(self)}"
110
+ self.logger.info(f"Starting background thread '{self.thread.name}'.")
60
111
  self.thread.start()
61
112
 
62
- async def stop_thread(self):
63
- """Gracefully stop the async thread."""
64
- # Signal any periodic task to stop
65
- self._stop_event.set()
66
-
67
- # Cancel and await the running task
68
- if self.task:
69
- self.task.cancel()
70
- try:
71
- await asyncio.wrap_future(self.task) # Wait for the cancellation to propagate
72
- except asyncio.CancelledError:
73
- self.logger.info(f"{self.thread.name} Task cancelled successfully.")
74
- except Exception as e:
75
- self.logger.error(f"Unexpected error while cancelling the task {self.thread.name}: {e}")
113
+ def stop_thread(self):
114
+ """Gracefully signals the async thread to stop."""
115
+ self.logger.info(f"Signaling thread '{self.thread.name}' to stop.")
116
+ self._stop_event.set() # Signal the periodic task to stop
76
117
 
77
- # Stop the event loop safely
78
- self.loop.call_soon_threadsafe(self.loop.stop)
118
+ # If it's a one-off task, the loop will stop itself.
119
+ # If it's a periodic task, we need to cancel its main task to unblock run_forever.
120
+ if self.task and not self.task.done():
121
+ self.loop.call_soon_threadsafe(self.task.cancel)
122
+
123
+ # Stop the event loop if it's running
124
+ if self.loop.is_running():
125
+ self.loop.call_soon_threadsafe(self.loop.stop)
79
126
 
80
- # Wait for the thread to join
81
- self.thread.join()
82
- self.logger.info(f"{self.thread.name} Thread stopped and event loop cleaned up.")
127
+ # Wait for the thread to actually finish its execution
128
+ self.thread.join(timeout=10) # Give it some time to clean up
129
+ if self.thread.is_alive():
130
+ self.logger.warning(f"Thread '{self.thread.name}' did not terminate gracefully after join timeout.")
131
+ else:
132
+ self.logger.info(f"Thread '{self.thread.name}' successfully stopped.")
@@ -28,11 +28,10 @@ import threading
28
28
  cst = pytz.timezone('US/Central')
29
29
 
30
30
  class BitunixSignal:
31
- def __init__(self, api_key, secret_key, settings, threadManager, notifications, bitunixApi, logger):
31
+ def __init__(self, api_key, secret_key, settings, notifications, bitunixApi, logger):
32
32
  self.api_key = api_key
33
33
  self.secret_key = secret_key
34
34
  self.settings=settings
35
- self.threadManager = threadManager
36
35
  self.notifications = notifications
37
36
  self.bitunixApi = bitunixApi
38
37
  self.logger = logger
@@ -101,7 +100,7 @@ class BitunixSignal:
101
100
  self.tickerObjects.update_settings(settings)
102
101
 
103
102
  async def load_tickers(self):
104
- if self.settings.TICKERS=="":
103
+ if self.settings.LONG_TICKERS=="" and self.settings.SHORT_TICKERS=="" and self.settings.ALL_TICKERS=="":
105
104
  symbols = await self.bitunixApi.GetTickerList(float(self.settings.THRESHOLD), float(self.settings.MIN_VOLUME))
106
105
  self.pendingPositions= await self.bitunixApi.GetPendingPositionData()
107
106
  self.pendingOrders= await self.bitunixApi.GetPendingOrderData()
@@ -113,15 +112,17 @@ class BitunixSignal:
113
112
  olist = [entry['symbol'] for entry in self.pendingOrders['orderList']]
114
113
  newlist=olist+plist+list(set(symbols))
115
114
  self.tickerList=newlist[:300]
116
- if "STMXUSDT" in self.tickerList:
117
- self.tickerList.remove("STMXUSDT")
118
- if "AMBUSDT" in self.tickerList:
119
- self.tickerList.remove("AMBUSDT")
120
- #self.tickerList=['POLUSDT']
121
115
  else:
122
- self.tickerList = self.settings.TICKERS.split(",")
116
+ all_tickers = self.settings.LONG_TICKERS + "," + self.settings.SHORT_TICKERS + "," + self.settings.ALL_TICKERS;
117
+ self.tickerList = all_tickers.split(",")
123
118
  self.tickerList = [sym.strip().upper() for sym in self.tickerList if sym.strip()]
124
-
119
+
120
+ #remove ignore tickers
121
+ if self.settings.IGNORE_TICKERS!="":
122
+ ignore_tickers = self.settings.IGNORE_TICKERS.split(",")
123
+ ignore_tickers = [sym.strip().upper() for sym in ignore_tickers if sym.strip()]
124
+ self.tickerList = [sym for sym in self.tickerList if sym not in ignore_tickers]
125
+
125
126
  [await self.add_ticker_to_tickerObjects(sym) for sym in self.tickerList]
126
127
  self.notifications.add_notification(f"{len(self.tickerList)} ticker list loaded")
127
128
 
@@ -157,14 +158,14 @@ class BitunixSignal:
157
158
  self.StoreDepthDataTask = AsyncThreadRunner(self.bitunixPublicDepthWebSocketClient.run_websocket, self.logger, 0, self.StoreDepthData)
158
159
  self.StoreDepthDataTask.start_thread(thread_name="StoreDepthData")
159
160
  self.depth_que = asyncio.Queue()
160
- self.ProcessDepthDataTask = AsyncThreadRunner(self.ProcessDepthData, self.logger, interval=0) # run only once
161
+ self.ProcessDepthDataTask = AsyncThreadRunner(self.ProcessDepthData, self.logger, interval=int(self.settings.TICKER_DATA_API_INTERVAL)) # run only once
161
162
  self.ProcessDepthDataTask.start_thread(thread_name="ProcessDepthData")
162
163
 
163
164
  self.bitunixPublicTickerWebSocketClient.tickerList = self.tickerList
164
165
  self.StoreTickerDataTask = AsyncThreadRunner(self.bitunixPublicTickerWebSocketClient.run_websocket, self.logger, 0, self.StoreTickerData)
165
166
  self.StoreTickerDataTask.start_thread(thread_name="StoreTickerData")
166
167
  self.ticker_que = asyncio.Queue()
167
- self.ProcessTickerDataTask = AsyncThreadRunner(self.ProcessTickerData, self.logger, interval=0) # run only once
168
+ self.ProcessTickerDataTask = AsyncThreadRunner(self.ProcessTickerData, self.logger, interval=int(self.settings.TICKER_DATA_API_INTERVAL)) # run only once
168
169
  self.ProcessTickerDataTask.start_thread(thread_name="ProcessTickerData")
169
170
 
170
171
 
@@ -237,38 +238,39 @@ class BitunixSignal:
237
238
 
238
239
  #api data
239
240
  async def GetTickerData(self):
240
- if not self.settings.USE_PUBLIC_WEBSOCKET:
241
- start=time.time()
242
- # Get the current time and set the seconds and microseconds to zero
243
- current_time = datetime.now()
244
- current_minute = current_time.replace(second=0, microsecond=0)
245
- ts = int(current_minute.timestamp())*1000
246
-
247
- #api used insted of websocket
248
- data = await self.bitunixApi.GetTickerData()
249
- self.tickerdf = pd.DataFrame()
250
- if data:
251
-
252
- # Create a DataFrame from the data
253
- self.tickerdf = pd.DataFrame(data, columns=["symbol", "last"])
254
-
255
- #remove not required symbols
256
- self.tickerdf.loc[~self.tickerdf['symbol'].isin(self.tickerObjects.symbols()), :] = None
257
- self.tickerdf.dropna(inplace=True)
258
-
259
- self.tickerdf['ts']=ts
260
- self.tickerdf["tickerObj"] = self.tickerdf["symbol"].map(self.tickerObjects.get_tickerDict())
261
- self.tuples_list = list(zip(self.tickerdf["tickerObj"], self.tickerdf["last"].astype(float), self.tickerdf["ts"]))
262
- self.tickerObjects.form_candle(self.tuples_list)
241
+ if self.settings.VERBOSE_LOGGING:
242
+ self.logger.info(f"GetTickerData started")
243
+ start=time.time()
244
+ # Get the current time and set the seconds and microseconds to zero
245
+ current_time = datetime.now()
246
+ current_minute = current_time.replace(second=0, microsecond=0)
247
+ ts = int(current_minute.timestamp())*1000
248
+
249
+ #api used insted of websocket
250
+ data = await self.bitunixApi.GetTickerData()
251
+ self.tickerdf = pd.DataFrame()
252
+ if data:
253
+
254
+ # Create a DataFrame from the data
255
+ self.tickerdf = pd.DataFrame(data, columns=["symbol", "last"])
256
+
257
+ #remove not required symbols
258
+ self.tickerdf.loc[~self.tickerdf['symbol'].isin(self.tickerObjects.symbols()), :] = None
259
+ self.tickerdf.dropna(inplace=True)
260
+
261
+ self.tickerdf['ts']=ts
262
+ self.tickerdf["tickerObj"] = self.tickerdf["symbol"].map(self.tickerObjects.get_tickerDict())
263
+ self.tuples_list = list(zip(self.tickerdf["tickerObj"], self.tickerdf["last"].astype(float), self.tickerdf["ts"]))
264
+ self.tickerObjects.form_candle(self.tuples_list)
263
265
 
264
- self.lastTickerDataTime = time.time()
265
- if self.settings.VERBOSE_LOGGING:
266
- self.logger.info(f"GetTickerData: elapsed time {time.time()-start}")
267
- if self.settings.BENCHMARK:
268
- self.connection = sqlite3.connect(self.settings.DATABASE)
269
- self.cursor = self.connection.cursor()
270
- self.cursor.execute("INSERT INTO benchmark (process_name, time) VALUES (?, ?)", ("GetTickerData", time.time()-start))
271
- self.connection.commit()
266
+ self.lastTickerDataTime = time.time()
267
+ if self.settings.VERBOSE_LOGGING:
268
+ self.logger.info(f"GetTickerData: elapsed time {time.time()-start}")
269
+ if self.settings.BENCHMARK:
270
+ self.connection = sqlite3.connect(self.settings.DATABASE)
271
+ self.cursor = self.connection.cursor()
272
+ self.cursor.execute("INSERT INTO benchmark (process_name, time) VALUES (?, ?)", ("GetTickerData", time.time()-start))
273
+ self.connection.commit()
272
274
 
273
275
  async def drain_queue(self, queue):
274
276
  items = []
@@ -284,32 +286,34 @@ class BitunixSignal:
284
286
 
285
287
  # Function to process the last price deque
286
288
  async def ProcessTickerData(self):
287
- while True:
288
- try:
289
- latest_data = {}
290
- reversed_items = await self.drain_queue(self.ticker_que)
291
- while reversed_items:
292
- message = reversed_items.popleft()
293
- data = json.loads(message)
294
- if data.get('symbol') and data.get('ch') == 'ticker':
295
- symbol = data["symbol"]
296
- ts = data["ts"]
297
- if symbol not in latest_data or ts > latest_data[symbol]['ts']:
298
- latest_data[symbol] = {'ts': ts, 'last': float(data['data']['la'])}
299
- await asyncio.sleep(0.01)
300
- # Convert to DataFrame
301
- self.tickerdf = pd.DataFrame.from_dict(latest_data, orient="index")
302
- if not self.tickerdf.empty:
303
- self.tickerdf["tickerObj"] = self.tickerdf.index.map(self.tickerObjects.get_tickerDict())
304
- self.tuples_list = list(zip(self.tickerdf["tickerObj"], self.tickerdf["last"].astype(float), self.tickerdf["ts"]))
305
- self.tickerObjects.form_candle(self.tuples_list)
306
- self.lastTickerDataTime = time.time()
307
- except Exception as e:
308
- self.logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
309
- self.logger.info(traceback.print_exc())
310
- await asyncio.sleep(0.01)
311
- self.logger.info(f"ProcessTickerData: exitied out of the loop, exiting app")
312
- os._exit(1) # Exit the program
289
+ if self.settings.VERBOSE_LOGGING:
290
+ self.logger.info(f"Started ProcessTickerData")
291
+ start=time.time()
292
+ try:
293
+ # Get the current time and set the seconds and microseconds to zero
294
+ latest_data = {}
295
+ reversed_items = await self.drain_queue(self.ticker_que)
296
+ while reversed_items:
297
+ message = reversed_items.popleft()
298
+ data = json.loads(message)
299
+ if data.get('symbol') and data.get('ch') == 'ticker':
300
+ symbol = data["symbol"]
301
+ ts = data["ts"]
302
+ if symbol not in latest_data or ts > latest_data[symbol]['ts']:
303
+ latest_data[symbol] = {'ts': ts, 'last': float(data['data']['la'])}
304
+ await asyncio.sleep(0.01)
305
+ # Convert to DataFrame
306
+ self.tickerdf = pd.DataFrame.from_dict(latest_data, orient="index")
307
+ if not self.tickerdf.empty:
308
+ self.tickerdf["tickerObj"] = self.tickerdf.index.map(self.tickerObjects.get_tickerDict())
309
+ self.tuples_list = list(zip(self.tickerdf["tickerObj"], self.tickerdf["last"].astype(float), self.tickerdf["ts"]))
310
+ self.tickerObjects.form_candle(self.tuples_list)
311
+ self.lastTickerDataTime = time.time()
312
+ except Exception as e:
313
+ self.logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
314
+ self.logger.info(traceback.print_exc())
315
+ if self.settings.VERBOSE_LOGGING:
316
+ self.logger.info(f"ProcessTickerData: elapsed time {time.time()-start}")
313
317
 
314
318
 
315
319
  #websocket data to update bid and ask
@@ -319,30 +323,31 @@ class BitunixSignal:
319
323
 
320
324
  # Function to process the bid, ask
321
325
  async def ProcessDepthData(self):
322
- while True:
323
- try:
324
- latest_data = {}
325
- reversed_items = await self.drain_queue(self.depth_que)
326
- while reversed_items:
327
- message = reversed_items.popleft()
328
- data = json.loads(message)
329
- if data.get('symbol') and data.get('ch') == 'depth_book1':
330
- symbol = data["symbol"]
331
- ts = data["ts"]
332
- if symbol not in latest_data or ts > latest_data[symbol]['ts']:
333
- latest_data[symbol] = {'ts': ts, 'bid': float(data['data']['b'][0][0]), 'ask': float(data['data']['a'][0][0])}
334
- await asyncio.sleep(0.01)
335
- # Convert to DataFrame
336
- self.depthdf = pd.DataFrame.from_dict(latest_data, orient="index")
337
- if not self.depthdf.empty:
338
- self.depthdf["tickerObj"] = self.depthdf.index.map(self.tickerObjects.get_tickerDict())
339
- self.depthdf.apply(self.apply_depth_data2, axis=1)
340
- except Exception as e:
341
- self.logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
342
- self.logger.info(traceback.print_exc())
343
- await asyncio.sleep(0.01)
344
- self.logger.info(f"ProcessDepthData: exitied out of the loop, exiting app")
345
- os._exit(1) # Exit the program
326
+ if self.settings.VERBOSE_LOGGING:
327
+ self.logger.info(f"Started ProcessDepthData")
328
+ start=time.time()
329
+ try:
330
+ latest_data = {}
331
+ reversed_items = await self.drain_queue(self.depth_que)
332
+ while reversed_items:
333
+ message = reversed_items.popleft()
334
+ data = json.loads(message)
335
+ if data.get('symbol') and data.get('ch') == 'depth_book1':
336
+ symbol = data["symbol"]
337
+ ts = data["ts"]
338
+ if symbol not in latest_data or ts > latest_data[symbol]['ts']:
339
+ latest_data[symbol] = {'ts': ts, 'bid': float(data['data']['b'][0][0]), 'ask': float(data['data']['a'][0][0])}
340
+ await asyncio.sleep(0.01)
341
+ # Convert to DataFrame
342
+ self.depthdf = pd.DataFrame.from_dict(latest_data, orient="index")
343
+ if not self.depthdf.empty:
344
+ self.depthdf["tickerObj"] = self.depthdf.index.map(self.tickerObjects.get_tickerDict())
345
+ self.depthdf.apply(self.apply_depth_data2, axis=1)
346
+ except Exception as e:
347
+ self.logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
348
+ self.logger.info(traceback.print_exc())
349
+ if self.settings.VERBOSE_LOGGING:
350
+ self.logger.info(f"ProcessDepthData: elapsed time {time.time()-start}")
346
351
 
347
352
  def apply_depth_data2(self, row):
348
353
  row["tickerObj"].set_ask(row["ask"])
@@ -399,11 +404,11 @@ class BitunixSignal:
399
404
  ###########################################################################################################
400
405
  async def checkTickerAndAutotradeStatus(self):
401
406
  while True:
402
- if self.lastAutoTradeTime + 300 < time.time(): # or self.lastTickerDataTime + 300 < time.time():
407
+ if self.lastAutoTradeTime + 1500 < time.time() or self.lastTickerDataTime + 1500 < time.time():
403
408
  self.notifications.add_notification("AutoTradeProcess or GetTickerData is not running")
404
409
  os._exit(1)
405
410
  break
406
- await asyncio.sleep(300)
411
+ await asyncio.sleep(1500)
407
412
 
408
413
  async def GetportfolioData(self):
409
414
  start=time.time()
@@ -650,14 +655,16 @@ class BitunixSignal:
650
655
  trade_time = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
651
656
  # Convert datetime object to Unix timestamp)
652
657
  trade_time_unix = int(trade_time.timestamp())
653
- #print(f"trade_time_unix: {trade_time_unix}, trade_time: {trade_time}")
658
+ print(f"last_trade_time: {trade_time}, last_trade_time_unix: {trade_time_unix}")
654
659
  # Get current Unix timestamp
655
- current_time_unix = int(time.time())
656
- #print(f"current_time_unix: {current_time_unix}, time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
660
+ current_time = time.time()
661
+ current_time_unix = int(current_time)
662
+ formatted_time = datetime.fromtimestamp(current_time).strftime("%y-%m-%d %H:%M:%S")
663
+ print(f"current_time: {formatted_time}, current_time_unix: {current_time_unix}")
657
664
  # Calculate duration in minutes
658
665
  duration_minutes = (current_time_unix - trade_time_unix) / 60
659
666
  #print(f"diff: {current_time_unix - trade_time_unix}")
660
- #print(f"duration_minutes: {duration_minutes}")
667
+ print(f"duration_minutes: {duration_minutes}")
661
668
 
662
669
  return round(duration_minutes)
663
670
 
@@ -722,14 +729,20 @@ class BitunixSignal:
722
729
  # Calculate the duration in minutes since the position was opened
723
730
  data = await self.bitunixApi.GetPositionHistoryData({'symbol': row['symbol']})
724
731
  duration_minutes = None
732
+ mtime = None
725
733
  if data and 'positionList' in data and len(data['positionList']) > 0:
726
734
  xtime = float(data['positionList'][0]['mtime'])
727
735
  mtime = pd.to_datetime(xtime, unit='ms').tz_localize('UTC').tz_convert(cst).strftime('%Y-%m-%d %H:%M:%S')
728
736
  duration_minutes = await self.get_duration(mtime)
729
- self.logger.info(f"row.symbol: {row.symbol} , duration_minutes: {duration_minutes}, last trade time: {mtime if duration_minutes is not None else 'N/A'}")
737
+ self.logger.info(f"row.symbol: {row.symbol} , last trade time: {mtime}, duration_minutes: {duration_minutes}")
730
738
  if duration_minutes is None or duration_minutes > self.settings.DELAY_IN_MINUTES_FOR_SAME_TICKER_TRADES:
731
739
  side = "BUY" if row[f'{period}_barcolor'] == self.green and row[f'{period}_trend'] == "BUY" else "SELL" if row[f'{period}_barcolor'] == self.red and row[f'{period}_trend'] == "SELL" else ""
732
- #self.logger.info(f"row.symbol: {row.symbol} , ok to {side}, {row[f'{period}_barcolor']} , {row[f'{period}_trend']} ")
740
+ if row['symbol'] in self.settings.LONG_TICKERS.split(","):
741
+ side = "BUY"
742
+ self.logger.info(f"row.symbol: {row.symbol} , will always be long")
743
+ if row['symbol'] in self.settings.SHORT_TICKERS.split(","):
744
+ side = "SELL"
745
+ self.logger.info(f"row.symbol: {row.symbol} , will always be short")
733
746
  if side != "":
734
747
  select = True
735
748
  self.pendingPositions = await self.bitunixApi.GetPendingPositionData({'symbol': row.symbol})
@@ -875,69 +888,70 @@ class BitunixSignal:
875
888
  tporderId = order['id']
876
889
  tpStopType = order['tpStopType']
877
890
  tpOrderType = order['tpOrderType']
878
- tpPrice = float(order['tpPrice'])
879
- old_tpPrice = tpPrice
891
+ old_tpPrice = float(order['tpPrice'])
880
892
  if order['slPrice'] is not None:
881
893
  slorderId = order['id']
882
894
  slStopType = order['slStopType']
883
895
  slOrderType = order['slOrderType']
884
- slPrice = float(order['slPrice'])
885
- old_slPrice = slPrice
896
+ old_slPrice = float(order['slPrice'])
886
897
 
887
898
  # move TP and SL in the direction of profit
888
899
  tp_midpoint = None
889
- sl_midpoint = None
890
- if self.settings.BOT_TRAIL_TP:
891
- if old_tpPrice is not None:
892
- tp_midpoint = old_tpPrice / (1 + self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else old_tpPrice / (1 - self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE) if tpPrice is not None else None
893
- if tp_midpoint is not None and (price > tp_midpoint and side == "BUY" or price < tp_midpoint and side == "SELL"):
900
+ if self.settings.BOT_TRAIL_TP and old_tpPrice is not None:
901
+ tp_midpoint = old_tpPrice / (1 + self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else old_tpPrice / (1 - self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE)
902
+ tp_midpoint = Decimal(await self.str_precision(tp_midpoint))
903
+ tp_midpoint =float(str(tp_midpoint.quantize(Decimal(f'1e-{decimal_places}'))))
894
904
 
905
+ if price > tp_midpoint and side == "BUY" or price < tp_midpoint and side == "SELL":
895
906
  tpPrice = price * (1 + float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE) if side == "BUY" else price * (1 - float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE)
896
907
  tpPrice = Decimal(await self.str_precision(tpPrice))
897
908
  tpPrice =float(str(tpPrice.quantize(Decimal(f'1e-{decimal_places}'))))
898
909
  tpOrderPrice = await self.decrement_by_last_decimal(str(tpPrice))
899
910
 
900
-
901
- if self.settings.BOT_TRAIL_SL:
902
- if old_slPrice is not None:
903
- sl_midpoint = old_slPrice / (1 - self.settings.LOSS_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else old_slPrice / (1 + self.settings.LOSS_PERCENTAGE/100/self.settings.LEVERAGE) if tpPrice is not None else None
911
+ if tpPrice is not None:
912
+ self.notifications.add_notification(
913
+ f'{colors.CYAN} {row.symbol} avgOpenPrice: {avgOpenPrice}, current price: {price}, ROI: {roi}%, old_TP: {old_tpPrice}, TP midpoint: {tp_midpoint}, TP: {tpPrice}'
914
+ )
915
+ if old_tpPrice < tpPrice if side== "BUY" else old_tpPrice > tpPrice:
916
+ if old_tpPrice is None or tpPrice != old_tpPrice:
917
+ datajs2 = await self.bitunixApi.ModifyTpSlOrder({'orderId':tporderId,'tpPrice':str(tpPrice), 'tpOrderPrice':str(tpOrderPrice), 'tpQty':str(qty),'tpStopType':tpStopType,'tpOrderType':tpOrderType})
918
+ if datajs2 is not None:
919
+ self.notifications.add_notification(
920
+ f'{colors.CYAN} Take Profit order for {row.symbol} moved from {old_tpPrice} to {tpPrice}'
921
+ )
922
+
923
+ breakeven_calc = False
924
+ sl_midpoint = None
925
+ if self.settings.BOT_TRAIL_SL and old_slPrice is not None:
926
+ if roi > self.settings.PROFIT_PERCENTAGE * self.settings.SL_BREAKEVEN_PERCENTAGE/100 and self.settings.PROFIT_PERCENTAGE < self.settings.LOSS_PERCENTAGE:
927
+ sl_midpoint = old_slPrice / (1 - self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else old_slPrice / (1 + self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE)
928
+ breakeven_calc = True
929
+ else:
930
+ sl_midpoint = old_slPrice / (1 - self.settings.LOSS_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else old_slPrice / (1 + self.settings.LOSS_PERCENTAGE/100/self.settings.LEVERAGE)
931
+ sl_midpoint = Decimal(await self.str_precision(sl_midpoint))
932
+ sl_midpoint =float(str(sl_midpoint.quantize(Decimal(f'1e-{decimal_places}'))))
904
933
 
905
- if sl_midpoint is not None and (price > sl_midpoint and side == "BUY" or price < sl_midpoint and side == "SELL"):
906
- if roi > self.settings.PROFIT_PERCENTAGE * 0.75 and self.settings.PROFIT_PERCENTAGE < self.settings.LOSS_PERCENTAGE:
907
- slPrice = price * (1 - float(self.settings.PROFIT_PERCENTAGE * 0.75) / 100 / self.settings.LEVERAGE) if side == "BUY" else avgOpenPrice * (1 + float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE)
934
+ if price > sl_midpoint and side == "BUY" or price < sl_midpoint and side == "SELL":
935
+ if breakeven_calc:
936
+ slPrice = price * (1 - float(self.settings.PROFIT_PERCENTAGE) /100 /self.settings.LEVERAGE) if side == "BUY" else price * (1 + float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE)
937
+ if slPrice < avgOpenPrice and side == "BUY" or slPrice > avgOpenPrice and side == "SELL":
938
+ slPrice = avgOpenPrice
908
939
  else:
909
940
  slPrice = price * (1 - float(self.settings.LOSS_PERCENTAGE) / 100 / self.settings.LEVERAGE) if side == "BUY" else price * (1 + float(self.settings.LOSS_PERCENTAGE) / 100 / self.settings.LEVERAGE)
910
941
  slPrice = Decimal(await self.str_precision(slPrice))
911
942
  slPrice = float(str(slPrice.quantize(Decimal(f'1e-{decimal_places}'))))
912
943
  slOrderPrice = await self.increment_by_last_decimal(await self.str_precision(slPrice))
913
944
 
914
- if (self.settings.BOT_TRAIL_TP and tpOrderPrice is not None) or (self.settings.BOT_TRAIL_SL and slOrderPrice is not None):
915
- self.notifications.add_notification(
916
- f'{colors.CYAN} {row.symbol} avgOpenPrice: {avgOpenPrice}, current price: {price}, ROI: {roi}%, TP: {old_tpPrice}, TP midpoint: {tp_midpoint}, new TP: {tpPrice}, SL: {old_slPrice}, SL midpoint: {sl_midpoint}, new SL: {slPrice}'
917
- )
918
-
919
-
920
- if self.settings.BOT_TRAIL_TP and tpPrice is not None and tpOrderPrice is not None:
921
- if old_tpPrice is None or tpPrice != old_tpPrice:
922
- datajs2 = await self.bitunixApi.ModifyTpSlOrder({'orderId':tporderId,'tpPrice':str(tpPrice), 'tpOrderPrice':str(tpOrderPrice), 'tpQty':str(qty),'tpStopType':tpStopType,'tpOrderType':tpOrderType})
923
- if datajs2 is not None:
924
- self.notifications.add_notification(
925
- f'{colors.CYAN} Take Profit order for {row.symbol} moved from {old_tpPrice} to {tpPrice}'
926
- )
927
-
928
- if self.settings.BOT_TRAIL_SL and slPrice is not None and slOrderPrice is not None:
929
- if old_slPrice is None or slPrice != old_slPrice:
930
- datajs3 = await self.bitunixApi.ModifyTpSlOrder({'orderId':slorderId,'slPrice':str(slPrice),'slQty':str(qty),'slStopType':slStopType,'slOrderType':slOrderType})
931
- if datajs3 is not None:
932
- if roi > self.settings.PROFIT_PERCENTAGE * 0.75 and self.settings.PROFIT_PERCENTAGE < self.settings.LOSS_PERCENTAGE:
945
+ if slPrice is not None:
946
+ self.notifications.add_notification(
947
+ f'{colors.CYAN} {row.symbol} avgOpenPrice: {avgOpenPrice}, current price: {price}, ROI: {roi}%, old_SL: {old_slPrice}, SL midpoint: {sl_midpoint}, SL: {slPrice}'
948
+ )
949
+ if old_slPrice < slPrice if side== "BUY" else old_slPrice > slPrice:
950
+ datajs3 = await self.bitunixApi.ModifyTpSlOrder({'orderId':slorderId,'slPrice':str(slPrice),'slQty':str(qty),'slStopType':slStopType,'slOrderType':slOrderType})
951
+ if datajs3 is not None:
933
952
  self.notifications.add_notification(
934
- f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to breakeven {slPrice}'
953
+ f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to {"profitable" if breakeven_calc else ""} {slPrice}'
935
954
  )
936
- else:
937
- self.notifications.add_notification(
938
- f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to {slPrice}'
939
- )
940
-
941
955
 
942
956
 
943
957
  if self.settings.BOT_CONTROLS_TP_SL:
@@ -330,8 +330,9 @@ class Interval:
330
330
 
331
331
  buy_conditions = ema_open or macd_open or bbm_open or rsi_open or bos_open or trendline_open
332
332
  additional_buy_conditions = (
333
- (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
334
- (not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BULLISH")
333
+ (self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN else True
334
+ ) and (
335
+ (self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN and self.candle_trend == "BULLISH") if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN else True
335
336
  )
336
337
 
337
338
 
@@ -345,8 +346,9 @@ class Interval:
345
346
 
346
347
  sell_conditions = ema_close or macd_close or bbm_close or rsi_close or bos_close or trendline_close
347
348
  additional_sell_conditions = (
348
- (self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") and
349
- (self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BEARISH")
349
+ (self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN else True
350
+ ) and (
351
+ (self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN and self.candle_trend == "BEARISH") if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN else True
350
352
  )
351
353
 
352
354
  # Determine current signal
@@ -372,8 +374,9 @@ class Interval:
372
374
 
373
375
  buy_conditions = ema_open and macd_open and bbm_open and rsi_open and bos_open and trendline_open
374
376
  additional_buy_conditions = (
375
- (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
376
- (not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BULLISH")
377
+ (self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN else True
378
+ ) and (
379
+ (self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN and self.candle_trend == "BULLISH") if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN else True
377
380
  )
378
381
 
379
382
  # Check for SELL signal
@@ -392,8 +395,9 @@ class Interval:
392
395
 
393
396
  sell_conditions = ema_close and macd_close and bbm_close and rsi_close and bos_close and trendline_close
394
397
  additional_sell_conditions = (
395
- (self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") or
396
- (self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BEARISH")
398
+ (self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN else True
399
+ ) and (
400
+ (self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN and self.candle_trend == "BEARISH") if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN else True
397
401
  )
398
402
 
399
403
  # Determine current signal
@@ -12,7 +12,6 @@ from pathlib import Path
12
12
  import re
13
13
 
14
14
 
15
- from ThreadManager import ThreadManager
16
15
  from BitunixApi import BitunixApi
17
16
  from BitunixSignal import BitunixSignal
18
17
  from NotificationManager import NotificationManager
@@ -75,10 +74,9 @@ class bitunix():
75
74
  self.logger=logger
76
75
  self.autoTrade=settings.AUTOTRADE
77
76
  self.settings = settings
78
- self.threadManager = ThreadManager()
79
77
  self.notifications = NotificationManager(logger)
80
78
  self.bitunixApi = BitunixApi(api_key, secret_key, settings, self.logger)
81
- self.bitunixSignal = BitunixSignal(api_key, secret_key, settings, self.threadManager, self.notifications, self.bitunixApi, self.logger)
79
+ self.bitunixSignal = BitunixSignal(api_key, secret_key, settings, self.notifications, self.bitunixApi, self.logger)
82
80
 
83
81
  self.websocket_connections = set()
84
82
  self.DB = {"admin": {"password": password}}
@@ -9,7 +9,10 @@ class Settings(BaseSettings):
9
9
  AUTOTRADE: bool = Field(default=False)
10
10
 
11
11
  #Ticker list
12
- TICKERS:str = Field(default="")
12
+ ALL_TICKERS:str = Field(default="")
13
+ LONG_TICKERS:str = Field(default="")
14
+ SHORT_TICKERS:str = Field(default="")
15
+ IGNORE_TICKERS:str = Field(default="")
13
16
  THRESHOLD: float = Field(default=100.0, ge=0.0)
14
17
  MIN_VOLUME: int = Field(default=500000000, ge=0)
15
18
 
@@ -19,6 +22,7 @@ class Settings(BaseSettings):
19
22
  MAX_AUTO_TRADES: int = Field(default=10, ge=0)
20
23
  PROFIT_PERCENTAGE: float = Field(default=10, ge=0.0)
21
24
  LOSS_PERCENTAGE: float = Field(default=10, ge=0.0)
25
+ SL_BREAKEVEN_PERCENTAGE: float = Field(default=50, ge=0.0, le=100.0)
22
26
  PROFIT_AMOUNT: float = Field(default=0, ge=0.0)
23
27
  LOSS_AMOUNT: float = Field(default=5.0, ge=0.0)
24
28
  BOT_CONTROLS_TP_SL: bool = Field(default=False)
@@ -1 +1 @@
1
- __version__ = "3.3.8"
1
+ __version__ = "3.4.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bitunix_automated_crypto_trading
3
- Version: 3.3.8
3
+ Version: 3.4.0
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=tnnLqU2EaD-XPRghFrFeKiZBWDxbGaos_ozrh20_NfA,6504
2
+ bitunix_automated_crypto_trading/BitunixApi.py,sha256=5qg-K5IcsAbb6K1feP9zL7RzSz7JzdGAxoY8R_YWxWE,15666
3
+ bitunix_automated_crypto_trading/BitunixSignal.py,sha256=VMfJEKiJbt1UOwi6gsQ4mAidmKs4NCzs3V7CrZaRua4,92377
4
+ bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=uiqAcis3u-ct07tjaTiC87ujzvcAtVRZ31CMiTBDW_M,11309
5
+ bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
6
+ bitunix_automated_crypto_trading/NotificationManager.py,sha256=exs6REABBA1omTeTGuUuECzxs5dGqdyL7oI8WyxS6Xc,798
7
+ bitunix_automated_crypto_trading/SupportResistance.py,sha256=x_to4M4OHg0h8o40DXDBa4E_5io-y2Lb5qo2VzFnu_8,5765
8
+ bitunix_automated_crypto_trading/TickerManager.py,sha256=2eQNeSUDPya39oBL-ObyaVUYpzn0q9ykk7q93-A7S1k,42762
9
+ bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
10
+ bitunix_automated_crypto_trading/bitunix.py,sha256=dlf5m0964JW6-9V-xFWv0_4C3OWJ6yTq22E4qWWsGSI,27038
11
+ bitunix_automated_crypto_trading/config.py,sha256=AlcB0mmwOPfVWuiPrEiexk_JK9qhM2l9sZIdPBtNvqM,6146
12
+ bitunix_automated_crypto_trading/logger.py,sha256=NHnA5JZdUFkTAhB7i-1iCAwrdf1fxhDuRvJUkbKPi9Y,2923
13
+ bitunix_automated_crypto_trading/version.py,sha256=DKmNcG3UtnZIAj5xty7yLrIQp4fPaaT1yvrbtln45_8,21
14
+ bitunix_automated_crypto_trading-3.4.0.dist-info/METADATA,sha256=dK1dcaufdtHdO4c5OadX3XpKhF-mMAh7N5nxtSIZQME,996
15
+ bitunix_automated_crypto_trading-3.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ bitunix_automated_crypto_trading-3.4.0.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
17
+ bitunix_automated_crypto_trading-3.4.0.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
18
+ bitunix_automated_crypto_trading-3.4.0.dist-info/RECORD,,
@@ -1,70 +0,0 @@
1
- #usage
2
- #start threads
3
- #self.threadManager.start_thread("GetportfolioData", 1, self.GetportfolioData)
4
- #self.threadManager.start_thread("GetPendingPositionData", 0, self.GetPendingPositionData)
5
- #self.threadManager.start_thread("GetOrderData", 1, self.GetPendingOrderData)
6
- #self.threadManager.start_thread("GetTradeHistoryData", 1, self.GetTradeHistoryData)
7
- #self.threadManager.start_thread("BuySellList", 60, self.BuySellList)
8
- #self.threadManager.start_thread("AutoTradeProcess", 60, self.AutoTradeProcess)
9
-
10
- import time
11
- import threading
12
- from logger import Logger
13
- global LOG_FILE
14
-
15
- def job_func(*args, **kwargs):
16
- print(f"Job running with arguments: {args}, {kwargs}")
17
-
18
- def run_threaded(job_func, args):
19
- job_thread = threading.Thread(target=job_func, args=args)
20
- job_thread.start()
21
-
22
- class ManagedThread(threading.Thread):
23
- def __init__(self, interval, target, logger, *args, **kwargs):
24
- super().__init__()
25
- self.target = target
26
- self.interval = interval
27
- self.args = args
28
- self.kwargs = kwargs
29
- self._stop_event = threading.Event()
30
- self.logger = logger
31
-
32
- def run(self):
33
- while not self._stop_event.is_set():
34
- stime=time.time()
35
- try:
36
- self.target(self, *self.args, **self.kwargs)
37
- except Exception as e:
38
- self.logger.info(f"error in thread {self.name}, {e}, {e.args}, {type(e).__name__}")
39
- elapsed_time = time.time() - stime
40
- if self.interval==0:
41
- break
42
- time_to_wait = max(0.05, self.interval - elapsed_time)
43
- time.sleep(time_to_wait)
44
-
45
- def stop(self):
46
- self._stop_event.set()
47
-
48
- def should_stop(self):
49
- return self._stop_event.is_set()
50
-
51
- class ThreadManager:
52
- def __init__(self):
53
- self.threads = []
54
-
55
- def start_thread(self, name, interval, thread, *args, **kwargs):
56
- thread = ManagedThread(interval, thread, *args, **kwargs)
57
- thread.name = name
58
- thread.start()
59
- self.threads.append(thread)
60
- thread.join(interval)
61
- return thread
62
-
63
- def stop_all_threads(self):
64
- for thread in self.threads:
65
- thread.stop()
66
- for thread in self.threads:
67
- thread.join()
68
- self.threads = []
69
-
70
- threadManager = ThreadManager()
@@ -1,19 +0,0 @@
1
- bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=bNIM_1xRYQOFEsIn74EX6qVpC59-GMhhr2CeiPr_GWg,3253
2
- bitunix_automated_crypto_trading/BitunixApi.py,sha256=5qg-K5IcsAbb6K1feP9zL7RzSz7JzdGAxoY8R_YWxWE,15666
3
- bitunix_automated_crypto_trading/BitunixSignal.py,sha256=nMdHsWqtnrHcem_OlC4k8gbI2KDrJKuivzTG13g-udI,91104
4
- bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=uiqAcis3u-ct07tjaTiC87ujzvcAtVRZ31CMiTBDW_M,11309
5
- bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
6
- bitunix_automated_crypto_trading/NotificationManager.py,sha256=exs6REABBA1omTeTGuUuECzxs5dGqdyL7oI8WyxS6Xc,798
7
- bitunix_automated_crypto_trading/SupportResistance.py,sha256=x_to4M4OHg0h8o40DXDBa4E_5io-y2Lb5qo2VzFnu_8,5765
8
- bitunix_automated_crypto_trading/ThreadManager.py,sha256=Lw5_1EIT0m3AFSv5CIMpnjtA0DnNw2qQ6JtSpT34LyM,2349
9
- bitunix_automated_crypto_trading/TickerManager.py,sha256=tYzO7-vxb-60a-yvsRDC4w9GNdTsqWwy8DxbagtsqAc,42023
10
- bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
11
- bitunix_automated_crypto_trading/bitunix.py,sha256=lxwnYARxldA2oU6GdjupilXIlnUh4RX8rQLCOn7x13I,27143
12
- bitunix_automated_crypto_trading/config.py,sha256=2K-5NzWoVUaU3O2tgi4qt-3dA_sCC6Nm3vFQBZrO5y8,5943
13
- bitunix_automated_crypto_trading/logger.py,sha256=NHnA5JZdUFkTAhB7i-1iCAwrdf1fxhDuRvJUkbKPi9Y,2923
14
- bitunix_automated_crypto_trading/version.py,sha256=37tflRBXps5ly3x-d0V5MX2DfwsqrPwDzFH9EVchsG4,21
15
- bitunix_automated_crypto_trading-3.3.8.dist-info/METADATA,sha256=HXT-qw3CG4V7GtZlr-vdv86LP6p_o-qxhamOgQH7354,996
16
- bitunix_automated_crypto_trading-3.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- bitunix_automated_crypto_trading-3.3.8.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
18
- bitunix_automated_crypto_trading-3.3.8.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
19
- bitunix_automated_crypto_trading-3.3.8.dist-info/RECORD,,