bitunix-automated-crypto-trading 3.3.7__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.
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/PKG-INFO +1 -1
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/README.md +9 -0
- bitunix_automated_crypto_trading-3.3.9/bitunix_automated_crypto_trading/AsyncThreadRunner.py +132 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/BitunixSignal.py +69 -61
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/bitunix.py +1 -3
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/config.py +5 -1
- bitunix_automated_crypto_trading-3.3.9/bitunix_automated_crypto_trading/version.py +1 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/PKG-INFO +1 -1
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/SOURCES.txt +0 -1
- bitunix_automated_crypto_trading-3.3.7/bitunix_automated_crypto_trading/AsyncThreadRunner.py +0 -82
- bitunix_automated_crypto_trading-3.3.7/bitunix_automated_crypto_trading/ThreadManager.py +0 -70
- bitunix_automated_crypto_trading-3.3.7/bitunix_automated_crypto_trading/version.py +0 -1
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/BitunixApi.py +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/BitunixWebSocket.py +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/NotificationManager.py +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/SupportResistance.py +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/TickerManager.py +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/__init__.py +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading/logger.py +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/dependency_links.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/entry_points.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/requires.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/bitunix_automated_crypto_trading.egg-info/top_level.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/setup.cfg +0 -0
- {bitunix_automated_crypto_trading-3.3.7 → bitunix_automated_crypto_trading-3.3.9}/setup.py +0 -0
@@ -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,
|
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.
|
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.
|
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 +
|
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(
|
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
|
-
|
654
|
+
print(f"last_trade_time: {trade_time}, last_trade_time_unix: {trade_time_unix}")
|
654
655
|
# Get current Unix timestamp
|
655
|
-
|
656
|
-
|
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
|
-
|
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} ,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
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
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
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
|
908
|
-
if
|
909
|
-
slPrice = price * (1 - float(self.settings.PROFIT_PERCENTAGE) /
|
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
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
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
|
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.
|
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
|
-
|
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)
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "3.3.9"
|
@@ -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
|
bitunix_automated_crypto_trading-3.3.7/bitunix_automated_crypto_trading/AsyncThreadRunner.py
DELETED
@@ -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()
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "3.3.7"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|