bitunix-automated-crypto-trading 3.3.8__py3-none-any.whl → 3.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bitunix_automated_crypto_trading/AsyncThreadRunner.py +85 -35
- bitunix_automated_crypto_trading/BitunixSignal.py +156 -142
- bitunix_automated_crypto_trading/TickerManager.py +12 -8
- bitunix_automated_crypto_trading/bitunix.py +1 -3
- bitunix_automated_crypto_trading/config.py +5 -1
- bitunix_automated_crypto_trading/version.py +1 -1
- {bitunix_automated_crypto_trading-3.3.8.dist-info → bitunix_automated_crypto_trading-3.4.0.dist-info}/METADATA +1 -1
- bitunix_automated_crypto_trading-3.4.0.dist-info/RECORD +18 -0
- bitunix_automated_crypto_trading/ThreadManager.py +0 -70
- bitunix_automated_crypto_trading-3.3.8.dist-info/RECORD +0 -19
- {bitunix_automated_crypto_trading-3.3.8.dist-info → bitunix_automated_crypto_trading-3.4.0.dist-info}/WHEEL +0 -0
- {bitunix_automated_crypto_trading-3.3.8.dist-info → bitunix_automated_crypto_trading-3.4.0.dist-info}/entry_points.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.8.dist-info → bitunix_automated_crypto_trading-3.4.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
import threading
|
3
|
-
|
4
|
-
import
|
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
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
63
|
-
"""Gracefully
|
64
|
-
|
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
|
-
#
|
78
|
-
|
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
|
81
|
-
self.thread.join()
|
82
|
-
self.
|
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
|
|
@@ -157,14 +158,14 @@ class BitunixSignal:
|
|
157
158
|
self.StoreDepthDataTask = AsyncThreadRunner(self.bitunixPublicDepthWebSocketClient.run_websocket, self.logger, 0, self.StoreDepthData)
|
158
159
|
self.StoreDepthDataTask.start_thread(thread_name="StoreDepthData")
|
159
160
|
self.depth_que = asyncio.Queue()
|
160
|
-
self.ProcessDepthDataTask = AsyncThreadRunner(self.ProcessDepthData, self.logger, interval=
|
161
|
+
self.ProcessDepthDataTask = AsyncThreadRunner(self.ProcessDepthData, self.logger, interval=int(self.settings.TICKER_DATA_API_INTERVAL)) # run only once
|
161
162
|
self.ProcessDepthDataTask.start_thread(thread_name="ProcessDepthData")
|
162
163
|
|
163
164
|
self.bitunixPublicTickerWebSocketClient.tickerList = self.tickerList
|
164
165
|
self.StoreTickerDataTask = AsyncThreadRunner(self.bitunixPublicTickerWebSocketClient.run_websocket, self.logger, 0, self.StoreTickerData)
|
165
166
|
self.StoreTickerDataTask.start_thread(thread_name="StoreTickerData")
|
166
167
|
self.ticker_que = asyncio.Queue()
|
167
|
-
self.ProcessTickerDataTask = AsyncThreadRunner(self.ProcessTickerData, self.logger, interval=
|
168
|
+
self.ProcessTickerDataTask = AsyncThreadRunner(self.ProcessTickerData, self.logger, interval=int(self.settings.TICKER_DATA_API_INTERVAL)) # run only once
|
168
169
|
self.ProcessTickerDataTask.start_thread(thread_name="ProcessTickerData")
|
169
170
|
|
170
171
|
|
@@ -237,38 +238,39 @@ class BitunixSignal:
|
|
237
238
|
|
238
239
|
#api data
|
239
240
|
async def GetTickerData(self):
|
240
|
-
if
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
241
|
+
if self.settings.VERBOSE_LOGGING:
|
242
|
+
self.logger.info(f"GetTickerData started")
|
243
|
+
start=time.time()
|
244
|
+
# Get the current time and set the seconds and microseconds to zero
|
245
|
+
current_time = datetime.now()
|
246
|
+
current_minute = current_time.replace(second=0, microsecond=0)
|
247
|
+
ts = int(current_minute.timestamp())*1000
|
248
|
+
|
249
|
+
#api used insted of websocket
|
250
|
+
data = await self.bitunixApi.GetTickerData()
|
251
|
+
self.tickerdf = pd.DataFrame()
|
252
|
+
if data:
|
253
|
+
|
254
|
+
# Create a DataFrame from the data
|
255
|
+
self.tickerdf = pd.DataFrame(data, columns=["symbol", "last"])
|
256
|
+
|
257
|
+
#remove not required symbols
|
258
|
+
self.tickerdf.loc[~self.tickerdf['symbol'].isin(self.tickerObjects.symbols()), :] = None
|
259
|
+
self.tickerdf.dropna(inplace=True)
|
260
|
+
|
261
|
+
self.tickerdf['ts']=ts
|
262
|
+
self.tickerdf["tickerObj"] = self.tickerdf["symbol"].map(self.tickerObjects.get_tickerDict())
|
263
|
+
self.tuples_list = list(zip(self.tickerdf["tickerObj"], self.tickerdf["last"].astype(float), self.tickerdf["ts"]))
|
264
|
+
self.tickerObjects.form_candle(self.tuples_list)
|
263
265
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
266
|
+
self.lastTickerDataTime = time.time()
|
267
|
+
if self.settings.VERBOSE_LOGGING:
|
268
|
+
self.logger.info(f"GetTickerData: elapsed time {time.time()-start}")
|
269
|
+
if self.settings.BENCHMARK:
|
270
|
+
self.connection = sqlite3.connect(self.settings.DATABASE)
|
271
|
+
self.cursor = self.connection.cursor()
|
272
|
+
self.cursor.execute("INSERT INTO benchmark (process_name, time) VALUES (?, ?)", ("GetTickerData", time.time()-start))
|
273
|
+
self.connection.commit()
|
272
274
|
|
273
275
|
async def drain_queue(self, queue):
|
274
276
|
items = []
|
@@ -284,32 +286,34 @@ class BitunixSignal:
|
|
284
286
|
|
285
287
|
# Function to process the last price deque
|
286
288
|
async def ProcessTickerData(self):
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
self.
|
309
|
-
self.
|
310
|
-
|
311
|
-
|
312
|
-
|
289
|
+
if self.settings.VERBOSE_LOGGING:
|
290
|
+
self.logger.info(f"Started ProcessTickerData")
|
291
|
+
start=time.time()
|
292
|
+
try:
|
293
|
+
# Get the current time and set the seconds and microseconds to zero
|
294
|
+
latest_data = {}
|
295
|
+
reversed_items = await self.drain_queue(self.ticker_que)
|
296
|
+
while reversed_items:
|
297
|
+
message = reversed_items.popleft()
|
298
|
+
data = json.loads(message)
|
299
|
+
if data.get('symbol') and data.get('ch') == 'ticker':
|
300
|
+
symbol = data["symbol"]
|
301
|
+
ts = data["ts"]
|
302
|
+
if symbol not in latest_data or ts > latest_data[symbol]['ts']:
|
303
|
+
latest_data[symbol] = {'ts': ts, 'last': float(data['data']['la'])}
|
304
|
+
await asyncio.sleep(0.01)
|
305
|
+
# Convert to DataFrame
|
306
|
+
self.tickerdf = pd.DataFrame.from_dict(latest_data, orient="index")
|
307
|
+
if not self.tickerdf.empty:
|
308
|
+
self.tickerdf["tickerObj"] = self.tickerdf.index.map(self.tickerObjects.get_tickerDict())
|
309
|
+
self.tuples_list = list(zip(self.tickerdf["tickerObj"], self.tickerdf["last"].astype(float), self.tickerdf["ts"]))
|
310
|
+
self.tickerObjects.form_candle(self.tuples_list)
|
311
|
+
self.lastTickerDataTime = time.time()
|
312
|
+
except Exception as e:
|
313
|
+
self.logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
|
314
|
+
self.logger.info(traceback.print_exc())
|
315
|
+
if self.settings.VERBOSE_LOGGING:
|
316
|
+
self.logger.info(f"ProcessTickerData: elapsed time {time.time()-start}")
|
313
317
|
|
314
318
|
|
315
319
|
#websocket data to update bid and ask
|
@@ -319,30 +323,31 @@ class BitunixSignal:
|
|
319
323
|
|
320
324
|
# Function to process the bid, ask
|
321
325
|
async def ProcessDepthData(self):
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
self.
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
326
|
+
if self.settings.VERBOSE_LOGGING:
|
327
|
+
self.logger.info(f"Started ProcessDepthData")
|
328
|
+
start=time.time()
|
329
|
+
try:
|
330
|
+
latest_data = {}
|
331
|
+
reversed_items = await self.drain_queue(self.depth_que)
|
332
|
+
while reversed_items:
|
333
|
+
message = reversed_items.popleft()
|
334
|
+
data = json.loads(message)
|
335
|
+
if data.get('symbol') and data.get('ch') == 'depth_book1':
|
336
|
+
symbol = data["symbol"]
|
337
|
+
ts = data["ts"]
|
338
|
+
if symbol not in latest_data or ts > latest_data[symbol]['ts']:
|
339
|
+
latest_data[symbol] = {'ts': ts, 'bid': float(data['data']['b'][0][0]), 'ask': float(data['data']['a'][0][0])}
|
340
|
+
await asyncio.sleep(0.01)
|
341
|
+
# Convert to DataFrame
|
342
|
+
self.depthdf = pd.DataFrame.from_dict(latest_data, orient="index")
|
343
|
+
if not self.depthdf.empty:
|
344
|
+
self.depthdf["tickerObj"] = self.depthdf.index.map(self.tickerObjects.get_tickerDict())
|
345
|
+
self.depthdf.apply(self.apply_depth_data2, axis=1)
|
346
|
+
except Exception as e:
|
347
|
+
self.logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
|
348
|
+
self.logger.info(traceback.print_exc())
|
349
|
+
if self.settings.VERBOSE_LOGGING:
|
350
|
+
self.logger.info(f"ProcessDepthData: elapsed time {time.time()-start}")
|
346
351
|
|
347
352
|
def apply_depth_data2(self, row):
|
348
353
|
row["tickerObj"].set_ask(row["ask"])
|
@@ -399,11 +404,11 @@ class BitunixSignal:
|
|
399
404
|
###########################################################################################################
|
400
405
|
async def checkTickerAndAutotradeStatus(self):
|
401
406
|
while True:
|
402
|
-
if self.lastAutoTradeTime +
|
407
|
+
if self.lastAutoTradeTime + 1500 < time.time() or self.lastTickerDataTime + 1500 < time.time():
|
403
408
|
self.notifications.add_notification("AutoTradeProcess or GetTickerData is not running")
|
404
409
|
os._exit(1)
|
405
410
|
break
|
406
|
-
await asyncio.sleep(
|
411
|
+
await asyncio.sleep(1500)
|
407
412
|
|
408
413
|
async def GetportfolioData(self):
|
409
414
|
start=time.time()
|
@@ -650,14 +655,16 @@ class BitunixSignal:
|
|
650
655
|
trade_time = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
|
651
656
|
# Convert datetime object to Unix timestamp)
|
652
657
|
trade_time_unix = int(trade_time.timestamp())
|
653
|
-
|
658
|
+
print(f"last_trade_time: {trade_time}, last_trade_time_unix: {trade_time_unix}")
|
654
659
|
# Get current Unix timestamp
|
655
|
-
|
656
|
-
|
660
|
+
current_time = time.time()
|
661
|
+
current_time_unix = int(current_time)
|
662
|
+
formatted_time = datetime.fromtimestamp(current_time).strftime("%y-%m-%d %H:%M:%S")
|
663
|
+
print(f"current_time: {formatted_time}, current_time_unix: {current_time_unix}")
|
657
664
|
# Calculate duration in minutes
|
658
665
|
duration_minutes = (current_time_unix - trade_time_unix) / 60
|
659
666
|
#print(f"diff: {current_time_unix - trade_time_unix}")
|
660
|
-
|
667
|
+
print(f"duration_minutes: {duration_minutes}")
|
661
668
|
|
662
669
|
return round(duration_minutes)
|
663
670
|
|
@@ -722,14 +729,20 @@ class BitunixSignal:
|
|
722
729
|
# Calculate the duration in minutes since the position was opened
|
723
730
|
data = await self.bitunixApi.GetPositionHistoryData({'symbol': row['symbol']})
|
724
731
|
duration_minutes = None
|
732
|
+
mtime = None
|
725
733
|
if data and 'positionList' in data and len(data['positionList']) > 0:
|
726
734
|
xtime = float(data['positionList'][0]['mtime'])
|
727
735
|
mtime = pd.to_datetime(xtime, unit='ms').tz_localize('UTC').tz_convert(cst).strftime('%Y-%m-%d %H:%M:%S')
|
728
736
|
duration_minutes = await self.get_duration(mtime)
|
729
|
-
self.logger.info(f"row.symbol: {row.symbol} ,
|
737
|
+
self.logger.info(f"row.symbol: {row.symbol} , last trade time: {mtime}, duration_minutes: {duration_minutes}")
|
730
738
|
if duration_minutes is None or duration_minutes > self.settings.DELAY_IN_MINUTES_FOR_SAME_TICKER_TRADES:
|
731
739
|
side = "BUY" if row[f'{period}_barcolor'] == self.green and row[f'{period}_trend'] == "BUY" else "SELL" if row[f'{period}_barcolor'] == self.red and row[f'{period}_trend'] == "SELL" else ""
|
732
|
-
|
740
|
+
if row['symbol'] in self.settings.LONG_TICKERS.split(","):
|
741
|
+
side = "BUY"
|
742
|
+
self.logger.info(f"row.symbol: {row.symbol} , will always be long")
|
743
|
+
if row['symbol'] in self.settings.SHORT_TICKERS.split(","):
|
744
|
+
side = "SELL"
|
745
|
+
self.logger.info(f"row.symbol: {row.symbol} , will always be short")
|
733
746
|
if side != "":
|
734
747
|
select = True
|
735
748
|
self.pendingPositions = await self.bitunixApi.GetPendingPositionData({'symbol': row.symbol})
|
@@ -875,69 +888,70 @@ class BitunixSignal:
|
|
875
888
|
tporderId = order['id']
|
876
889
|
tpStopType = order['tpStopType']
|
877
890
|
tpOrderType = order['tpOrderType']
|
878
|
-
|
879
|
-
old_tpPrice = tpPrice
|
891
|
+
old_tpPrice = float(order['tpPrice'])
|
880
892
|
if order['slPrice'] is not None:
|
881
893
|
slorderId = order['id']
|
882
894
|
slStopType = order['slStopType']
|
883
895
|
slOrderType = order['slOrderType']
|
884
|
-
|
885
|
-
old_slPrice = slPrice
|
896
|
+
old_slPrice = float(order['slPrice'])
|
886
897
|
|
887
898
|
# move TP and SL in the direction of profit
|
888
899
|
tp_midpoint = None
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
if tp_midpoint is not None and (price > tp_midpoint and side == "BUY" or price < tp_midpoint and side == "SELL"):
|
900
|
+
if self.settings.BOT_TRAIL_TP and old_tpPrice is not None:
|
901
|
+
tp_midpoint = old_tpPrice / (1 + self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else old_tpPrice / (1 - self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE)
|
902
|
+
tp_midpoint = Decimal(await self.str_precision(tp_midpoint))
|
903
|
+
tp_midpoint =float(str(tp_midpoint.quantize(Decimal(f'1e-{decimal_places}'))))
|
894
904
|
|
905
|
+
if price > tp_midpoint and side == "BUY" or price < tp_midpoint and side == "SELL":
|
895
906
|
tpPrice = price * (1 + float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE) if side == "BUY" else price * (1 - float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE)
|
896
907
|
tpPrice = Decimal(await self.str_precision(tpPrice))
|
897
908
|
tpPrice =float(str(tpPrice.quantize(Decimal(f'1e-{decimal_places}'))))
|
898
909
|
tpOrderPrice = await self.decrement_by_last_decimal(str(tpPrice))
|
899
910
|
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
911
|
+
if tpPrice is not None:
|
912
|
+
self.notifications.add_notification(
|
913
|
+
f'{colors.CYAN} {row.symbol} avgOpenPrice: {avgOpenPrice}, current price: {price}, ROI: {roi}%, old_TP: {old_tpPrice}, TP midpoint: {tp_midpoint}, TP: {tpPrice}'
|
914
|
+
)
|
915
|
+
if old_tpPrice < tpPrice if side== "BUY" else old_tpPrice > tpPrice:
|
916
|
+
if old_tpPrice is None or tpPrice != old_tpPrice:
|
917
|
+
datajs2 = await self.bitunixApi.ModifyTpSlOrder({'orderId':tporderId,'tpPrice':str(tpPrice), 'tpOrderPrice':str(tpOrderPrice), 'tpQty':str(qty),'tpStopType':tpStopType,'tpOrderType':tpOrderType})
|
918
|
+
if datajs2 is not None:
|
919
|
+
self.notifications.add_notification(
|
920
|
+
f'{colors.CYAN} Take Profit order for {row.symbol} moved from {old_tpPrice} to {tpPrice}'
|
921
|
+
)
|
922
|
+
|
923
|
+
breakeven_calc = False
|
924
|
+
sl_midpoint = None
|
925
|
+
if self.settings.BOT_TRAIL_SL and old_slPrice is not None:
|
926
|
+
if roi > self.settings.PROFIT_PERCENTAGE * self.settings.SL_BREAKEVEN_PERCENTAGE/100 and self.settings.PROFIT_PERCENTAGE < self.settings.LOSS_PERCENTAGE:
|
927
|
+
sl_midpoint = old_slPrice / (1 - self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else old_slPrice / (1 + self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE)
|
928
|
+
breakeven_calc = True
|
929
|
+
else:
|
930
|
+
sl_midpoint = old_slPrice / (1 - self.settings.LOSS_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else old_slPrice / (1 + self.settings.LOSS_PERCENTAGE/100/self.settings.LEVERAGE)
|
931
|
+
sl_midpoint = Decimal(await self.str_precision(sl_midpoint))
|
932
|
+
sl_midpoint =float(str(sl_midpoint.quantize(Decimal(f'1e-{decimal_places}'))))
|
904
933
|
|
905
|
-
if
|
906
|
-
if
|
907
|
-
slPrice = price * (1 - float(self.settings.PROFIT_PERCENTAGE
|
934
|
+
if price > sl_midpoint and side == "BUY" or price < sl_midpoint and side == "SELL":
|
935
|
+
if breakeven_calc:
|
936
|
+
slPrice = price * (1 - float(self.settings.PROFIT_PERCENTAGE) /100 /self.settings.LEVERAGE) if side == "BUY" else price * (1 + float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE)
|
937
|
+
if slPrice < avgOpenPrice and side == "BUY" or slPrice > avgOpenPrice and side == "SELL":
|
938
|
+
slPrice = avgOpenPrice
|
908
939
|
else:
|
909
940
|
slPrice = price * (1 - float(self.settings.LOSS_PERCENTAGE) / 100 / self.settings.LEVERAGE) if side == "BUY" else price * (1 + float(self.settings.LOSS_PERCENTAGE) / 100 / self.settings.LEVERAGE)
|
910
941
|
slPrice = Decimal(await self.str_precision(slPrice))
|
911
942
|
slPrice = float(str(slPrice.quantize(Decimal(f'1e-{decimal_places}'))))
|
912
943
|
slOrderPrice = await self.increment_by_last_decimal(await self.str_precision(slPrice))
|
913
944
|
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
if old_tpPrice is None or tpPrice != old_tpPrice:
|
922
|
-
datajs2 = await self.bitunixApi.ModifyTpSlOrder({'orderId':tporderId,'tpPrice':str(tpPrice), 'tpOrderPrice':str(tpOrderPrice), 'tpQty':str(qty),'tpStopType':tpStopType,'tpOrderType':tpOrderType})
|
923
|
-
if datajs2 is not None:
|
924
|
-
self.notifications.add_notification(
|
925
|
-
f'{colors.CYAN} Take Profit order for {row.symbol} moved from {old_tpPrice} to {tpPrice}'
|
926
|
-
)
|
927
|
-
|
928
|
-
if self.settings.BOT_TRAIL_SL and slPrice is not None and slOrderPrice is not None:
|
929
|
-
if old_slPrice is None or slPrice != old_slPrice:
|
930
|
-
datajs3 = await self.bitunixApi.ModifyTpSlOrder({'orderId':slorderId,'slPrice':str(slPrice),'slQty':str(qty),'slStopType':slStopType,'slOrderType':slOrderType})
|
931
|
-
if datajs3 is not None:
|
932
|
-
if roi > self.settings.PROFIT_PERCENTAGE * 0.75 and self.settings.PROFIT_PERCENTAGE < self.settings.LOSS_PERCENTAGE:
|
945
|
+
if slPrice is not None:
|
946
|
+
self.notifications.add_notification(
|
947
|
+
f'{colors.CYAN} {row.symbol} avgOpenPrice: {avgOpenPrice}, current price: {price}, ROI: {roi}%, old_SL: {old_slPrice}, SL midpoint: {sl_midpoint}, SL: {slPrice}'
|
948
|
+
)
|
949
|
+
if old_slPrice < slPrice if side== "BUY" else old_slPrice > slPrice:
|
950
|
+
datajs3 = await self.bitunixApi.ModifyTpSlOrder({'orderId':slorderId,'slPrice':str(slPrice),'slQty':str(qty),'slStopType':slStopType,'slOrderType':slOrderType})
|
951
|
+
if datajs3 is not None:
|
933
952
|
self.notifications.add_notification(
|
934
|
-
f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to
|
953
|
+
f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to {"profitable" if breakeven_calc else ""} {slPrice}'
|
935
954
|
)
|
936
|
-
else:
|
937
|
-
self.notifications.add_notification(
|
938
|
-
f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to {slPrice}'
|
939
|
-
)
|
940
|
-
|
941
955
|
|
942
956
|
|
943
957
|
if self.settings.BOT_CONTROLS_TP_SL:
|
@@ -330,8 +330,9 @@ class Interval:
|
|
330
330
|
|
331
331
|
buy_conditions = ema_open or macd_open or bbm_open or rsi_open or bos_open or trendline_open
|
332
332
|
additional_buy_conditions = (
|
333
|
-
(
|
334
|
-
|
333
|
+
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN else True
|
334
|
+
) and (
|
335
|
+
(self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN and self.candle_trend == "BULLISH") if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN else True
|
335
336
|
)
|
336
337
|
|
337
338
|
|
@@ -345,8 +346,9 @@ class Interval:
|
|
345
346
|
|
346
347
|
sell_conditions = ema_close or macd_close or bbm_close or rsi_close or bos_close or trendline_close
|
347
348
|
additional_sell_conditions = (
|
348
|
-
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") and
|
349
|
-
|
349
|
+
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN else True
|
350
|
+
) and (
|
351
|
+
(self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN and self.candle_trend == "BEARISH") if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN else True
|
350
352
|
)
|
351
353
|
|
352
354
|
# Determine current signal
|
@@ -372,8 +374,9 @@ class Interval:
|
|
372
374
|
|
373
375
|
buy_conditions = ema_open and macd_open and bbm_open and rsi_open and bos_open and trendline_open
|
374
376
|
additional_buy_conditions = (
|
375
|
-
(
|
376
|
-
|
377
|
+
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN else True
|
378
|
+
) and (
|
379
|
+
(self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN and self.candle_trend == "BULLISH") if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN else True
|
377
380
|
)
|
378
381
|
|
379
382
|
# Check for SELL signal
|
@@ -392,8 +395,9 @@ class Interval:
|
|
392
395
|
|
393
396
|
sell_conditions = ema_close and macd_close and bbm_close and rsi_close and bos_close and trendline_close
|
394
397
|
additional_sell_conditions = (
|
395
|
-
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG")
|
396
|
-
|
398
|
+
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN else True
|
399
|
+
) and (
|
400
|
+
(self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN and self.candle_trend == "BEARISH") if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN else True
|
397
401
|
)
|
398
402
|
|
399
403
|
# Determine current signal
|
@@ -12,7 +12,6 @@ from pathlib import Path
|
|
12
12
|
import re
|
13
13
|
|
14
14
|
|
15
|
-
from ThreadManager import ThreadManager
|
16
15
|
from BitunixApi import BitunixApi
|
17
16
|
from BitunixSignal import BitunixSignal
|
18
17
|
from NotificationManager import NotificationManager
|
@@ -75,10 +74,9 @@ class bitunix():
|
|
75
74
|
self.logger=logger
|
76
75
|
self.autoTrade=settings.AUTOTRADE
|
77
76
|
self.settings = settings
|
78
|
-
self.threadManager = ThreadManager()
|
79
77
|
self.notifications = NotificationManager(logger)
|
80
78
|
self.bitunixApi = BitunixApi(api_key, secret_key, settings, self.logger)
|
81
|
-
self.bitunixSignal = BitunixSignal(api_key, secret_key, settings, self.
|
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)
|
@@ -1 +1 @@
|
|
1
|
-
__version__ = "3.
|
1
|
+
__version__ = "3.4.0"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=tnnLqU2EaD-XPRghFrFeKiZBWDxbGaos_ozrh20_NfA,6504
|
2
|
+
bitunix_automated_crypto_trading/BitunixApi.py,sha256=5qg-K5IcsAbb6K1feP9zL7RzSz7JzdGAxoY8R_YWxWE,15666
|
3
|
+
bitunix_automated_crypto_trading/BitunixSignal.py,sha256=VMfJEKiJbt1UOwi6gsQ4mAidmKs4NCzs3V7CrZaRua4,92377
|
4
|
+
bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=uiqAcis3u-ct07tjaTiC87ujzvcAtVRZ31CMiTBDW_M,11309
|
5
|
+
bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
|
6
|
+
bitunix_automated_crypto_trading/NotificationManager.py,sha256=exs6REABBA1omTeTGuUuECzxs5dGqdyL7oI8WyxS6Xc,798
|
7
|
+
bitunix_automated_crypto_trading/SupportResistance.py,sha256=x_to4M4OHg0h8o40DXDBa4E_5io-y2Lb5qo2VzFnu_8,5765
|
8
|
+
bitunix_automated_crypto_trading/TickerManager.py,sha256=2eQNeSUDPya39oBL-ObyaVUYpzn0q9ykk7q93-A7S1k,42762
|
9
|
+
bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
|
10
|
+
bitunix_automated_crypto_trading/bitunix.py,sha256=dlf5m0964JW6-9V-xFWv0_4C3OWJ6yTq22E4qWWsGSI,27038
|
11
|
+
bitunix_automated_crypto_trading/config.py,sha256=AlcB0mmwOPfVWuiPrEiexk_JK9qhM2l9sZIdPBtNvqM,6146
|
12
|
+
bitunix_automated_crypto_trading/logger.py,sha256=NHnA5JZdUFkTAhB7i-1iCAwrdf1fxhDuRvJUkbKPi9Y,2923
|
13
|
+
bitunix_automated_crypto_trading/version.py,sha256=DKmNcG3UtnZIAj5xty7yLrIQp4fPaaT1yvrbtln45_8,21
|
14
|
+
bitunix_automated_crypto_trading-3.4.0.dist-info/METADATA,sha256=dK1dcaufdtHdO4c5OadX3XpKhF-mMAh7N5nxtSIZQME,996
|
15
|
+
bitunix_automated_crypto_trading-3.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
+
bitunix_automated_crypto_trading-3.4.0.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
|
17
|
+
bitunix_automated_crypto_trading-3.4.0.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
|
18
|
+
bitunix_automated_crypto_trading-3.4.0.dist-info/RECORD,,
|
@@ -1,70 +0,0 @@
|
|
1
|
-
#usage
|
2
|
-
#start threads
|
3
|
-
#self.threadManager.start_thread("GetportfolioData", 1, self.GetportfolioData)
|
4
|
-
#self.threadManager.start_thread("GetPendingPositionData", 0, self.GetPendingPositionData)
|
5
|
-
#self.threadManager.start_thread("GetOrderData", 1, self.GetPendingOrderData)
|
6
|
-
#self.threadManager.start_thread("GetTradeHistoryData", 1, self.GetTradeHistoryData)
|
7
|
-
#self.threadManager.start_thread("BuySellList", 60, self.BuySellList)
|
8
|
-
#self.threadManager.start_thread("AutoTradeProcess", 60, self.AutoTradeProcess)
|
9
|
-
|
10
|
-
import time
|
11
|
-
import threading
|
12
|
-
from logger import Logger
|
13
|
-
global LOG_FILE
|
14
|
-
|
15
|
-
def job_func(*args, **kwargs):
|
16
|
-
print(f"Job running with arguments: {args}, {kwargs}")
|
17
|
-
|
18
|
-
def run_threaded(job_func, args):
|
19
|
-
job_thread = threading.Thread(target=job_func, args=args)
|
20
|
-
job_thread.start()
|
21
|
-
|
22
|
-
class ManagedThread(threading.Thread):
|
23
|
-
def __init__(self, interval, target, logger, *args, **kwargs):
|
24
|
-
super().__init__()
|
25
|
-
self.target = target
|
26
|
-
self.interval = interval
|
27
|
-
self.args = args
|
28
|
-
self.kwargs = kwargs
|
29
|
-
self._stop_event = threading.Event()
|
30
|
-
self.logger = logger
|
31
|
-
|
32
|
-
def run(self):
|
33
|
-
while not self._stop_event.is_set():
|
34
|
-
stime=time.time()
|
35
|
-
try:
|
36
|
-
self.target(self, *self.args, **self.kwargs)
|
37
|
-
except Exception as e:
|
38
|
-
self.logger.info(f"error in thread {self.name}, {e}, {e.args}, {type(e).__name__}")
|
39
|
-
elapsed_time = time.time() - stime
|
40
|
-
if self.interval==0:
|
41
|
-
break
|
42
|
-
time_to_wait = max(0.05, self.interval - elapsed_time)
|
43
|
-
time.sleep(time_to_wait)
|
44
|
-
|
45
|
-
def stop(self):
|
46
|
-
self._stop_event.set()
|
47
|
-
|
48
|
-
def should_stop(self):
|
49
|
-
return self._stop_event.is_set()
|
50
|
-
|
51
|
-
class ThreadManager:
|
52
|
-
def __init__(self):
|
53
|
-
self.threads = []
|
54
|
-
|
55
|
-
def start_thread(self, name, interval, thread, *args, **kwargs):
|
56
|
-
thread = ManagedThread(interval, thread, *args, **kwargs)
|
57
|
-
thread.name = name
|
58
|
-
thread.start()
|
59
|
-
self.threads.append(thread)
|
60
|
-
thread.join(interval)
|
61
|
-
return thread
|
62
|
-
|
63
|
-
def stop_all_threads(self):
|
64
|
-
for thread in self.threads:
|
65
|
-
thread.stop()
|
66
|
-
for thread in self.threads:
|
67
|
-
thread.join()
|
68
|
-
self.threads = []
|
69
|
-
|
70
|
-
threadManager = ThreadManager()
|
@@ -1,19 +0,0 @@
|
|
1
|
-
bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=bNIM_1xRYQOFEsIn74EX6qVpC59-GMhhr2CeiPr_GWg,3253
|
2
|
-
bitunix_automated_crypto_trading/BitunixApi.py,sha256=5qg-K5IcsAbb6K1feP9zL7RzSz7JzdGAxoY8R_YWxWE,15666
|
3
|
-
bitunix_automated_crypto_trading/BitunixSignal.py,sha256=nMdHsWqtnrHcem_OlC4k8gbI2KDrJKuivzTG13g-udI,91104
|
4
|
-
bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=uiqAcis3u-ct07tjaTiC87ujzvcAtVRZ31CMiTBDW_M,11309
|
5
|
-
bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
|
6
|
-
bitunix_automated_crypto_trading/NotificationManager.py,sha256=exs6REABBA1omTeTGuUuECzxs5dGqdyL7oI8WyxS6Xc,798
|
7
|
-
bitunix_automated_crypto_trading/SupportResistance.py,sha256=x_to4M4OHg0h8o40DXDBa4E_5io-y2Lb5qo2VzFnu_8,5765
|
8
|
-
bitunix_automated_crypto_trading/ThreadManager.py,sha256=Lw5_1EIT0m3AFSv5CIMpnjtA0DnNw2qQ6JtSpT34LyM,2349
|
9
|
-
bitunix_automated_crypto_trading/TickerManager.py,sha256=tYzO7-vxb-60a-yvsRDC4w9GNdTsqWwy8DxbagtsqAc,42023
|
10
|
-
bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
|
11
|
-
bitunix_automated_crypto_trading/bitunix.py,sha256=lxwnYARxldA2oU6GdjupilXIlnUh4RX8rQLCOn7x13I,27143
|
12
|
-
bitunix_automated_crypto_trading/config.py,sha256=2K-5NzWoVUaU3O2tgi4qt-3dA_sCC6Nm3vFQBZrO5y8,5943
|
13
|
-
bitunix_automated_crypto_trading/logger.py,sha256=NHnA5JZdUFkTAhB7i-1iCAwrdf1fxhDuRvJUkbKPi9Y,2923
|
14
|
-
bitunix_automated_crypto_trading/version.py,sha256=37tflRBXps5ly3x-d0V5MX2DfwsqrPwDzFH9EVchsG4,21
|
15
|
-
bitunix_automated_crypto_trading-3.3.8.dist-info/METADATA,sha256=HXT-qw3CG4V7GtZlr-vdv86LP6p_o-qxhamOgQH7354,996
|
16
|
-
bitunix_automated_crypto_trading-3.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
17
|
-
bitunix_automated_crypto_trading-3.3.8.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
|
18
|
-
bitunix_automated_crypto_trading-3.3.8.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
|
19
|
-
bitunix_automated_crypto_trading-3.3.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|