bitunix-automated-crypto-trading 3.1.3__py3-none-any.whl → 3.1.5__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/BitunixSignal.py +62 -31
- bitunix_automated_crypto_trading/SupportResistance.py +89 -0
- bitunix_automated_crypto_trading/TickerManager.py +52 -67
- bitunix_automated_crypto_trading/config.py +2 -1
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.5.dist-info}/METADATA +1 -1
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.5.dist-info}/RECORD +9 -8
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.5.dist-info}/WHEEL +1 -1
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.5.dist-info}/entry_points.txt +0 -0
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.5.dist-info}/top_level.txt +0 -0
@@ -111,7 +111,7 @@ class BitunixSignal:
|
|
111
111
|
newlist=olist+plist+list(set(symbols))
|
112
112
|
self.tickerList=newlist[:300]
|
113
113
|
self.tickerList.remove("STMXUSDT")
|
114
|
-
#self.tickerList=['
|
114
|
+
#self.tickerList=['POLUSDT']
|
115
115
|
|
116
116
|
[await self.add_ticker_to_tickerObjects(sym) for sym in self.tickerList]
|
117
117
|
self.notifications.add_notification(f"{len(self.tickerList)} ticker list loaded")
|
@@ -271,12 +271,7 @@ class BitunixSignal:
|
|
271
271
|
# Function to add data to the last price deque
|
272
272
|
async def StoreTickerData(self, message):
|
273
273
|
if self.settings.USE_PUBLIC_WEBSOCKET and message:
|
274
|
-
|
275
|
-
data = json.loads(message)
|
276
|
-
if data.get('symbol') and data.get('ch') == 'ticker':
|
277
|
-
await self.ticker_que.put(data)
|
278
|
-
except json.JSONDecodeError as e:
|
279
|
-
logger.warning(f"Failed to decode message: {message}. Error: {e}")
|
274
|
+
await self.ticker_que.put(message)
|
280
275
|
|
281
276
|
# Function to process the last price deque
|
282
277
|
async def ProcessTickerData(self):
|
@@ -285,11 +280,14 @@ class BitunixSignal:
|
|
285
280
|
latest_data = {}
|
286
281
|
reversed_items = await self.drain_queue(self.ticker_que)
|
287
282
|
while reversed_items:
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
283
|
+
message = reversed_items.popleft()
|
284
|
+
data = json.loads(message)
|
285
|
+
if data.get('symbol') and data.get('ch') == 'ticker':
|
286
|
+
symbol = data["symbol"]
|
287
|
+
ts = data["ts"]
|
288
|
+
if symbol not in latest_data or ts > latest_data[symbol]['ts']:
|
289
|
+
latest_data[symbol] = {'ts': ts, 'last': float(data['data']['la'])}
|
290
|
+
await asyncio.sleep(0.01)
|
293
291
|
# Convert to DataFrame
|
294
292
|
self.tickerdf = pd.DataFrame.from_dict(latest_data, orient="index")
|
295
293
|
if not self.tickerdf.empty:
|
@@ -300,7 +298,7 @@ class BitunixSignal:
|
|
300
298
|
except Exception as e:
|
301
299
|
logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
|
302
300
|
logger.info(traceback.print_exc())
|
303
|
-
await asyncio.sleep(0.
|
301
|
+
await asyncio.sleep(0.01)
|
304
302
|
logger.info(f"ProcessTickerData: exitied out of the loop, exiting app")
|
305
303
|
os._exit(1) # Exit the program
|
306
304
|
|
@@ -308,13 +306,8 @@ class BitunixSignal:
|
|
308
306
|
#websocket data to update bid and ask
|
309
307
|
async def StoreDepthData(self, message):
|
310
308
|
if self.settings.USE_PUBLIC_WEBSOCKET and message:
|
311
|
-
|
312
|
-
|
313
|
-
if data.get('symbol') and data.get('ch') == 'depth_book1':
|
314
|
-
await self.depth_que.put(data)
|
315
|
-
except json.JSONDecodeError as e:
|
316
|
-
logger.warning(f"Failed to decode message: {message}. Error: {e}")
|
317
|
-
|
309
|
+
await self.depth_que.put(message)
|
310
|
+
|
318
311
|
# Function to process the bid, ask
|
319
312
|
async def ProcessDepthData(self):
|
320
313
|
while True:
|
@@ -322,11 +315,14 @@ class BitunixSignal:
|
|
322
315
|
latest_data = {}
|
323
316
|
reversed_items = await self.drain_queue(self.depth_que)
|
324
317
|
while reversed_items:
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
318
|
+
message = reversed_items.popleft()
|
319
|
+
data = json.loads(message)
|
320
|
+
if data.get('symbol') and data.get('ch') == 'depth_book1':
|
321
|
+
symbol = data["symbol"]
|
322
|
+
ts = data["ts"]
|
323
|
+
if symbol not in latest_data or ts > latest_data[symbol]['ts']:
|
324
|
+
latest_data[symbol] = {'ts': ts, 'bid': float(data['data']['b'][0][0]), 'ask': float(data['data']['a'][0][0])}
|
325
|
+
await asyncio.sleep(0.01)
|
330
326
|
# Convert to DataFrame
|
331
327
|
self.depthdf = pd.DataFrame.from_dict(latest_data, orient="index")
|
332
328
|
if not self.depthdf.empty:
|
@@ -335,7 +331,7 @@ class BitunixSignal:
|
|
335
331
|
except Exception as e:
|
336
332
|
logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
|
337
333
|
logger.info(traceback.print_exc())
|
338
|
-
await asyncio.sleep(0.
|
334
|
+
await asyncio.sleep(0.01)
|
339
335
|
logger.info(f"ProcessDepthData: exitied out of the loop, exiting app")
|
340
336
|
os._exit(1) # Exit the program
|
341
337
|
|
@@ -563,12 +559,12 @@ class BitunixSignal:
|
|
563
559
|
self.signaldf_filtered = self.tickerObjects.signaldf_filtered
|
564
560
|
|
565
561
|
if not self.positiondf.empty and not self.signaldf_full.empty:
|
566
|
-
columns=['symbol', f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_candle_trend", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low"]
|
562
|
+
columns=['symbol', f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_trendline", f"{period}_candle_trend", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low"]
|
567
563
|
columns2=["qty", "side", "unrealizedPNL", "realizedPNL", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
|
568
564
|
if set(columns).issubset(self.signaldf_full.columns) and set(columns2).issubset(self.positiondf.columns):
|
569
|
-
columnOrder= ['symbol', "side", "unrealizedPNL", "realizedPNL", f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_adx", f"{period}_candle_trend", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low", "qty", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
|
565
|
+
columnOrder= ['symbol', "side", "unrealizedPNL", "realizedPNL", f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_trendline", f"{period}_adx", f"{period}_candle_trend", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low", "qty", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
|
570
566
|
self.positiondf2 = pd.merge(self.positiondf, self.signaldf_full[["symbol", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
|
571
|
-
f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_adx", f"{period}_candle_trend",
|
567
|
+
f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_trendline", f"{period}_adx", f"{period}_candle_trend",
|
572
568
|
f"{period}_trend",f"{period}_cb", f"{period}_barcolor"]], left_on="symbol", right_index=True, how="left")[columnOrder]
|
573
569
|
self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf2)
|
574
570
|
else:
|
@@ -582,7 +578,7 @@ class BitunixSignal:
|
|
582
578
|
# Assign to self.signaldf for HTML rendering
|
583
579
|
self.signaldf = self.signaldf_filtered[[
|
584
580
|
"symbol", f"{period}_trend",f"{period}_cb", f"{period}_barcolor",
|
585
|
-
f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi",f"{period}_adx",f"{period}_candle_trend",
|
581
|
+
f"{period}_ema_open", f"{period}_ema_close", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_trendline",f"{period}_adx",f"{period}_candle_trend",
|
586
582
|
'lastcolor', 'bidcolor', 'askcolor', 'bid', 'last', 'ask',
|
587
583
|
f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
|
588
584
|
]].sort_values(by=[f'{period}_cb'], ascending=[False])
|
@@ -693,7 +689,7 @@ class BitunixSignal:
|
|
693
689
|
total_pnl = unrealized_pnl + realized_pnl
|
694
690
|
side=row['side']
|
695
691
|
|
696
|
-
requiredCols=[f'{period}_open', f'{period}_close', f'{period}_high', f'{period}_low', f'{period}_ema_open', f"{period}_ema_close", f'{period}_macd', f'{period}_bbm', f'{period}_rsi', f'{period}_candle_trend', f'{period}_trend', f'{period}_cb', f'{period}_barcolor']
|
692
|
+
requiredCols=[f'{period}_open', f'{period}_close', f'{period}_high', f'{period}_low', f'{period}_ema_open', f"{period}_ema_close", f'{period}_macd', f'{period}_bbm', f'{period}_rsi', f'{period}_trendline', f'{period}_candle_trend', f'{period}_trend', f'{period}_cb', f'{period}_barcolor']
|
697
693
|
required_cols = set(requiredCols)
|
698
694
|
|
699
695
|
# Close position that fall the below criteria
|
@@ -879,6 +875,41 @@ class BitunixSignal:
|
|
879
875
|
)
|
880
876
|
continue
|
881
877
|
|
878
|
+
# TrendLine
|
879
|
+
if self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_CLOSE:
|
880
|
+
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_trendline'] == "SELL" and total_pnl>0:
|
881
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
882
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
883
|
+
|
884
|
+
self.notifications.add_notification(
|
885
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to {period} trendline for {row.symbol} with {row.qty} qty @ {price})'
|
886
|
+
)
|
887
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
888
|
+
positionId=row.positionId,
|
889
|
+
ticker=row.symbol,
|
890
|
+
qty=row.qty,
|
891
|
+
price=price,
|
892
|
+
side=row.side,
|
893
|
+
tradeSide="CLOSE"
|
894
|
+
)
|
895
|
+
continue
|
896
|
+
|
897
|
+
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_trendline'] == "BUY" and total_pnl>0:
|
898
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
899
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
900
|
+
self.notifications.add_notification(
|
901
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to {period} trendline for {row.symbol} with {row.qty} qty @ {price})'
|
902
|
+
)
|
903
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
904
|
+
positionId=row.positionId,
|
905
|
+
ticker=row.symbol,
|
906
|
+
qty=row.qty,
|
907
|
+
price=price,
|
908
|
+
side=row.side,
|
909
|
+
tradeSide="CLOSE"
|
910
|
+
)
|
911
|
+
continue
|
912
|
+
|
882
913
|
# Close on weak trend after open
|
883
914
|
if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_CLOSE:
|
884
915
|
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_adx'] == "WEAK":
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import numpy as np
|
3
|
+
from scipy.signal import find_peaks
|
4
|
+
|
5
|
+
class SupportResistance:
|
6
|
+
import pandas as pd
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
class SupportResistance:
|
10
|
+
def _find_peaks(self, prices, prominence=None, distance=None):
|
11
|
+
"""Finds local peaks using scipy.signal.find_peaks."""
|
12
|
+
peaks, _ = find_peaks(prices, prominence=prominence, distance=distance)
|
13
|
+
return peaks
|
14
|
+
|
15
|
+
def _find_troughs(self, prices, prominence=None, distance=None):
|
16
|
+
"""Finds local troughs by finding peaks in the inverted prices."""
|
17
|
+
troughs, _ = find_peaks(-prices, prominence=prominence, distance=distance)
|
18
|
+
return troughs
|
19
|
+
|
20
|
+
def _get_n_most_recent_prominent(self, indices, prices, n=2):
|
21
|
+
"""Gets the indices of the n most recent prominent peaks or troughs."""
|
22
|
+
if not indices.size:
|
23
|
+
return np.array([])
|
24
|
+
# Sort indices by time (which is the index itself in our case) in descending order
|
25
|
+
sorted_indices_by_time = np.sort(indices)[::-1]
|
26
|
+
return sorted_indices_by_time[:n]
|
27
|
+
|
28
|
+
def _get_line(self, index1, price1, index2, price2, length):
|
29
|
+
"""Calculates the line equation (y = mx + c)."""
|
30
|
+
if index1 == index2:
|
31
|
+
return np.full(length, price1) # Horizontal line
|
32
|
+
slope = (price2 - price1) / (index2 - index1)
|
33
|
+
intercept = price1 - slope * index1
|
34
|
+
return slope * np.arange(length) + intercept
|
35
|
+
|
36
|
+
def support_resistance_trend_lines(self, data, lookback=20, prominence=None, distance=None):
|
37
|
+
if data is None or len(data) < lookback:
|
38
|
+
return pd.DataFrame()
|
39
|
+
try:
|
40
|
+
recent_data = data.iloc[-lookback:].copy()
|
41
|
+
high_prices = recent_data['high'].values
|
42
|
+
low_prices = recent_data['low'].values
|
43
|
+
ts = recent_data['time'].values
|
44
|
+
|
45
|
+
# Find all local peaks and troughs
|
46
|
+
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
|
+
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
|
+
|
58
|
+
support_line = np.full(lookback, np.nan)
|
59
|
+
resistance_line = np.full(lookback, np.nan)
|
60
|
+
|
61
|
+
if len(most_recent_peaks_indices) >= 2:
|
62
|
+
# Sort by index (time) to ensure correct order
|
63
|
+
sorted_peak_indices = most_recent_peaks_indices #np.sort(most_recent_peaks_indices)
|
64
|
+
resistance_line_full = self._get_line(sorted_peak_indices[0], high_prices[sorted_peak_indices[0]],
|
65
|
+
sorted_peak_indices[1], high_prices[sorted_peak_indices[1]], lookback)
|
66
|
+
# Set values before the second peak to NaN
|
67
|
+
resistance_line[sorted_peak_indices[1]:] = resistance_line_full[sorted_peak_indices[1]:]
|
68
|
+
elif len(most_recent_peaks_indices) == 1:
|
69
|
+
resistance_line[most_recent_peaks_indices[0]:] = high_prices[most_recent_peaks_indices[0]] # Horizontal line from the peak onwards
|
70
|
+
|
71
|
+
if len(most_recent_troughs_indices) >= 2:
|
72
|
+
# Sort by index (time) to ensure correct order
|
73
|
+
sorted_trough_indices = most_recent_troughs_indices #np.sort(most_recent_troughs_indices)
|
74
|
+
support_line_full = self._get_line(sorted_trough_indices[0], low_prices[sorted_trough_indices[0]],
|
75
|
+
sorted_trough_indices[1], low_prices[sorted_trough_indices[1]], lookback)
|
76
|
+
# Set values before the second trough to NaN
|
77
|
+
support_line[sorted_trough_indices[1]:] = support_line_full[sorted_trough_indices[1]:]
|
78
|
+
elif len(most_recent_troughs_indices) == 1:
|
79
|
+
support_line[most_recent_troughs_indices[0]:] = low_prices[most_recent_troughs_indices[0]] # Horizontal line from the trough onwards
|
80
|
+
|
81
|
+
results_df = pd.DataFrame({
|
82
|
+
'time': ts,
|
83
|
+
'support_line': support_line,
|
84
|
+
'resistance_line': resistance_line
|
85
|
+
})
|
86
|
+
return results_df
|
87
|
+
except Exception as e:
|
88
|
+
print(f"An error occurred: {e}")
|
89
|
+
return pd.DataFrame()
|
@@ -1,5 +1,7 @@
|
|
1
1
|
import pandas as pd
|
2
|
-
|
2
|
+
pd.set_option('future.no_silent_downcasting', True)
|
3
|
+
import warnings
|
4
|
+
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
3
5
|
import numpy as np
|
4
6
|
import asyncio
|
5
7
|
import talib
|
@@ -8,6 +10,8 @@ from logger import Logger
|
|
8
10
|
logger = Logger(__name__).get_logger()
|
9
11
|
import gc
|
10
12
|
from concurrent.futures import ProcessPoolExecutor
|
13
|
+
from SupportResistance import SupportResistance
|
14
|
+
sr = SupportResistance()
|
11
15
|
|
12
16
|
class Interval:
|
13
17
|
def __init__(self, symbol, intervalId, delta, data, settings):
|
@@ -21,6 +25,7 @@ class Interval:
|
|
21
25
|
self.current_signal="HOLD"
|
22
26
|
self.ema_open_signal="HOLD"
|
23
27
|
self.ema_close_signal="HOLD"
|
28
|
+
self.trendline_signal = "HOLD"
|
24
29
|
self.macd_signal="HOLD"
|
25
30
|
self.bbm_signal="HOLD"
|
26
31
|
self.rsi_signal="HOLD"
|
@@ -39,51 +44,13 @@ class Interval:
|
|
39
44
|
study = self.calculate_study(new_value)
|
40
45
|
self._data = study
|
41
46
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
# 1. Identify highest high and lowest low candle
|
50
|
-
highest_high_index = np.argmax(high_prices)
|
51
|
-
lowest_low_index = np.argmin(low_prices)
|
52
|
-
highest_high_price = high_prices[highest_high_index]
|
53
|
-
lowest_low_price = low_prices[lowest_low_index]
|
54
|
-
|
55
|
-
# 2. Top line: Tilt down from highest high to touch a subsequent high (if any)
|
56
|
-
slope_top = -float('inf') # Initialize with a very steep negative slope
|
57
|
-
intercept_top = highest_high_price - slope_top * highest_high_index
|
58
|
-
|
59
|
-
for i in range(highest_high_index + 1, lookback):
|
60
|
-
current_slope = (high_prices[i] - highest_high_price) / (i - highest_high_index) if (i - highest_high_index) != 0 else 0
|
61
|
-
if current_slope > slope_top:
|
62
|
-
slope_top = current_slope
|
63
|
-
intercept_top = highest_high_price - slope_top * highest_high_index
|
64
|
-
|
65
|
-
top_line = slope_top * candle_indices + intercept_top
|
66
|
-
|
67
|
-
# 3. Bottom line: Tilt up from lowest low to touch a subsequent low (if any)
|
68
|
-
slope_bottom = float('inf') # Initialize with a very steep positive slope
|
69
|
-
intercept_bottom = lowest_low_price - slope_bottom * lowest_low_index
|
70
|
-
|
71
|
-
for i in range(lowest_low_index + 1, lookback):
|
72
|
-
current_slope = (low_prices[i] - lowest_low_price) / (i - lowest_low_index) if (i - lowest_low_index) != 0 else 0
|
73
|
-
if current_slope < slope_bottom:
|
74
|
-
slope_bottom = current_slope
|
75
|
-
intercept_bottom = lowest_low_price - slope_bottom * lowest_low_index
|
76
|
-
|
77
|
-
bottom_line = slope_bottom * candle_indices + intercept_bottom
|
78
|
-
|
79
|
-
results_df = pd.DataFrame({
|
80
|
-
'time': ts,
|
81
|
-
'top_line': top_line,
|
82
|
-
'bottom_line': bottom_line
|
83
|
-
})
|
84
|
-
|
85
|
-
return results_df
|
86
|
-
|
47
|
+
def get_line(self, lookback, candle_indices, index1, price1, index2, price2):
|
48
|
+
if index1 == index2:
|
49
|
+
return np.full(lookback, price1) # Horizontal line
|
50
|
+
slope = (price2 - price1) / (index2 - index1)
|
51
|
+
intercept = price1 - slope * index1
|
52
|
+
return slope * candle_indices + intercept
|
53
|
+
|
87
54
|
def calculate_study(self, new_value):
|
88
55
|
df = pd.DataFrame(new_value)
|
89
56
|
if not df.empty: #and df.shape[0] >= int(self.settings.BARS):
|
@@ -258,14 +225,24 @@ class Interval:
|
|
258
225
|
|
259
226
|
#Trendline
|
260
227
|
if self.settings.TRENDLINE_STUDY:
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
228
|
+
loopback = len(df)
|
229
|
+
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)
|
230
|
+
if df_sr is not None and len(df_sr) >= 1:
|
231
|
+
df['support_line'] = df_sr['support_line']
|
232
|
+
df['resistance_line'] = df_sr['resistance_line']
|
233
|
+
df.fillna({'support_line':0}, inplace=True)
|
234
|
+
df.fillna({'resistance_line':0}, inplace=True)
|
235
|
+
if df is not None and len(df) >= 2:
|
236
|
+
if df['high'].iloc[-3] >= df['support_line'].iloc[-3] and df['close'].iloc[-2] < df['support_line'].iloc[-2] and df['close'].iloc[-1] < df['open'].iloc[-1]:
|
237
|
+
self.trendline_signal = "SELL"
|
238
|
+
elif df['low'].iloc[-3] <= df['resistance_line'].iloc[-3] and df['close'].iloc[-2] > df['resistance_line'].iloc[-2] and df['close'].iloc[-1] > df['open'].iloc[-1]:
|
239
|
+
self.trendline_signal = "BUY"
|
240
|
+
elif df['close'].iloc[-3] > df['support_line'].iloc[-3] and df['close'].iloc[-2] > df['support_line'].iloc[-2] and df['close'].iloc[-1] > df['open'].iloc[-1]:
|
241
|
+
self.trendline_signal = "BUY"
|
242
|
+
elif df['close'].iloc[-3] < df['resistance_line'].iloc[-3] and df['close'].iloc[-2] < df['resistance_line'].iloc[-2] and df['close'].iloc[-1] < df['open'].iloc[-1]:
|
243
|
+
self.trendline_signal = "SELL"
|
244
|
+
else:
|
245
|
+
self.trendline_signal = "HOLD"
|
269
246
|
|
270
247
|
# Calculate the ADX
|
271
248
|
if self.settings.ADX_STUDY:
|
@@ -314,7 +291,8 @@ class Interval:
|
|
314
291
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY") or
|
315
292
|
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY") or
|
316
293
|
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY") or
|
317
|
-
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY")
|
294
|
+
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY") or
|
295
|
+
(self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "BUY")
|
318
296
|
)
|
319
297
|
additional_buy_conditions = (
|
320
298
|
(not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") or
|
@@ -326,7 +304,8 @@ class Interval:
|
|
326
304
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL") or
|
327
305
|
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "SELL") or
|
328
306
|
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "SELL") or
|
329
|
-
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "SELL")
|
307
|
+
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "SELL") or
|
308
|
+
(self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "SELL")
|
330
309
|
)
|
331
310
|
additional_sell_conditions = (
|
332
311
|
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") or
|
@@ -349,7 +328,8 @@ class Interval:
|
|
349
328
|
(not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_open_signal == "BUY") and
|
350
329
|
(not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "BUY") and
|
351
330
|
(not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "BUY") and
|
352
|
-
(not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "BUY")
|
331
|
+
(not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "BUY") and
|
332
|
+
(not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_OPEN or self.trendline_signal == "BUY")
|
353
333
|
)
|
354
334
|
additional_buy_conditions = (
|
355
335
|
(not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
|
@@ -361,7 +341,8 @@ class Interval:
|
|
361
341
|
(not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_open_signal == "SELL") and
|
362
342
|
(not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "SELL") and
|
363
343
|
(not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "SELL") and
|
364
|
-
(not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "SELL")
|
344
|
+
(not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "SELL") and
|
345
|
+
(not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_OPEN or self.trendline_signal == "SELL")
|
365
346
|
)
|
366
347
|
additional_sell_conditions = (
|
367
348
|
(not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
|
@@ -379,7 +360,7 @@ class Interval:
|
|
379
360
|
|
380
361
|
except Exception as e:
|
381
362
|
logger.info(f"Function: calculate_study, {e}, {e.args}, {type(e).__name__}")
|
382
|
-
logger.info(traceback.
|
363
|
+
logger.info(traceback.print_exc())
|
383
364
|
retval = df.to_dict('records')
|
384
365
|
del df
|
385
366
|
gc.collect()
|
@@ -531,7 +512,7 @@ class Ticker:
|
|
531
512
|
|
532
513
|
except Exception as e:
|
533
514
|
logger.info(f"Function: create_bar_with_last_and_ts, {e}, {e.args}, {type(e).__name__}")
|
534
|
-
logger.info(traceback.
|
515
|
+
logger.info(traceback.print_exc())
|
535
516
|
|
536
517
|
|
537
518
|
class Tickers:
|
@@ -639,7 +620,7 @@ class Tickers:
|
|
639
620
|
results = executor.map(self.process_ticker_candle, tuples_list, chunksize=10)
|
640
621
|
for args, result in zip(tuples_list, results):
|
641
622
|
for intervalId, interval in result[3].items():
|
642
|
-
if not interval._data is None:
|
623
|
+
if not interval._data is None and len(interval._data) >= self.settings.BARS:
|
643
624
|
args[0].get_interval_ticks(intervalId)._data = interval._data
|
644
625
|
args[0].get_interval_ticks(intervalId).current_signal = interval.current_signal
|
645
626
|
args[0].get_interval_ticks(intervalId).ema_open_signal = interval.ema_open_signal
|
@@ -647,6 +628,7 @@ class Tickers:
|
|
647
628
|
args[0].get_interval_ticks(intervalId).macd_signal = interval.macd_signal
|
648
629
|
args[0].get_interval_ticks(intervalId).bbm_signal = interval.bbm_signal
|
649
630
|
args[0].get_interval_ticks(intervalId).rsi_signal = interval.rsi_signal
|
631
|
+
args[0].get_interval_ticks(intervalId).trendline_signal, = interval.trendline_signal,
|
650
632
|
args[0].get_interval_ticks(intervalId).candle_trend = interval.candle_trend
|
651
633
|
args[0].get_interval_ticks(intervalId).adx_signal = interval.adx_signal
|
652
634
|
args[0].get_interval_ticks(intervalId).signal_strength = interval.signal_strength
|
@@ -683,6 +665,7 @@ class Tickers:
|
|
683
665
|
f"{period}_macd":intervalObj.macd_signal,
|
684
666
|
f"{period}_bbm":intervalObj.bbm_signal,
|
685
667
|
f"{period}_rsi":intervalObj.rsi_signal,
|
668
|
+
f"{period}_trendline": intervalObj.trendline_signal,
|
686
669
|
f"{period}_adx":intervalObj.adx_signal,
|
687
670
|
f"{period}_candle_trend":intervalObj.candle_trend,
|
688
671
|
'bid' : bid,
|
@@ -704,7 +687,7 @@ class Tickers:
|
|
704
687
|
'lastcolor': "", 'bidcolor': "", 'askcolor': "", 'bid': 0.0, 'ask': 0.0, 'last': 0.0,
|
705
688
|
f"{period}_cb":0, f"{period}_barcolor": "", f"{period}_trend": "",
|
706
689
|
f"{period}_open": 0.0, f"{period}_close": 0.0, f"{period}_high": 0.0, f"{period}_low": 0.0,
|
707
|
-
f"{period}_ema": "", f"{period}_macd": "", f"{period}_bbm": "", f"{period}_rsi": "", f"{period}_candle_trend": "", f"{period}_adx": ""
|
690
|
+
f"{period}_ema": "", f"{period}_macd": "", f"{period}_bbm": "", f"{period}_rsi": "", f"{period}_trendline": "", f"{period}_candle_trend": "", f"{period}_adx": ""
|
708
691
|
}
|
709
692
|
df.fillna(fill_values, inplace=True)
|
710
693
|
df.set_index("symbol", inplace=True, drop=False)
|
@@ -713,19 +696,21 @@ class Tickers:
|
|
713
696
|
trending_conditions = [
|
714
697
|
(
|
715
698
|
(df[f'{period}_trend']=='BUY') &
|
716
|
-
(df[f'{period}_cb']>1)
|
717
|
-
|
699
|
+
(df[f'{period}_cb']>1)
|
700
|
+
#&
|
701
|
+
#(df[f'{period}_barcolor']==self.green)
|
718
702
|
),
|
719
703
|
(
|
720
704
|
(df[f'{period}_trend']=='SELL') &
|
721
|
-
(df[f'{period}_cb']>1)
|
722
|
-
|
705
|
+
(df[f'{period}_cb']>1)
|
706
|
+
#&
|
707
|
+
#(df[f'{period}_barcolor']==self.red)
|
723
708
|
)
|
724
709
|
]
|
725
710
|
self.signaldf_filtered = df[np.any(trending_conditions, axis=0)].copy()
|
726
711
|
except Exception as e:
|
727
712
|
logger.info(f"Function: getCurrentData, {e}, {e.args}, {type(e).__name__}")
|
728
|
-
logger.info(traceback.
|
713
|
+
logger.info(traceback.print_exc())
|
729
714
|
finally:
|
730
715
|
del df, current_data
|
731
716
|
gc.collect()
|
@@ -61,7 +61,8 @@ class Settings(BaseSettings):
|
|
61
61
|
|
62
62
|
TRENDLINE_CHART: bool = Field(default=True)
|
63
63
|
TRENDLINE_STUDY: bool = Field(default=True)
|
64
|
-
|
64
|
+
TRENDLINE_PEAK_DISTANCE: int = Field(default=1, ge=0, le=30)
|
65
|
+
TRENDLINE_LOOKBACK: int = Field(default=20, ge=10, le=200)
|
65
66
|
TRENDLINE_CHECK_ON_OPEN: bool = Field(default=False)
|
66
67
|
TRENDLINE_CHECK_ON_CLOSE: bool = Field(default=False)
|
67
68
|
|
@@ -1,17 +1,18 @@
|
|
1
1
|
bitunix_automated_crypto_trading/AsyncThreadRunner.py,sha256=LJ6ny1KSCVoIbfeNypsY4aHYcbkGME9M3epQ9e8B1O8,3224
|
2
2
|
bitunix_automated_crypto_trading/BitunixApi.py,sha256=W0uem1wIs1uM-jaGtXXzL_JQfnCDb7imyTZ2Tqjk8e8,11230
|
3
|
-
bitunix_automated_crypto_trading/BitunixSignal.py,sha256=
|
3
|
+
bitunix_automated_crypto_trading/BitunixSignal.py,sha256=TOseCeTNpCwVcReXRvHoq1sQybhoiIkFvATbu05wQ8c,67215
|
4
4
|
bitunix_automated_crypto_trading/BitunixWebSocket.py,sha256=mbuvk8UFWKgv4KLV07TgLgxLVTRJnOKuf02mLB-VoCY,11143
|
5
5
|
bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py,sha256=Pqdzhh_nfIxFEZH9L_R5QXB8moDPbgeTGT_hmBkHWMg,2899
|
6
6
|
bitunix_automated_crypto_trading/NotificationManager.py,sha256=pqDquEe-oujD2v8B543524vo62aRMjfB4YW-3DMhVGQ,795
|
7
|
+
bitunix_automated_crypto_trading/SupportResistance.py,sha256=Dx4uBC4On-GO1t2EFk02jzSmZzaMD48mUJI9rG2rgMo,4946
|
7
8
|
bitunix_automated_crypto_trading/ThreadManager.py,sha256=WmYRQu8aNrgp5HwSqpBqX1WESNSEpbD2CznPFpd9HuI,2330
|
8
|
-
bitunix_automated_crypto_trading/TickerManager.py,sha256=
|
9
|
+
bitunix_automated_crypto_trading/TickerManager.py,sha256=JTlXUL9GadRjloK6Gw2HhacjvIe31CiiIFl1KGkWztA,37444
|
9
10
|
bitunix_automated_crypto_trading/__init__.py,sha256=1hzk6nX8NnUCr1tsq8oFq1qGCNhNwnwldWE75641Eew,78
|
10
11
|
bitunix_automated_crypto_trading/bitunix.py,sha256=Zyrf8P-xi7DJogX_bcc6uDW66J7FAxdS-0jbuHRPFfM,25845
|
11
|
-
bitunix_automated_crypto_trading/config.py,sha256=
|
12
|
+
bitunix_automated_crypto_trading/config.py,sha256=zeNmV1KK8Yv5cHzBxaly0lmcjwSZEBAMUm_OvFSBCK4,4922
|
12
13
|
bitunix_automated_crypto_trading/logger.py,sha256=tAaTQcv5r--zupk_Fhfe1USfBAzSiXzVa4QRnaOFIFY,2933
|
13
|
-
bitunix_automated_crypto_trading-3.1.
|
14
|
-
bitunix_automated_crypto_trading-3.1.
|
15
|
-
bitunix_automated_crypto_trading-3.1.
|
16
|
-
bitunix_automated_crypto_trading-3.1.
|
17
|
-
bitunix_automated_crypto_trading-3.1.
|
14
|
+
bitunix_automated_crypto_trading-3.1.5.dist-info/METADATA,sha256=YJdYd5cjlCI7XA-Ef9WMVDCnblI22sSxyX--HmMcK-Q,996
|
15
|
+
bitunix_automated_crypto_trading-3.1.5.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
16
|
+
bitunix_automated_crypto_trading-3.1.5.dist-info/entry_points.txt,sha256=UXREYHuSl2XYd_tOtLIq0zg3d1kX3lixX5SpN8yGBw4,82
|
17
|
+
bitunix_automated_crypto_trading-3.1.5.dist-info/top_level.txt,sha256=uyFzHUCOsp8elnG2Ovor6xXcf7dxRxY-C-Txiwix64Q,33
|
18
|
+
bitunix_automated_crypto_trading-3.1.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|