bitunix-automated-crypto-trading 3.3.8__tar.gz → 3.3.9__tar.gz

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.
Files changed (26) hide show
  1. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/PKG-INFO +1 -1
  2. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/README.md +9 -0
  3. bitunix_automated_crypto_trading-3.3.9/bitunix_automated_crypto_trading/AsyncThreadRunner.py +132 -0
  4. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/BitunixSignal.py +69 -61
  5. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/bitunix.py +1 -3
  6. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/config.py +5 -1
  7. bitunix_automated_crypto_trading-3.3.9/bitunix_automated_crypto_trading/version.py +1 -0
  8. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/PKG-INFO +1 -1
  9. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/SOURCES.txt +0 -1
  10. bitunix_automated_crypto_trading-3.3.8/bitunix_automated_crypto_trading/AsyncThreadRunner.py +0 -82
  11. bitunix_automated_crypto_trading-3.3.8/bitunix_automated_crypto_trading/ThreadManager.py +0 -70
  12. bitunix_automated_crypto_trading-3.3.8/bitunix_automated_crypto_trading/version.py +0 -1
  13. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/BitunixApi.py +0 -0
  14. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/BitunixWebSocket.py +0 -0
  15. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +0 -0
  16. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/NotificationManager.py +0 -0
  17. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/SupportResistance.py +0 -0
  18. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/TickerManager.py +0 -0
  19. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/__init__.py +0 -0
  20. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/logger.py +0 -0
  21. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/dependency_links.txt +0 -0
  22. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/entry_points.txt +0 -0
  23. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/requires.txt +0 -0
  24. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/top_level.txt +0 -0
  25. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/setup.cfg +0 -0
  26. {bitunix_automated_crypto_trading-3.3.8 → bitunix_automated_crypto_trading-3.3.9}/setup.py +0 -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.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
@@ -51,6 +51,13 @@ Supports running the app in multiple instances (bots) with each instance having
51
51
  The platform can be configured through the `config.py` file or `config.txt`. Key configuration parameters include:
52
52
  - Trading Parameters:
53
53
  - `AUTOTRADE` : True or False
54
+
55
+ - `ALL_TICKERS` will be tickers that are not in LONG_TICKERS and SHORT_TICKERS, this willtrade all other tickers that are not in LONG_TICKERS and SHORT_TICKERS
56
+ - `LONG_TICKERS` will always trade long, even if the signal is SELL
57
+ - `SHORT_TICKERS` will always trade short, even if the signal is BUY
58
+ - `IGNORE_TICKERS` will not trade these tickers
59
+ If you want to trade all tickers except the ones in IGNORE_TICKERS, then set ALL_TICKERS, LONG_TICKERS and SHORT_TICKERS to ""
60
+
54
61
  - `TICKERS`: supply a list of tickers seperated by comma to trade, e.g., "BTCUSDT,ETHUSDT" or "" to auto select based on MIN_VOLUME and THRESHOLD
55
62
  - `LEVERAGE`: Trading leverage (1-100)
56
63
  - `THRESHOLD`: Ticker selection based close near high or low of the day
@@ -61,6 +68,8 @@ The platform can be configured through the `config.py` file or `config.txt`. Key
61
68
  - `LOSS_AMOUNT`: Maximum loss amount
62
69
  - `PROFIT_PERCENTAGE`: Target profit ROI percentage
63
70
  - `LOSS_PERCENTAGE`: Maximum loss ROI percentage
71
+ - `SL_BREAKEVEN_PERCENTAGE`
72
+ Percentage of profit to move the stop loss to breakeven, default is 75%
64
73
  - `BOT_CONTROLS_TP_SL`: True or False
65
74
  if false then take profit and stop loss will be placed by the bot when TP or SL is reached, if true then take profit and stop loss will be placed when the trade is opened
66
75
  - `BOT_TRAIL_TP`
