bitunix-automated-crypto-trading 3.1.7__py3-none-any.whl → 3.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -47
- bitunix_automated_crypto_trading/bitunix.py +42 -16
- bitunix_automated_crypto_trading/config.py +8 -2
- bitunix_automated_crypto_trading/logger.py +1 -1
- {bitunix_automated_crypto_trading-3.1.7.dist-info → bitunix_automated_crypto_trading-3.1.9.dist-info}/METADATA +1 -1
- bitunix_automated_crypto_trading-3.1.9.dist-info/RECORD +18 -0
- bitunix_automated_crypto_trading-3.1.7.dist-info/RECORD +0 -18
- {bitunix_automated_crypto_trading-3.1.7.dist-info → bitunix_automated_crypto_trading-3.1.9.dist-info}/WHEEL +0 -0
- {bitunix_automated_crypto_trading-3.1.7.dist-info → bitunix_automated_crypto_trading-3.1.9.dist-info}/entry_points.txt +0 -0
- {bitunix_automated_crypto_trading-3.1.7.dist-info → bitunix_automated_crypto_trading-3.1.9.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,23 +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
|
-
if df['high'].size > 1 and df['low'].size > 1 and df['close'].size > 1:
|
69
|
-
high = talib.MAX(df.iloc[:-1]['high'].values, timeperiod=self.settings.BOS_PERIOD)[-1]
|
70
|
-
low = talib.MIN(df.iloc[:-1]['low'].values, timeperiod=self.settings.BOS_PERIOD)[-1]
|
71
|
-
close = df['close'].iloc[-1]
|
72
|
-
if close > high:
|
73
|
-
self.bos_signal = "BUY"
|
74
|
-
elif close < low:
|
75
|
-
self.bos_signal = "SELL"
|
76
|
-
else:
|
77
|
-
self.bos_signal = "HOLD"
|
78
|
-
else:
|
79
|
-
self.bos_signal = "HOLD"
|
80
|
-
else:
|
81
|
-
self.bos_signal = "HOLD"
|
82
|
-
|
83
68
|
# Calculate the Moving Averages
|
84
69
|
if self.settings.EMA_STUDY:
|
85
70
|
df['ma_fast'] = talib.EMA(df['close'], timeperiod=self.settings.MA_FAST)
|
@@ -248,27 +233,47 @@ class Interval:
|
|
248
233
|
# Drop RSI columns if not used
|
249
234
|
df.drop(['rsi_fast', 'rsi_slow', 'rsi_slope', 'rsi_angle'], axis=1, inplace=True, errors='ignore')
|
250
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
|
+
|
251
255
|
#Trendline
|
252
256
|
if self.settings.TRENDLINE_STUDY:
|
253
257
|
loopback = len(df)
|
254
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)
|
255
259
|
if df_sr is not None and len(df_sr) >= 1:
|
256
|
-
df['
|
257
|
-
df['
|
258
|
-
df.fillna({'
|
259
|
-
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
|
+
|
260
265
|
if df is not None and len(df) >= 2:
|
261
266
|
if self.settings.TRENDLINE_BREAKOUT:
|
262
|
-
if df['close'].iloc[-
|
267
|
+
if df['close'].iloc[-1] > df['trend_resistance_line'].iloc[-1]:
|
263
268
|
self.trendline_signal = "BUY"
|
264
|
-
elif df['close'].iloc[-
|
269
|
+
elif df['close'].iloc[-1] < df['trend_support_line'].iloc[-1]:
|
265
270
|
self.trendline_signal = "SELL"
|
266
271
|
else:
|
267
272
|
self.trendline_signal = "HOLD"
|
268
273
|
else:
|
269
|
-
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]:
|
270
275
|
self.trendline_signal = "SELL"
|
271
|
-
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]:
|
272
277
|
self.trendline_signal = "BUY"
|
273
278
|
else:
|
274
279
|
self.trendline_signal = "HOLD"
|
@@ -317,7 +322,7 @@ class Interval:
|
|
317
322
|
|
318
323
|
# Check for BUY signal
|
319
324
|
buy_conditions = (
|
320
|
-
(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
|
321
326
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY") or
|
322
327
|
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY") or
|
323
328
|
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY") or
|
@@ -331,7 +336,7 @@ class Interval:
|
|
331
336
|
|
332
337
|
# Check for SELL signal
|
333
338
|
sell_conditions = (
|
334
|
-
(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
|
335
340
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL") or
|
336
341
|
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "SELL") or
|
337
342
|
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "SELL") or
|
@@ -352,7 +357,7 @@ class Interval:
|
|
352
357
|
self.current_signal = "HOLD"
|
353
358
|
else:
|
354
359
|
buy_conditions = (
|
355
|
-
(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")
|
356
361
|
or not self.settings.BOS_STUDY
|
357
362
|
) and (
|
358
363
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY")
|
@@ -386,7 +391,7 @@ class Interval:
|
|
386
391
|
|
387
392
|
# Check for SELL signal
|
388
393
|
sell_conditions = (
|
389
|
-
(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")
|
390
395
|
or not self.settings.BOS_STUDY
|
391
396
|
) and (
|
392
397
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL")
|
@@ -428,8 +433,8 @@ class Interval:
|
|
428
433
|
|
429
434
|
|
430
435
|
except Exception as e:
|
431
|
-
logger.info(f"Function: calculate_study, {e}, {e.args}, {type(e).__name__}")
|
432
|
-
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())
|
433
438
|
retval = df.to_dict('records')
|
434
439
|
del df
|
435
440
|
gc.collect()
|
@@ -437,10 +442,10 @@ class Interval:
|
|
437
442
|
|
438
443
|
class Ticker:
|
439
444
|
|
440
|
-
def __init__(self, symbol, intervalIds, settings):
|
445
|
+
def __init__(self, symbol, intervalIds, settings, logger):
|
441
446
|
self.symbol=symbol
|
442
447
|
self.settings=settings
|
443
|
-
|
448
|
+
self.logger=logger
|
444
449
|
self._ts=0
|
445
450
|
self._bid = 0.0
|
446
451
|
self.bidcolor = ""
|
@@ -451,11 +456,11 @@ class Ticker:
|
|
451
456
|
self._mtv=0.0
|
452
457
|
self.intervalIds=intervalIds
|
453
458
|
self._intervals = {
|
454
|
-
'1m' : Interval(self.symbol, '1m', 60000, [], self.settings),
|
455
|
-
'5m' : Interval(self.symbol, '5m', 300000, [], self.settings),
|
456
|
-
'15m': Interval(self.symbol, '15m', 900000, [], self.settings),
|
457
|
-
'1h' : Interval(self.symbol, '1h', 3600000, [], self.settings),
|
458
|
-
'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),
|
459
464
|
}
|
460
465
|
self.current_data={}
|
461
466
|
self.trades = []
|
@@ -463,6 +468,8 @@ class Ticker:
|
|
463
468
|
self.green="#A5DFDF"
|
464
469
|
self.red="#FFB1C1"
|
465
470
|
|
471
|
+
self.logger = logger
|
472
|
+
|
466
473
|
def update_settings(self, settings):
|
467
474
|
self.settings=settings
|
468
475
|
for intervalId in self.intervalIds:
|
@@ -580,12 +587,12 @@ class Ticker:
|
|
580
587
|
intervalObj.set_data(ticks_interval)
|
581
588
|
|
582
589
|
except Exception as e:
|
583
|
-
logger.info(f"Function: create_bar_with_last_and_ts, {e}, {e.args}, {type(e).__name__}")
|
584
|
-
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())
|
585
592
|
|
586
593
|
|
587
594
|
class Tickers:
|
588
|
-
def __init__(self, settings):
|
595
|
+
def __init__(self, settings, logger):
|
589
596
|
self.settings=settings
|
590
597
|
|
591
598
|
self._tickerObjects={}
|
@@ -598,13 +605,15 @@ class Tickers:
|
|
598
605
|
|
599
606
|
self.intervalIds=['1m','5m','15m','1h','1d']
|
600
607
|
|
608
|
+
self.logger = logger
|
609
|
+
|
601
610
|
def update_settings(self, settings):
|
602
611
|
self.settings=settings
|
603
612
|
for symbol in self._tickerObjects:
|
604
613
|
self._tickerObjects[symbol].update_settings(settings)
|
605
614
|
|
606
615
|
def add(self, symbol):
|
607
|
-
ticker = Ticker(symbol, self.intervalIds, self.settings)
|
616
|
+
ticker = Ticker(symbol, self.intervalIds, self.settings, self.logger)
|
608
617
|
self._tickerObjects[symbol]=ticker
|
609
618
|
|
610
619
|
def get_tickerDict(self):
|
@@ -730,13 +739,13 @@ class Tickers:
|
|
730
739
|
f"{period}_trend": intervalObj.current_signal,
|
731
740
|
f"{period}_cb": intervalObj.signal_strength,
|
732
741
|
f"{period}_barcolor": lastcandle['barcolor'],
|
733
|
-
f"{period}_bos": intervalObj.bos_signal,
|
734
742
|
f"{period}_ema_open": intervalObj.ema_open_signal,
|
735
743
|
f"{period}_ema_open": intervalObj.ema_open_signal,
|
736
744
|
f"{period}_ema_close": intervalObj.ema_close_signal,
|
737
745
|
f"{period}_macd":intervalObj.macd_signal,
|
738
746
|
f"{period}_bbm":intervalObj.bbm_signal,
|
739
747
|
f"{period}_rsi":intervalObj.rsi_signal,
|
748
|
+
f"{period}_bos": intervalObj.bos_signal,
|
740
749
|
f"{period}_trendline": intervalObj.trendline_signal,
|
741
750
|
f"{period}_adx":intervalObj.adx_signal,
|
742
751
|
f"{period}_candle_trend":intervalObj.candle_trend,
|
@@ -782,8 +791,8 @@ class Tickers:
|
|
782
791
|
]
|
783
792
|
self.signaldf_filtered = df[np.any(trending_conditions, axis=0)].copy()
|
784
793
|
except Exception as e:
|
785
|
-
logger.info(f"Function: getCurrentData, {e}, {e.args}, {type(e).__name__}")
|
786
|
-
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())
|
787
796
|
finally:
|
788
797
|
del df, current_data
|
789
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):
|
@@ -22,7 +23,6 @@ class Settings(BaseSettings):
|
|
22
23
|
BARS: int = Field(default=100, ge=1)
|
23
24
|
|
24
25
|
# Technical Indicators Parameters
|
25
|
-
BOS_PERIOD: int = Field(default=20, ge=1)
|
26
26
|
MA_FAST: int = Field(default=10, ge=1)
|
27
27
|
MA_MEDIUM: int = Field(default=20, ge=1)
|
28
28
|
MA_SLOW: int = Field(default=50, ge=1)
|
@@ -38,7 +38,12 @@ class Settings(BaseSettings):
|
|
38
38
|
# Technical Indicators
|
39
39
|
OPEN_ON_ANY_SIGNAL: bool = Field(default=True)
|
40
40
|
|
41
|
+
BOS_PERIOD: int = Field(default=20, ge=1)
|
41
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
|
+
|
42
47
|
EMA_CHART: bool = Field(default=True)
|
43
48
|
EMA_STUDY: bool = Field(default=True)
|
44
49
|
EMA_CROSSING: bool = Field(default=False)
|
@@ -104,7 +109,8 @@ class Settings(BaseSettings):
|
|
104
109
|
|
105
110
|
class Config:
|
106
111
|
# Specify the file name for loading environment variables
|
107
|
-
|
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
|
108
114
|
|
109
115
|
|
110
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=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=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.9.dist-info/METADATA,sha256=WKb5fZi1xjoXmcX8IErXwTlpVcfTSMHF7GDqAQVyovw,996
|
15
|
+
bitunix_automated_crypto_trading-3.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
+
bitunix_automated_crypto_trading-3.1.9.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
|
17
|
+
bitunix_automated_crypto_trading-3.1.9.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
|
18
|
+
bitunix_automated_crypto_trading-3.1.9.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=5V86dZEEFIFf2W1-qWKYuK0E9L7lxcK_TyaVAzS1OEg,41769
|
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=zio989qxqU52dCdyUvudtBqVZpDCGLgypy09cECcAc0,5070
|
13
|
-
bitunix_automated_crypto_trading/logger.py,sha256=tAaTQcv5r--zupk_Fhfe1USfBAzSiXzVa4QRnaOFIFY,2933
|
14
|
-
bitunix_automated_crypto_trading-3.1.7.dist-info/METADATA,sha256=4lxIzSR06V9fh5oVzCHDgISGb21zE8JeV7vPbroiJnY,996
|
15
|
-
bitunix_automated_crypto_trading-3.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
-
bitunix_automated_crypto_trading-3.1.7.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
|
17
|
-
bitunix_automated_crypto_trading-3.1.7.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
|
18
|
-
bitunix_automated_crypto_trading-3.1.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|