bitunix-automated-crypto-trading 3.3.7__py3-none-any.whl → 3.3.9__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
 
@@ -309,7 +310,7 @@ class BitunixSignal:
309
310
  self.logger.info(traceback.print_exc())
310
311
  await asyncio.sleep(0.01)
311
312
  self.logger.info(f"ProcessTickerData: exitied out of the loop, exiting app")
312
- os._exit(1) # Exit the program
313
+ #os._exit(1) # Exit the program
313
314
 
314
315
 
315
316
  #websocket data to update bid and ask
@@ -342,7 +343,7 @@ class BitunixSignal:
342
343
  self.logger.info(traceback.print_exc())
343
344
  await asyncio.sleep(0.01)
344
345
  self.logger.info(f"ProcessDepthData: exitied out of the loop, exiting app")
345
- os._exit(1) # Exit the program
346
+ #os._exit(1) # Exit the program
346
347
 
347
348
  def apply_depth_data2(self, row):
348
349
  row["tickerObj"].set_ask(row["ask"])
@@ -399,11 +400,11 @@ class BitunixSignal:
399
400
  ###########################################################################################################
400
401
  async def checkTickerAndAutotradeStatus(self):
401
402
  while True:
402
- if self.lastAutoTradeTime + 300 < time.time() or self.lastTickerDataTime + 300 < time.time():
403
+ if self.lastAutoTradeTime + 1500 < time.time() or self.lastTickerDataTime + 1500 < time.time():
403
404
  self.notifications.add_notification("AutoTradeProcess or GetTickerData is not running")
404
405
  os._exit(1)
405
406
  break
406
- await asyncio.sleep(300)
407
+ await asyncio.sleep(1500)
407
408
 
408
409
  async def GetportfolioData(self):
409
410
  start=time.time()
@@ -650,14 +651,16 @@ class BitunixSignal:
650
651
  trade_time = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
651
652
  # Convert datetime object to Unix timestamp)
652
653
  trade_time_unix = int(trade_time.timestamp())
653
- #print(f"trade_time_unix: {trade_time_unix}, trade_time: {trade_time}")
654
+ print(f"last_trade_time: {trade_time}, last_trade_time_unix: {trade_time_unix}")
654
655
  # 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')}")
656
+ current_time = time.time()
657
+ current_time_unix = int(current_time)
658
+ formatted_time = datetime.fromtimestamp(current_time).strftime("%y-%m-%d %H:%M:%S")
659
+ print(f"current_time: {formatted_time}, current_time_unix: {current_time_unix}")
657
660
  # Calculate duration in minutes
658
661
  duration_minutes = (current_time_unix - trade_time_unix) / 60
659
662
  #print(f"diff: {current_time_unix - trade_time_unix}")
660
- #print(f"duration_minutes: {duration_minutes}")
663
+ print(f"duration_minutes: {duration_minutes}")
661
664
 
662
665
  return round(duration_minutes)
663
666
 
@@ -722,14 +725,20 @@ class BitunixSignal:
722
725
  # Calculate the duration in minutes since the position was opened
723
726
  data = await self.bitunixApi.GetPositionHistoryData({'symbol': row['symbol']})
724
727
  duration_minutes = None
728
+ mtime = None
725
729
  if data and 'positionList' in data and len(data['positionList']) > 0:
726
730
  xtime = float(data['positionList'][0]['mtime'])
727
731
  mtime = pd.to_datetime(xtime, unit='ms').tz_localize('UTC').tz_convert(cst).strftime('%Y-%m-%d %H:%M:%S')
728
732
  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'}")
733
+ self.logger.info(f"row.symbol: {row.symbol} , last trade time: {mtime}, duration_minutes: {duration_minutes}")
730
734
  if duration_minutes is None or duration_minutes > self.settings.DELAY_IN_MINUTES_FOR_SAME_TICKER_TRADES:
731
735
  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']} ")
736
+ if row['symbol'] in self.settings.LONG_TICKERS.split(","):
737
+ side = "BUY"
738
+ self.logger.info(f"row.symbol: {row.symbol} , will always be long")
739
+ if row['symbol'] in self.settings.SHORT_TICKERS.split(","):
740
+ side = "SELL"
741
+ self.logger.info(f"row.symbol: {row.symbol} , will always be short")
733
742
  if side != "":
734
743
  select = True
735
744
  self.pendingPositions = await self.bitunixApi.GetPendingPositionData({'symbol': row.symbol})
@@ -875,69 +884,68 @@ class BitunixSignal:
875
884
  tporderId = order['id']
876
885
  tpStopType = order['tpStopType']
877
886
  tpOrderType = order['tpOrderType']
878
- tpPrice = float(order['tpPrice'])
879
- old_tpPrice = tpPrice
887
+ old_tpPrice = float(order['tpPrice'])
880
888
  if order['slPrice'] is not None:
881
889
  slorderId = order['id']
882
890
  slStopType = order['slStopType']
883
891
  slOrderType = order['slOrderType']
884
- slPrice = float(order['slPrice'])
885
- old_slPrice = slPrice
892
+ old_slPrice = float(order['slPrice'])
886
893
 
887
894
  # move TP and SL in the direction of profit
888
895
  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"):
896
+ if self.settings.BOT_TRAIL_TP and old_tpPrice is not None:
897
+ 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)
898
+ tp_midpoint = Decimal(await self.str_precision(tp_midpoint))
899
+ tp_midpoint =float(str(tp_midpoint.quantize(Decimal(f'1e-{decimal_places}'))))
894
900
 
901
+ if price > tp_midpoint and side == "BUY" or price < tp_midpoint and side == "SELL":
895
902
  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
903
  tpPrice = Decimal(await self.str_precision(tpPrice))
897
904
  tpPrice =float(str(tpPrice.quantize(Decimal(f'1e-{decimal_places}'))))
898
905
  tpOrderPrice = await self.decrement_by_last_decimal(str(tpPrice))
899
906
 
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
904
- if roi > self.settings.PROFIT_PERCENTAGE:
905
- sl_midpoint = tp_midpoint
907
+ if tpPrice is not None:
908
+ self.notifications.add_notification(
909
+ f'{colors.CYAN} {row.symbol} avgOpenPrice: {avgOpenPrice}, current price: {price}, ROI: {roi}%, old_TP: {old_tpPrice}, TP midpoint: {tp_midpoint}, TP: {tpPrice}'
910
+ )
911
+ if old_tpPrice < tpPrice if side== "BUY" else old_tpPrice > tpPrice:
912
+ if old_tpPrice is None or tpPrice != old_tpPrice:
913
+ datajs2 = await self.bitunixApi.ModifyTpSlOrder({'orderId':tporderId,'tpPrice':str(tpPrice), 'tpOrderPrice':str(tpOrderPrice), 'tpQty':str(qty),'tpStopType':tpStopType,'tpOrderType':tpOrderType})
914
+ if datajs2 is not None:
915
+ self.notifications.add_notification(
916
+ f'{colors.CYAN} Take Profit order for {row.symbol} moved from {old_tpPrice} to {tpPrice}'
917
+ )
918
+
919
+ breakeven_calc = False
920
+ sl_midpoint = None
921
+ if self.settings.BOT_TRAIL_SL and old_slPrice is not None:
922
+ if roi > self.settings.PROFIT_PERCENTAGE * self.settings.SL_BREAKEVEN_PERCENTAGE/100 and self.settings.PROFIT_PERCENTAGE < self.settings.LOSS_PERCENTAGE:
923
+ 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)
924
+ breakeven_calc = True
925
+ else:
926
+ 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)
927
+ sl_midpoint = Decimal(await self.str_precision(sl_midpoint))
928
+ sl_midpoint =float(str(sl_midpoint.quantize(Decimal(f'1e-{decimal_places}'))))
906
929
 
907
- if sl_midpoint is not None and (price > sl_midpoint and side == "BUY" or price < sl_midpoint and side == "SELL"):
908
- if roi > self.settings.PROFIT_PERCENTAGE and self.settings.PROFIT_PERCENTAGE < self.settings.LOSS_PERCENTAGE:
909
- 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)
930
+ if price > sl_midpoint and side == "BUY" or price < sl_midpoint and side == "SELL":
931
+ if breakeven_calc:
932
+ 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)
910
933
  else:
911
934
  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)
912
935
  slPrice = Decimal(await self.str_precision(slPrice))
913
936
  slPrice = float(str(slPrice.quantize(Decimal(f'1e-{decimal_places}'))))
914
937
  slOrderPrice = await self.increment_by_last_decimal(await self.str_precision(slPrice))
915
938
 