@@ -0,0 +1,132 @@
1
+ import asyncio
2
+ import threading
3
+ import time
4
+ import logging # Using standard logging for demonstration
5
+
6
+ class AsyncThreadRunner:
7
+ def __init__(self, async_func, logger, interval, *args, **kwargs):
8
+ self.async_func = async_func
9
+ self.interval = interval
10
+ self.args = args
11
+ self.kwargs = kwargs
12
+ self.loop = asyncio.new_event_loop()
13
+ self._stop_event = threading.Event()
14
+ self.thread = threading.Thread(target=self.thread_function)
15
+ self.task = None
16
+ self.logger = logger
17
+ self.exception_occurred = False # New flag to indicate if an unhandled exception occurred
18
+
19
+ def thread_function(self):
20
+ """This function runs in the separate thread."""
21
+ asyncio.set_event_loop(self.loop)
22
+ self.exception_occurred = False # Reset on thread start
23
+
24
+ try:
25
+ if self.interval == 0:
26
+ # For one-off tasks, run it and then stop the loop
27
+ self.task = asyncio.run_coroutine_threadsafe(
28
+ self.run_once(), self.loop
29
+ )
30
+ else:
31
+ # For periodic tasks, schedule the periodic_run
32
+ self.task = asyncio.run_coroutine_threadsafe(
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.")
38
+ self.loop.run_forever()
39
+ except Exception as e:
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
44
+ finally:
45
+ self.logger.info(f"Thread '{self.thread.name}' event loop stopping.")
46
+ # Clean up pending tasks
47
+ pending = asyncio.all_tasks(self.loop)
48
+ for task in pending:
49
+ task.cancel()
50
+ try:
51
+ self.loop.run_until_complete(task)
52
+ except asyncio.CancelledError:
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
58
+ self.loop.run_until_complete(self.loop.shutdown_asyncgens())
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
+
74
+
75
+ async def periodic_run(self):
76
+ """Runs the async function periodically."""
77
+ try:
78
+ while not self._stop_event.is_set():
79
+ try:
80
+ await self.async_func(*self.args, **self.kwargs)
81
+ except Exception as e:
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.")
101
+ except asyncio.CancelledError:
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
106
+
107
+ def start_thread(self, thread_name=None):
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}'.")
111
+ self.thread.start()
112
+
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
117
+
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)
126
+
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
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}'))))
904
929
 
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)
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)
908
933
  else:
909
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)
910
935
  slPrice = Decimal(await self.str_precision(slPrice))
911
936
  slPrice = float(str(slPrice.quantize(Decimal(f'1e-{decimal_places}'))))
912
937
  slOrderPrice = await self.increment_by_last_decimal(await self.str_precision(slPrice))
913
938
 
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:
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bitunix_automated_crypto_trading
3
- Version: 3.3.8
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
@@ -7,7 +7,6 @@ bitunix_automated_crypto_trading/BitunixWebSocket.py
7
7
  bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py
8
8
  bitunix_automated_crypto_trading/NotificationManager.py
9
9
  bitunix_automated_crypto_trading/SupportResistance.py
10
- bitunix_automated_crypto_trading/ThreadManager.py
11
10
  bitunix_automated_crypto_trading/TickerManager.py
12
11
  bitunix_automated_crypto_trading/__init__.py
13
12
  bitunix_automated_crypto_trading/bitunix.py
@@ -1,82 +0,0 @@
1
- import asyncio
2
- import threading
3
- from logger import Logger
4
- import os
5
-
6
- class AsyncThreadRunner:
7
- def __init__(self, async_func, logger, interval, *args, **kwargs):
8
- self.async_func = async_func
9
- self.interval = interval
10
- self.args = args
11
- self.kwargs = kwargs
12
- self.loop = asyncio.new_event_loop()
13
- self._stop_event = threading.Event()
14
- self.thread = threading.Thread(target=self.thread_function)
15
- self.task = None
16
- self.logger = logger
17
-
18
- def thread_function(self):
19
- asyncio.set_event_loop(self.loop)
20
- try:
21
- if self.interval == 0:
22
- self.task = asyncio.run_coroutine_threadsafe(
23
- self.async_func(*self.args, **self.kwargs), self.loop
24
- )
25
- else:
26
- self.task = asyncio.run_coroutine_threadsafe(
27
- self.periodic_run(), self.loop
28
- )
29
- self.loop.run_forever()
30
- 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
33
- finally:
34
- pending = asyncio.all_tasks(self.loop)
35
- for task in pending:
36
- task.cancel()
37
- try:
38
- self.loop.run_until_complete(task)
39
- except asyncio.CancelledError:
40
- pass
41
- self.loop.run_until_complete(self.loop.shutdown_asyncgens())
42
- self.loop.close()
43
-
44
- async def periodic_run(self):
45
- try:
46
- while not self._stop_event.is_set():
47
- try:
48
- await self.async_func(*self.args, **self.kwargs)
49
- 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
55
- except asyncio.CancelledError:
56
- pass
57
-
58
- def start_thread(self, thread_name=None):
59
- self.thread.name = thread_name
60
- self.thread.start()
61
-
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}")
76
-
77
- # Stop the event loop safely
78
- self.loop.call_soon_threadsafe(self.loop.stop)
79
-
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.")
@@ -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()