bitunix-automated-crypto-trading 3.1.6__py3-none-any.whl → 3.1.8__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 +8 -8
- bitunix_automated_crypto_trading/BitunixApi.py +12 -13
- bitunix_automated_crypto_trading/BitunixSignal.py +83 -47
- bitunix_automated_crypto_trading/BitunixWebSocket.py +21 -21
- bitunix_automated_crypto_trading/NotificationManager.py +3 -3
- bitunix_automated_crypto_trading/SupportResistance.py +63 -35
- bitunix_automated_crypto_trading/ThreadManager.py +4 -3
- bitunix_automated_crypto_trading/TickerManager.py +56 -44
- bitunix_automated_crypto_trading/bitunix.py +42 -16
- bitunix_automated_crypto_trading/config.py +8 -1
- bitunix_automated_crypto_trading/logger.py +1 -1
- {bitunix_automated_crypto_trading-3.1.6.dist-info → bitunix_automated_crypto_trading-3.1.8.dist-info}/METADATA +1 -1
- bitunix_automated_crypto_trading-3.1.8.dist-info/RECORD +18 -0
- bitunix_automated_crypto_trading-3.1.6.dist-info/RECORD +0 -18
- {bitunix_automated_crypto_trading-3.1.6.dist-info → bitunix_automated_crypto_trading-3.1.8.dist-info}/WHEEL +0 -0
- {bitunix_automated_crypto_trading-3.1.6.dist-info → bitunix_automated_crypto_trading-3.1.8.dist-info}/entry_points.txt +0 -0
- {bitunix_automated_crypto_trading-3.1.6.dist-info → bitunix_automated_crypto_trading-3.1.8.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,7 @@
|
|
1
1
|
import pandas as pd
|
2
2
|
import numpy as np
|
3
3
|
from scipy.signal import find_peaks
|
4
|
-
|
5
|
-
class SupportResistance:
|
6
|
-
import pandas as pd
|
7
|
-
import numpy as np
|
4
|
+
import talib
|
8
5
|
|
9
6
|
class SupportResistance:
|
10
7
|
def _find_peaks(self, prices, prominence=None, distance=None):
|
@@ -33,7 +30,7 @@ class SupportResistance:
|
|
33
30
|
intercept = price1 - slope * index1
|
34
31
|
return slope * np.arange(length) + intercept
|
35
32
|
|
36
|
-
def support_resistance_trend_lines(self, data, lookback=
|
33
|
+
def support_resistance_trend_lines(self, data, lookback=24, prominence=None, distance=None):
|
37
34
|
if data is None or len(data) < lookback:
|
38
35
|
return pd.DataFrame()
|
39
36
|
try:
|
@@ -44,39 +41,69 @@ class SupportResistance:
|
|
44
41
|
|
45
42
|
# Find all local peaks and troughs
|
46
43
|
peak_indices_all = self._find_peaks(high_prices, prominence=prominence, distance=distance)
|
47
|
-
if len(peak_indices_all) > 0 and peak_indices_all[-1] < lookback - 2 and high_prices[-2] > high_prices[lookback - peak_indices_all[-1]]:
|
48
|
-
peak_indices_all = np.append(peak_indices_all,lookback - 2)
|
49
44
|
trough_indices_all = self._find_troughs(low_prices, prominence=prominence, distance=distance)
|
50
|
-
if len(trough_indices_all) > 0 and trough_indices_all[-1] < lookback - 2 and low_prices[-2] < low_prices[lookback - trough_indices_all[-1]]:
|
51
|
-
trough_indices_all = np.append(trough_indices_all, lookback - 2)
|
52
|
-
# Get the two most recent prominent peaks
|
53
|
-
most_recent_peaks_indices = self._get_n_most_recent_prominent(peak_indices_all, high_prices, n=2)
|
54
|
-
|
55
|
-
# Get the two most recent prominent troughs
|
56
|
-
most_recent_troughs_indices = self._get_n_most_recent_prominent(trough_indices_all, low_prices, n=2)
|
57
45
|
|
58
46
|
support_line = np.full(lookback, np.nan)
|
59
47
|
resistance_line = np.full(lookback, np.nan)
|
60
48
|
|
61
|
-
if len(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
49
|
+
if len(peak_indices_all) > 0:
|
50
|
+
if len(peak_indices_all) >= 2:
|
51
|
+
resistance_line_full = self._get_line(peak_indices_all[-1], high_prices[peak_indices_all[-1]],
|
52
|
+
peak_indices_all[-2], high_prices[peak_indices_all[-2]], lookback)
|
53
|
+
# Set values before the second peak to NaN
|
54
|
+
resistance_line[peak_indices_all[-2]:] = resistance_line_full[peak_indices_all[-2]:]
|
55
|
+
elif len(peak_indices_all) == 1:
|
56
|
+
resistance_line[peak_indices_all[-1]:] = high_prices[peak_indices_all[-1]] # Horizontal line from the peak onwards
|
57
|
+
|
58
|
+
if len(trough_indices_all) > 0:
|
59
|
+
if len(trough_indices_all) >= 2:
|
60
|
+
support_line_full = self._get_line(trough_indices_all[-1], low_prices[trough_indices_all[-1]],
|
61
|
+
trough_indices_all[-2], low_prices[trough_indices_all[-2]], lookback)
|
62
|
+
# Set values before the second trough to NaN
|
63
|
+
support_line[trough_indices_all[-2]:] = support_line_full[trough_indices_all[-2]:]
|
64
|
+
elif len(trough_indices_all) == 1:
|
65
|
+
support_line[trough_indices_all[-1]:] = low_prices[trough_indices_all[-1]] # Horizontal line from the trough onwards
|
66
|
+
|
67
|
+
results_df = pd.DataFrame({
|
68
|
+
'time': ts,
|
69
|
+
'support_line': support_line,
|
70
|
+
'resistance_line': resistance_line
|
71
|
+
})
|
72
|
+
return results_df
|
73
|
+
except Exception as e:
|
74
|
+
print(f"trendline error occurred: {e}")
|
75
|
+
return pd.DataFrame()
|
76
|
+
|
77
|
+
def support_resistance_bos_lines(self, data, length, lookback=24):
|
78
|
+
if data is None or len(data) < lookback:
|
79
|
+
return pd.DataFrame()
|
80
|
+
try:
|
81
|
+
recent_data = data.iloc[-length:].copy()
|
82
|
+
highs = recent_data['high'].values
|
83
|
+
high_prices = np.full_like(highs, np.nan, dtype=np.float64)
|
84
|
+
high_prices[-lookback:] = highs[-lookback:]
|
85
|
+
|
86
|
+
lows = recent_data['low'].values
|
87
|
+
low_prices = np.full_like(lows, np.nan, dtype=np.float64)
|
88
|
+
low_prices[-lookback:] = lows[-lookback:]
|
89
|
+
|
90
|
+
ts = recent_data['time'].values
|
91
|
+
|
92
|
+
support_line = np.full(length, np.nan)
|
93
|
+
resistance_line = np.full(length, np.nan)
|
94
|
+
|
95
|
+
# Find all local peaks and troughs and its highest and lowest values
|
96
|
+
peak_indices_all = self._find_peaks(high_prices)
|
97
|
+
if len(peak_indices_all) == 0:
|
98
|
+
highest_peak_index = peak_indices_all[np.argmax(high_prices[peak_indices_all])]
|
99
|
+
highest_peak_value = high_prices[highest_peak_index]
|
100
|
+
resistance_line[highest_peak_index:] = high_prices[highest_peak_index] # Horizontal line from the highest point onwards
|
101
|
+
|
102
|
+
trough_indices_all = self._find_troughs(low_prices)
|
103
|
+
if len(trough_indices_all) == 0:
|
104
|
+
lowest_peak_index = trough_indices_all[np.argmin(low_prices[trough_indices_all])]
|
105
|
+
lowest_peak_value = low_prices[lowest_peak_index]
|
106
|
+
support_line[lowest_peak_index:] = low_prices[lowest_peak_index] # Horizontal line from the lowset point onwards
|
80
107
|
|
81
108
|
results_df = pd.DataFrame({
|
82
109
|
'time': ts,
|
@@ -85,5 +112,6 @@ class SupportResistance:
|
|
85
112
|
})
|
86
113
|
return results_df
|
87
114
|
except Exception as e:
|
88
|
-
print(f"
|
89
|
-
return pd.DataFrame()
|
115
|
+
print(f"bos error occurred: {e}")
|
116
|
+
return pd.DataFrame()
|
117
|
+
|
@@ -10,7 +10,7 @@
|
|
10
10
|
import time
|
11
11
|
import threading
|
12
12
|
from logger import Logger
|
13
|
-
|
13
|
+
global LOG_FILE
|
14
14
|
|
15
15
|
def job_func(*args, **kwargs):
|
16
16
|
print(f"Job running with arguments: {args}, {kwargs}")
|
@@ -20,13 +20,14 @@ def run_threaded(job_func, args):
|
|
20
20
|
job_thread.start()
|
21
21
|
|
22
22
|
class ManagedThread(threading.Thread):
|
23
|
-
def __init__(self, interval, target, *args, **kwargs):
|
23
|
+
def __init__(self, interval, target, logger, *args, **kwargs):
|
24
24
|
super().__init__()
|
25
25
|
self.target = target
|
26
26
|
self.interval = interval
|
27
27
|
self.args = args
|
28
28
|
self.kwargs = kwargs
|
29
29
|
self._stop_event = threading.Event()
|
30
|
+
self.logger = logger
|
30
31
|
|
31
32
|
def run(self):
|
32
33
|
while not self._stop_event.is_set():
|
@@ -34,7 +35,7 @@ class ManagedThread(threading.Thread):
|
|
34
35
|
try:
|
35
36
|
self.target(self, *self.args, **self.kwargs)
|
36
37
|
except Exception as e:
|
37
|
-
logger.info(f"error in thread {self.name}, {e}, {e.args}, {type(e).__name__}")
|
38
|
+
self.logger.info(f"error in thread {self.name}, {e}, {e.args}, {type(e).__name__}")
|
38
39
|
elapsed_time = time.time() - stime
|
39
40
|
if self.interval==0:
|
40
41
|
break
|
@@ -7,25 +7,27 @@ import asyncio
|
|
7
7
|
import talib
|
8
8
|
import traceback
|
9
9
|
from logger import Logger
|
10
|
-
logger = Logger(__name__).get_logger()
|
11
10
|
import gc
|
12
11
|
from concurrent.futures import ProcessPoolExecutor
|
13
12
|
from SupportResistance import SupportResistance
|
14
13
|
sr = SupportResistance()
|
15
14
|
|
16
15
|
class Interval:
|
17
|
-
def __init__(self, symbol, intervalId, delta, data, settings):
|
16
|
+
def __init__(self, symbol, intervalId, delta, data, settings, logger):
|
18
17
|
self.symbol = symbol
|
19
18
|
self.intervalId = intervalId
|
20
19
|
self.delta = delta
|
21
20
|
self.settings=settings
|
22
21
|
self._data = data
|
22
|
+
self.logger = logger
|
23
|
+
|
23
24
|
|
24
25
|
#these signals are used to list stocks in the signals sections on the main page
|
25
26
|
self.current_signal="HOLD"
|
26
27
|
self.ema_open_signal="HOLD"
|
27
28
|
self.ema_close_signal="HOLD"
|
28
29
|
self.trendline_signal = "HOLD"
|
30
|
+
self.bos_signal = "HOLD"
|
29
31
|
self.macd_signal="HOLD"
|
30
32
|
self.bbm_signal="HOLD"
|
31
33
|
self.rsi_signal="HOLD"
|
@@ -63,20 +65,6 @@ class Interval:
|
|
63
65
|
# Get the last color and its consecutive count
|
64
66
|
self.signal_strength = df['Consecutive'].iloc[-1]
|
65
67
|
|
66
|
-
# Break of Strcuture
|
67
|
-
if self.settings.BOS_STUDY:
|
68
|
-
high = df.iloc[:-1]['high'].max()
|
69
|
-
low = df.iloc[:-1]['low'].min()
|
70
|
-
close = df['close'].iloc[-1]
|
71
|
-
if close > high:
|
72
|
-
self.bos_signal = "BUY"
|
73
|
-
elif close < low:
|
74
|
-
self.bos_signal = "SELL"
|
75
|
-
else:
|
76
|
-
self.bos_signal = "HOLD"
|
77
|
-
else:
|
78
|
-
self.bos_signal = "HOLD"
|
79
|
-
|
80
68
|
# Calculate the Moving Averages
|
81
69
|
if self.settings.EMA_STUDY:
|
82
70
|
df['ma_fast'] = talib.EMA(df['close'], timeperiod=self.settings.MA_FAST)
|
@@ -245,27 +233,47 @@ class Interval:
|
|
245
233
|
# Drop RSI columns if not used
|
246
234
|
df.drop(['rsi_fast', 'rsi_slow', 'rsi_slope', 'rsi_angle'], axis=1, inplace=True, errors='ignore')
|
247
235
|
|
236
|
+
# Break of Strcuture
|
237
|
+
if self.settings.BOS_STUDY:
|
238
|
+
df_sr= sr.support_resistance_bos_lines(df[['time','open', 'high', 'low', 'close']], len(df), self.settings.BOS_PERIOD)
|
239
|
+
if df_sr is not None and len(df_sr) >= 1:
|
240
|
+
df['bos_support_line'] = df_sr['support_line']
|
241
|
+
df['bos_resistance_line'] = df_sr['resistance_line']
|
242
|
+
df.fillna({'bos_support_line':0}, inplace=True)
|
243
|
+
df.fillna({'bos_resistance_line':0}, inplace=True)
|
244
|
+
|
245
|
+
if df['high'].size > 1 and df['low'].size > 1 and df['close'].size > 1:
|
246
|
+
if df['close'].iloc[-1] > df['bos_resistance_line'].iloc[-1]:
|
247
|
+
self.bos_signal = "BUY"
|
248
|
+
elif df['close'].iloc[-1] < df['bos_support_line'].iloc[-1]:
|
249
|
+
self.bos_signal = "SELL"
|
250
|
+
else:
|
251
|
+
self.bos_signal = "HOLD"
|
252
|
+
else:
|
253
|
+
self.bos_signal = "HOLD"
|
254
|
+
|
248
255
|
#Trendline
|
249
256
|
if self.settings.TRENDLINE_STUDY:
|
250
257
|
loopback = len(df)
|
251
258
|
df_sr= sr.support_resistance_trend_lines(df[['time','open', 'high', 'low', 'close']], loopback, prominence=None, distance=None if self.settings.TRENDLINE_PEAK_DISTANCE==0 else self.settings.TRENDLINE_PEAK_DISTANCE)
|
252
259
|
if df_sr is not None and len(df_sr) >= 1:
|
253
|
-
df['
|
254
|
-
df['
|
255
|
-
df.fillna({'
|
256
|
-
df.fillna({'
|
260
|
+
df['trend_support_line'] = df_sr['support_line']
|
261
|
+
df['trend_resistance_line'] = df_sr['resistance_line']
|
262
|
+
df.fillna({'trend_support_line':0}, inplace=True)
|
263
|
+
df.fillna({'trend_resistance_line':0}, inplace=True)
|
264
|
+
|
257
265
|
if df is not None and len(df) >= 2:
|
258
266
|
if self.settings.TRENDLINE_BREAKOUT:
|
259
|
-
if df['close'].iloc[-
|
267
|
+
if df['close'].iloc[-1] > df['trend_resistance_line'].iloc[-1]:
|
260
268
|
self.trendline_signal = "BUY"
|
261
|
-
elif df['close'].iloc[-
|
269
|
+
elif df['close'].iloc[-1] < df['trend_support_line'].iloc[-1]:
|
262
270
|
self.trendline_signal = "SELL"
|
263
271
|
else:
|
264
272
|
self.trendline_signal = "HOLD"
|
265
273
|
else:
|
266
|
-
if df['
|
274
|
+
if df['close'].iloc[-1] < df['trend_resistance_line'].iloc[-1] and df['close'].iloc[-2] < df['open'].iloc[-2] and df['close'].iloc[-1] < df['open'].iloc[-1]:
|
267
275
|
self.trendline_signal = "SELL"
|
268
|
-
elif df['
|
276
|
+
elif df['close'].iloc[-1] > df['trend_support_line'].iloc[-1] and df['close'].iloc[-2] > df['open'].iloc[-2] and df['close'].iloc[-1] > df['open'].iloc[-1]:
|
269
277
|
self.trendline_signal = "BUY"
|
270
278
|
else:
|
271
279
|
self.trendline_signal = "HOLD"
|
@@ -314,7 +322,7 @@ class Interval:
|
|
314
322
|
|
315
323
|
# Check for BUY signal
|
316
324
|
buy_conditions = (
|
317
|
-
(self.settings.BOS_STUDY and self.bos_signal == "BUY") or
|
325
|
+
(self.settings.BOS_STUDY and self.settings.BOS_CHECK_ON_OPEN and self.bos_signal == "BUY") or
|
318
326
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY") or
|
319
327
|
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY") or
|
320
328
|
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY") or
|
@@ -328,7 +336,7 @@ class Interval:
|
|
328
336
|
|
329
337
|
# Check for SELL signal
|
330
338
|
sell_conditions = (
|
331
|
-
(self.settings.BOS_STUDY and self.bos_signal == "SELL") or
|
339
|
+
(self.settings.BOS_STUDY and self.settings.BOS_CHECK_ON_OPEN and self.bos_signal == "SELL") or
|
332
340
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL") or
|
333
341
|
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "SELL") or
|
334
342
|
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "SELL") or
|
@@ -349,7 +357,7 @@ class Interval:
|
|
349
357
|
self.current_signal = "HOLD"
|
350
358
|
else:
|
351
359
|
buy_conditions = (
|
352
|
-
(self.settings.BOS_STUDY and self.bos_signal == "BUY")
|
360
|
+
(self.settings.BOS_STUDY and self.settings.BOS_CHECK_ON_OPEN and self.bos_signal == "BUY")
|
353
361
|
or not self.settings.BOS_STUDY
|
354
362
|
) and (
|
355
363
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY")
|
@@ -383,7 +391,7 @@ class Interval:
|
|
383
391
|
|
384
392
|
# Check for SELL signal
|
385
393
|
sell_conditions = (
|
386
|
-
(self.settings.BOS_STUDY and self.bos_signal == "SELL")
|
394
|
+
(self.settings.BOS_STUDY and self.settings.BOS_CHECK_ON_OPEN and self.bos_signal == "SELL")
|
387
395
|
or not self.settings.BOS_STUDY
|
388
396
|
) and (
|
389
397
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL")
|
@@ -425,8 +433,8 @@ class Interval:
|
|
425
433
|
|
426
434
|
|
427
435
|
except Exception as e:
|
428
|
-
logger.info(f"Function: calculate_study, {e}, {e.args}, {type(e).__name__}")
|
429
|
-
logger.info(traceback.print_exc())
|
436
|
+
self.logger.info(f"Function: calculate_study, {e}, {e.args}, {type(e).__name__}")
|
437
|
+
self.logger.info(traceback.print_exc())
|
430
438
|
retval = df.to_dict('records')
|
431
439
|
del df
|
432
440
|
gc.collect()
|
@@ -434,10 +442,10 @@ class Interval:
|
|
434
442
|
|
435
443
|
class Ticker:
|
436
444
|
|
437
|
-
def __init__(self, symbol, intervalIds, settings):
|
445
|
+
def __init__(self, symbol, intervalIds, settings, logger):
|
438
446
|
self.symbol=symbol
|
439
447
|
self.settings=settings
|
440
|
-
|
448
|
+
self.logger=logger
|
441
449
|
self._ts=0
|
442
450
|
self._bid = 0.0
|
443
451
|
self.bidcolor = ""
|
@@ -448,11 +456,11 @@ class Ticker:
|
|
448
456
|
self._mtv=0.0
|
449
457
|
self.intervalIds=intervalIds
|
450
458
|
self._intervals = {
|
451
|
-
'1m' : Interval(self.symbol, '1m', 60000, [], self.settings),
|
452
|
-
'5m' : Interval(self.symbol, '5m', 300000, [], self.settings),
|
453
|
-
'15m': Interval(self.symbol, '15m', 900000, [], self.settings),
|
454
|
-
'1h' : Interval(self.symbol, '1h', 3600000, [], self.settings),
|
455
|
-
'1d' : Interval(self.symbol, '1d', 86400000, [], self.settings),
|
459
|
+
'1m' : Interval(self.symbol, '1m', 60000, [], self.settings, self.logger),
|
460
|
+
'5m' : Interval(self.symbol, '5m', 300000, [], self.settings, self.logger),
|
461
|
+
'15m': Interval(self.symbol, '15m', 900000, [], self.settings, self.logger),
|
462
|
+
'1h' : Interval(self.symbol, '1h', 3600000, [], self.settings, self.logger),
|
463
|
+
'1d' : Interval(self.symbol, '1d', 86400000, [], self.settings, self.logger),
|
456
464
|
}
|
457
465
|
self.current_data={}
|
458
466
|
self.trades = []
|
@@ -460,6 +468,8 @@ class Ticker:
|
|
460
468
|
self.green="#A5DFDF"
|
461
469
|
self.red="#FFB1C1"
|
462
470
|
|
471
|
+
self.logger = logger
|
472
|
+
|
463
473
|
def update_settings(self, settings):
|
464
474
|
self.settings=settings
|
465
475
|
for intervalId in self.intervalIds:
|
@@ -577,12 +587,12 @@ class Ticker:
|
|
577
587
|
intervalObj.set_data(ticks_interval)
|
578
588
|
|
579
589
|
except Exception as e:
|
580
|
-
logger.info(f"Function: create_bar_with_last_and_ts, {e}, {e.args}, {type(e).__name__}")
|
581
|
-
logger.info(traceback.print_exc())
|
590
|
+
self.logger.info(f"Function: create_bar_with_last_and_ts, {e}, {e.args}, {type(e).__name__}")
|
591
|
+
self.logger.info(traceback.print_exc())
|
582
592
|
|
583
593
|
|
584
594
|
class Tickers:
|
585
|
-
def __init__(self, settings):
|
595
|
+
def __init__(self, settings, logger):
|
586
596
|
self.settings=settings
|
587
597
|
|
588
598
|
self._tickerObjects={}
|
@@ -595,13 +605,15 @@ class Tickers:
|
|
595
605
|
|
596
606
|
self.intervalIds=['1m','5m','15m','1h','1d']
|
597
607
|
|
608
|
+
self.logger = logger
|
609
|
+
|
598
610
|
def update_settings(self, settings):
|
599
611
|
self.settings=settings
|
600
612
|
for symbol in self._tickerObjects:
|
601
613
|
self._tickerObjects[symbol].update_settings(settings)
|
602
614
|
|
603
615
|
def add(self, symbol):
|
604
|
-
ticker = Ticker(symbol, self.intervalIds, self.settings)
|
616
|
+
ticker = Ticker(symbol, self.intervalIds, self.settings, self.logger)
|
605
617
|
self._tickerObjects[symbol]=ticker
|
606
618
|
|
607
619
|
def get_tickerDict(self):
|
@@ -727,13 +739,13 @@ class Tickers:
|
|
727
739
|
f"{period}_trend": intervalObj.current_signal,
|
728
740
|
f"{period}_cb": intervalObj.signal_strength,
|
729
741
|
f"{period}_barcolor": lastcandle['barcolor'],
|
730
|
-
f"{period}_bos": intervalObj.bos_signal,
|
731
742
|
f"{period}_ema_open": intervalObj.ema_open_signal,
|
732
743
|
f"{period}_ema_open": intervalObj.ema_open_signal,
|
733
744
|
f"{period}_ema_close": intervalObj.ema_close_signal,
|
734
745
|
f"{period}_macd":intervalObj.macd_signal,
|
735
746
|
f"{period}_bbm":intervalObj.bbm_signal,
|
736
747
|
f"{period}_rsi":intervalObj.rsi_signal,
|
748
|
+
f"{period}_bos": intervalObj.bos_signal,
|
737
749
|
f"{period}_trendline": intervalObj.trendline_signal,
|
738
750
|
f"{period}_adx":intervalObj.adx_signal,
|
739
751
|
f"{period}_candle_trend":intervalObj.candle_trend,
|
@@ -779,8 +791,8 @@ class Tickers:
|
|
779
791
|
]
|
780
792
|
self.signaldf_filtered = df[np.any(trending_conditions, axis=0)].copy()
|
781
793
|
except Exception as e:
|
782
|
-
logger.info(f"Function: getCurrentData, {e}, {e.args}, {type(e).__name__}")
|
783
|
-
logger.info(traceback.print_exc())
|
794
|
+
self.logger.info(f"Function: getCurrentData, {e}, {e.args}, {type(e).__name__}")
|
795
|
+
self.logger.info(traceback.print_exc())
|
784
796
|
finally:
|
785
797
|
del df, current_data
|
786
798
|
gc.collect()
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
import os
|
3
|
+
import sys
|
3
4
|
import uvicorn
|
4
5
|
import numpy as np
|
5
6
|
import pandas as pd
|
@@ -17,7 +18,6 @@ from NotificationManager import NotificationManager
|
|
17
18
|
from DataFrameHtmlRenderer import DataFrameHtmlRenderer
|
18
19
|
from config import Settings
|
19
20
|
from logger import Logger
|
20
|
-
logger = Logger(__name__).get_logger()
|
21
21
|
|
22
22
|
from fastapi import FastAPI, Request, Form, WebSocket, WebSocketDisconnect, Depends, Query
|
23
23
|
from fastapi.responses import HTMLResponse , JSONResponse, RedirectResponse, StreamingResponse
|
@@ -34,9 +34,24 @@ from pydantic import ValidationError
|
|
34
34
|
|
35
35
|
import sqlite3
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
|
38
|
+
global LOG_FILE
|
39
|
+
global ENV_FILE
|
40
|
+
global CONFIG_FILE
|
41
|
+
global API_KEY
|
42
|
+
global SECRET_KEY
|
43
|
+
global SECRET
|
44
|
+
global PASSWORD
|
45
|
+
global HOST
|
46
|
+
global port
|
47
|
+
|
48
|
+
env_file = sys.argv[1] if len(sys.argv) > 1 else ".env"
|
49
|
+
config_file=sys.argv[2] if len(sys.argv) > 2 else "config.txt"
|
50
|
+
port = sys.argv[3] if len(sys.argv) > 3 else "8000"
|
51
|
+
|
52
|
+
ENV_FILE = os.path.dirname(os.path.abspath(__file__))+"/"+env_file
|
53
|
+
CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+"/"+config_file
|
54
|
+
LOG_FILE = os.path.dirname(os.path.abspath(__file__))+"/"+config_file.replace(".txt", ".log")
|
40
55
|
|
41
56
|
#load environment variables
|
42
57
|
load_dotenv(ENV_FILE)
|
@@ -46,23 +61,24 @@ SECRET = os.getenv('SECRET')
|
|
46
61
|
PASSWORD = os.getenv('PASSWORD')
|
47
62
|
HOST = os.getenv('HOST')
|
48
63
|
|
49
|
-
|
50
|
-
|
64
|
+
logger = Logger(__name__, LOG_FILE).get_logger()
|
65
|
+
|
66
|
+
#logger.info(f"secret: {SECRET}, api_key: {API_KEY}, secret_key: {SECRET_KEY}, password: {PASSWORD}, host: {HOST}, port: {port}, config_file: {config_file}")
|
51
67
|
|
52
68
|
class bitunix():
|
53
|
-
def __init__(self, password, api_key, secret_key, settings):
|
69
|
+
def __init__(self, password, api_key, secret_key, settings, logger):
|
54
70
|
self.screen_refresh_interval =settings.SCREEN_REFRESH_INTERVAL
|
55
|
-
|
71
|
+
self.logger=logger
|
56
72
|
self.autoTrade=settings.AUTOTRADE
|
57
73
|
|
58
74
|
self.threadManager = ThreadManager()
|
59
|
-
self.notifications = NotificationManager()
|
60
|
-
self.bitunixApi = BitunixApi(api_key, secret_key, settings)
|
61
|
-
self.bitunixSignal = BitunixSignal(api_key, secret_key, settings, self.threadManager, self.notifications, self.bitunixApi)
|
75
|
+
self.notifications = NotificationManager(logger)
|
76
|
+
self.bitunixApi = BitunixApi(api_key, secret_key, settings, self.logger)
|
77
|
+
self.bitunixSignal = BitunixSignal(api_key, secret_key, settings, self.threadManager, self.notifications, self.bitunixApi, self.logger)
|
62
78
|
|
63
79
|
self.websocket_connections = set()
|
64
80
|
self.DB = {"admin": {"password": password}}
|
65
|
-
|
81
|
+
|
66
82
|
#sqllite database connection
|
67
83
|
self.connection = sqlite3.connect("bitunix.db")
|
68
84
|
#create table if not exist
|
@@ -104,7 +120,7 @@ app.add_middleware(
|
|
104
120
|
allow_headers=["*"],
|
105
121
|
)
|
106
122
|
templates = Jinja2Templates(directory=os.path.dirname(os.path.abspath(__file__))+"/templates")
|
107
|
-
SECRET=os.getenv('SECRET')
|
123
|
+
#SECRET=os.getenv('SECRET')
|
108
124
|
login_manager = LoginManager(SECRET, token_url="/auth/login", use_cookie=True)
|
109
125
|
login_manager.cookie_name = "auth_token"
|
110
126
|
|
@@ -242,6 +258,7 @@ async def wsmain(websocket):
|
|
242
258
|
"profit" : bitunix.bitunixSignal.profit,
|
243
259
|
"atctime": atctime,
|
244
260
|
"tdctime": tdctime,
|
261
|
+
"port" : port,
|
245
262
|
"status_messages": [] if len(notifications)==0 else notifications
|
246
263
|
}
|
247
264
|
|
@@ -402,6 +419,8 @@ async def wscharts(websocket):
|
|
402
419
|
"ema_chart": settings.EMA_CHART,
|
403
420
|
"trendline_study": settings.TRENDLINE_STUDY,
|
404
421
|
"trendline_chart": settings.TRENDLINE_CHART,
|
422
|
+
"bos_study": settings.BOS_STUDY,
|
423
|
+
"bos_chart": settings.BOS_CHART,
|
405
424
|
"macd_study": settings.MACD_STUDY,
|
406
425
|
"macd_chart": settings.MACD_CHART,
|
407
426
|
"bbm_study": settings.BBM_STUDY,
|
@@ -491,6 +510,8 @@ async def wschart(websocket):
|
|
491
510
|
"ema_chart": settings.EMA_CHART,
|
492
511
|
"trendline_study": settings.TRENDLINE_STUDY,
|
493
512
|
"trendline_chart": settings.TRENDLINE_CHART,
|
513
|
+
"bos_study": settings.BOS_STUDY,
|
514
|
+
"bos_chart": settings.BOS_CHART,
|
494
515
|
"macd_study": settings.MACD_STUDY,
|
495
516
|
"macd_chart": settings.MACD_CHART,
|
496
517
|
"bbm_study": settings.BBM_STUDY,
|
@@ -604,14 +625,19 @@ async def stream_log_file(websocket: WebSocket):
|
|
604
625
|
|
605
626
|
def main():
|
606
627
|
global bitunix
|
607
|
-
|
608
|
-
|
628
|
+
global settings
|
629
|
+
|
630
|
+
#load config variables using setting class in config.py validating using pydantic
|
631
|
+
settings = Settings()
|
632
|
+
|
633
|
+
bitunix = bitunix(PASSWORD, API_KEY, SECRET_KEY, settings, logger)
|
634
|
+
bitunix.bitunixSignal.notifications.add_notification(f"Starting auto trade on port {port} using {env_file} and {config_file} ....................")
|
609
635
|
import uvicorn
|
610
636
|
if settings.VERBOSE_LOGGING:
|
611
637
|
llevel = "debug"
|
612
638
|
else:
|
613
639
|
llevel = "error"
|
614
|
-
config1 = uvicorn.Config(app, host=HOST, port=
|
640
|
+
config1 = uvicorn.Config(app, host=HOST, port=port, log_level=llevel, reload=False)
|
615
641
|
server = uvicorn.Server(config1)
|
616
642
|
server.run()
|
617
643
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from pydantic import Field, SecretStr, validator
|
2
2
|
from pydantic_settings import BaseSettings
|
3
3
|
import os
|
4
|
+
import sys
|
4
5
|
import sqlite3
|
5
6
|
|
6
7
|
class Settings(BaseSettings):
|
@@ -37,7 +38,12 @@ class Settings(BaseSettings):
|
|
37
38
|
# Technical Indicators
|
38
39
|
OPEN_ON_ANY_SIGNAL: bool = Field(default=True)
|
39
40
|
|
41
|
+
BOS_PERIOD: int = Field(default=20, ge=1)
|
40
42
|
BOS_STUDY: bool = Field(default=True)
|
43
|
+
BOS_CHART: bool = Field(default=True)
|
44
|
+
BOS_CHECK_ON_OPEN: bool = Field(default=False)
|
45
|
+
BOS_CHECK_ON_CLOSE: bool = Field(default=False)
|
46
|
+
|
41
47
|
EMA_CHART: bool = Field(default=True)
|
42
48
|
EMA_STUDY: bool = Field(default=True)
|
43
49
|
EMA_CROSSING: bool = Field(default=False)
|
@@ -103,7 +109,8 @@ class Settings(BaseSettings):
|
|
103
109
|
|
104
110
|
class Config:
|
105
111
|
# Specify the file name for loading environment variables
|
106
|
-
|
112
|
+
config_file=sys.argv[2] if len(sys.argv) > 1 else "config.txt"
|
113
|
+
env_file = os.path.dirname(os.path.abspath(__file__))+"/"+config_file
|
107
114
|
|
108
115
|
|
109
116
|
@classmethod
|
@@ -45,7 +45,7 @@ class CSTFormatter(logging.Formatter):
|
|
45
45
|
return cst_time.isoformat()
|
46
46
|
|
47
47
|
class Logger:
|
48
|
-
def __init__(self, logger_name, log_file
|
48
|
+
def __init__(self, logger_name, log_file, level=logging.DEBUG, max_bytes=50 * 1024 * 1024, backup_count=100):
|
49
49
|
"""
|
50
50
|
Initialize the logger.
|
51
51
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=bNIM_1xRYQOFEsIn74EX6qVpC59-GMhhr2CeiPr_GWg,3253
|
2
|
+
bitunix_automated_crypto_trading/BitunixApi.py,sha256=udgBwbWowf7v46jVVyfs-VjeNh9ajnghTiJf70lvn5w,11269
|
3
|
+
bitunix_automated_crypto_trading/BitunixSignal.py,sha256=hZvu1FKhdHqESIUmDMMlVdnxsROnJHTMCfGSitrXNiM,70882
|
4
|
+
bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=-_7wRAZxJXG63joBq2k3TUC4qd06v7Miki-LGVtNIDk,11234
|
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=_aP9G_DQ16bHqZv8HdxIairqeZzqB8ipRUhBPijN7lk,5767
|
8
|
+
bitunix_automated_crypto_trading/ThreadManager.py,sha256=Lw5_1EIT0m3AFSv5CIMpnjtA0DnNw2qQ6JtSpT34LyM,2349
|
9
|
+
bitunix_automated_crypto_trading/TickerManager.py,sha256=B9HlgvBLIZ2skBCEojY0M0BS5ZvZR2rw4l25LhuTt1o,42307
|
10
|
+
bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
|
11
|
+
bitunix_automated_crypto_trading/bitunix.py,sha256=wEzJpa1Gv84tYJK1iV-kQVezbn1TxJuAk-CY7kwKShM,26887
|
12
|
+
bitunix_automated_crypto_trading/config.py,sha256=cdbPFRNbDPw2mxEKL4asffVtfUcq_zM0zTVqY0o3Hg4,5300
|
13
|
+
bitunix_automated_crypto_trading/logger.py,sha256=NHnA5JZdUFkTAhB7i-1iCAwrdf1fxhDuRvJUkbKPi9Y,2923
|
14
|
+
bitunix_automated_crypto_trading-3.1.8.dist-info/METADATA,sha256=pMRfJvAcx91aQ8_8ltWv8Uo2IGn5s1nGBOQmdkgH-3U,996
|
15
|
+
bitunix_automated_crypto_trading-3.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
+
bitunix_automated_crypto_trading-3.1.8.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
|
17
|
+
bitunix_automated_crypto_trading-3.1.8.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
|
18
|
+
bitunix_automated_crypto_trading-3.1.8.dist-info/RECORD,,
|
@@ -1,18 +0,0 @@
|
|
1
|
-
bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=LJ6ny1KSCVoIbfeNypsY4aHYcbkGME9M3epQ9e8B1O8,3224
|
2
|
-
bitunix_automated_crypto_trading/BitunixApi.py,sha256=W0uem1wIs1uM-jaGtXXzL_JQfnCDb7imyTZ2Tqjk8e8,11230
|
3
|
-
bitunix_automated_crypto_trading/BitunixSignal.py,sha256=JIJumU3nYcO_NRS5xMqrPeisMIiy_TM0EOEA5nr44cQ,67993
|
4
|
-
bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=mbuvk8UFWKgv4KLV07TgLgxLVTRJnOKuf02mLB-VoCY,11143
|
5
|
-
bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
|
6
|
-
bitunix_automated_crypto_trading/NotificationManager.py,sha256=pqDquEe-oujD2v8B543524vo62aRMjfB4YW-3DMhVGQ,795
|
7
|
-
bitunix_automated_crypto_trading/SupportResistance.py,sha256=Dx4uBC4On-GO1t2EFk02jzSmZzaMD48mUJI9rG2rgMo,4946
|
8
|
-
bitunix_automated_crypto_trading/ThreadManager.py,sha256=WmYRQu8aNrgp5HwSqpBqX1WESNSEpbD2CznPFpd9HuI,2330
|
9
|
-
bitunix_automated_crypto_trading/TickerManager.py,sha256=n2GyvfH2YMuZ31Q_IcLd4U3ZOEIOLhLwMIR8iI5Gp5A,41460
|
10
|
-
bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
|
11
|
-
bitunix_automated_crypto_trading/bitunix.py,sha256=Zyrf8P-xi7DJogX_bcc6uDW66J7FAxdS-0jbuHRPFfM,25845
|
12
|
-
bitunix_automated_crypto_trading/config.py,sha256=aBBHdkMnw4ODzcwQl1CCx_r1O08hShDRGlSA6JXt5lg,5024
|
13
|
-
bitunix_automated_crypto_trading/logger.py,sha256=tAaTQcv5r--zupk_Fhfe1USfBAzSiXzVa4QRnaOFIFY,2933
|
14
|
-
bitunix_automated_crypto_trading-3.1.6.dist-info/METADATA,sha256=CzoCXqOg2hhZOrZZ9UMEnC3ohtqNrWEi6xT17G7pjq0,996
|
15
|
-
bitunix_automated_crypto_trading-3.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
-
bitunix_automated_crypto_trading-3.1.6.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
|
17
|
-
bitunix_automated_crypto_trading-3.1.6.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
|
18
|
-
bitunix_automated_crypto_trading-3.1.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|