916
- if (self.settings.BOT_TRAIL_TP and tpOrderPrice is not None) or (self.settings.BOT_TRAIL_SL and slOrderPrice is not None):
917
- self.notifications.add_notification(
918
- 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}'
919
- )
920
-
921
-
922
- if self.settings.BOT_TRAIL_TP and tpPrice is not None and tpOrderPrice is not None:
923
- datajs2 = await self.bitunixApi.ModifyTpSlOrder({'orderId':tporderId,'tpPrice':str(tpPrice), 'tpOrderPrice':str(tpOrderPrice), 'tpQty':str(qty),'tpStopType':tpStopType,'tpOrderType':tpOrderType})
924
- if datajs2 is not None:
925
- self.notifications.add_notification(
926
- f'{colors.CYAN} Take Profit order for {row.symbol} moved from {old_tpPrice} to {tpPrice}'
927
- )
928
-
929
- if self.settings.BOT_TRAIL_SL and slPrice is not None and slOrderPrice is not None:
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 and self.settings.PROFIT_PERCENTAGE < self.settings.LOSS_PERCENTAGE:
939
+ if slPrice is not None:
940
+ self.notifications.add_notification(
941
+ f'{colors.CYAN} {row.symbol} avgOpenPrice: {avgOpenPrice}, current price: {price}, ROI: {roi}%, old_SL: {old_slPrice}, SL midpoint: {sl_midpoint}, SL: {slPrice}'
942
+ )
943
+ if old_slPrice < slPrice if side== "BUY" else old_slPrice > slPrice:
944
+ datajs3 = await self.bitunixApi.ModifyTpSlOrder({'orderId':slorderId,'slPrice':str(slPrice),'slQty':str(qty),'slStopType':slStopType,'slOrderType':slOrderType})
945
+ if datajs3 is not None:
933
946
  self.notifications.add_notification(
934
- f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to breakeven {slPrice}'
947
+ f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to {"break even" if breakeven_calc else ""} {slPrice}'
935
948
  )
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
949
 
942
950
 
943
951
  if self.settings.BOT_CONTROLS_TP_SL:
@@ -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.7"
1
+ __version__ = "3.3.9"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bitunix_automated_crypto_trading
3
- Version: 3.3.7
3
+ Version: 3.3.9
4
4
  Summary: Bitunix Futures Auto Trading Platform
5
5
  Home-page: https://github.com/tcj2001/bitunix-automated-crypto-trading
6
6
  Author: tcj2001
@@ -1,19 +1,18 @@
1
- bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=bNIM_1xRYQOFEsIn74EX6qVpC59-GMhhr2CeiPr_GWg,3253
1
+ bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=ZTznmxIBQ81oT4nFMa8ic7PGh-3QpStNh6tGG-3RHU0,6502
2
2
  bitunix_automated_crypto_trading/BitunixApi.py,sha256=5qg-K5IcsAbb6K1feP9zL7RzSz7JzdGAxoY8R_YWxWE,15666
3
- bitunix_automated_crypto_trading/BitunixSignal.py,sha256=jPxnlaKA7NJDl1az3m1bfNLpouUsMYa35CixwUCD25E,91091
3
+ bitunix_automated_crypto_trading/BitunixSignal.py,sha256=6YI-X5pXxM9ifvY8S7u0e6HyrXYVlUsYBWXgVpAfxbw,92090
4
4
  bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=uiqAcis3u-ct07tjaTiC87ujzvcAtVRZ31CMiTBDW_M,11309
5
5
  bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
6
6
  bitunix_automated_crypto_trading/NotificationManager.py,sha256=exs6REABBA1omTeTGuUuECzxs5dGqdyL7oI8WyxS6Xc,798
7
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
8
  bitunix_automated_crypto_trading/TickerManager.py,sha256=tYzO7-vxb-60a-yvsRDC4w9GNdTsqWwy8DxbagtsqAc,42023
10
9
  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
10
+ bitunix_automated_crypto_trading/bitunix.py,sha256=dlf5m0964JW6-9V-xFWv0_4C3OWJ6yTq22E4qWWsGSI,27038
11
+ bitunix_automated_crypto_trading/config.py,sha256=AlcB0mmwOPfVWuiPrEiexk_JK9qhM2l9sZIdPBtNvqM,6146
13
12
  bitunix_automated_crypto_trading/logger.py,sha256=NHnA5JZdUFkTAhB7i-1iCAwrdf1fxhDuRvJUkbKPi9Y,2923
14
- bitunix_automated_crypto_trading/version.py,sha256=Z9dy-a-QtSCS4duPQWpgA6G-wTeWi6aUt5Wi1lK_Ltg,21
15
- bitunix_automated_crypto_trading-3.3.7.dist-info/METADATA,sha256=pppKFPixMGagASNAjEA4nu5lY9wTbslP4__00JOmNIk,996
16
- bitunix_automated_crypto_trading-3.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- bitunix_automated_crypto_trading-3.3.7.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
18
- bitunix_automated_crypto_trading-3.3.7.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
19
- bitunix_automated_crypto_trading-3.3.7.dist-info/RECORD,,
13
+ bitunix_automated_crypto_trading/version.py,sha256=W-NPfrZxGZd77L27ZfNBeZFiVIWjDNLUvZpIjOMIqSY,21
14
+ bitunix_automated_crypto_trading-3.3.9.dist-info/METADATA,sha256=r6O6GEO7JvYJSoWS3ZX08Znm_8-XVjhgsRKykKs1-6w,996
15
+ bitunix_automated_crypto_trading-3.3.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ bitunix_automated_crypto_trading-3.3.9.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
17
+ bitunix_automated_crypto_trading-3.3.9.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
18
+ bitunix_automated_crypto_trading-3.3.9.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()