bitunix-automated-crypto-trading 3.1.3__py3-none-any.whl → 3.1.6__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 +89 -48
- bitunix_automated_crypto_trading/SupportResistance.py +89 -0
- bitunix_automated_crypto_trading/TickerManager.py +134 -79
- bitunix_automated_crypto_trading/config.py +10 -4
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.6.dist-info}/METADATA +1 -1
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.6.dist-info}/RECORD +9 -8
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.6.dist-info}/WHEEL +1 -1
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.6.dist-info}/entry_points.txt +0 -0
- {bitunix_automated_crypto_trading-3.1.3.dist-info → bitunix_automated_crypto_trading-3.1.6.dist-info}/top_level.txt +0 -0
@@ -99,19 +99,23 @@ class BitunixSignal:
|
|
99
99
|
self.tickerObjects.update_settings(settings)
|
100
100
|
|
101
101
|
async def load_tickers(self):
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
102
|
+
if self.settings.TICKERS=="":
|
103
|
+
symbols = await self.bitunixApi.GetTickerList(float(self.settings.THRESHOLD), float(self.settings.MIN_VOLUME))
|
104
|
+
self.pendingPositions= await self.bitunixApi.GetPendingPositionData()
|
105
|
+
self.pendingOrders= await self.bitunixApi.GetPendingOrderData()
|
106
|
+
olist=[]
|
107
|
+
plist=[]
|
108
|
+
if self.pendingPositions:
|
109
|
+
plist = [entry['symbol'] for entry in self.pendingPositions]
|
110
|
+
if self.pendingOrders['orderList']:
|
111
|
+
olist = [entry['symbol'] for entry in self.pendingOrders['orderList']]
|
112
|
+
newlist=olist+plist+list(set(symbols))
|
113
|
+
self.tickerList=newlist[:300]
|
114
|
+
self.tickerList.remove("STMXUSDT")
|
115
|
+
#self.tickerList=['POLUSDT']
|
116
|
+
else:
|
117
|
+
self.tickerList = self.settings.TICKERS.split(",")
|
118
|
+
self.tickerList = [sym.strip().upper() for sym in self.tickerList if sym.strip()]
|
115
119
|
|
116
120
|
[await self.add_ticker_to_tickerObjects(sym) for sym in self.tickerList]
|
117
121
|
self.notifications.add_notification(f"{len(self.tickerList)} ticker list loaded")
|
@@ -271,12 +275,7 @@ class BitunixSignal:
|
|
271
275
|
# Function to add data to the last price deque
|
272
276
|
async def StoreTickerData(self, message):
|
273
277
|
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}")
|
278
|
+
await self.ticker_que.put(message)
|
280
279
|
|
281
280
|
# Function to process the last price deque
|
282
281
|
async def ProcessTickerData(self):
|
@@ -285,11 +284,14 @@ class BitunixSignal:
|
|
285
284
|
latest_data = {}
|
286
285
|
reversed_items = await self.drain_queue(self.ticker_que)
|
287
286
|
while reversed_items:
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
287
|
+
message = reversed_items.popleft()
|
288
|
+
data = json.loads(message)
|
289
|
+
if data.get('symbol') and data.get('ch') == 'ticker':
|
290
|
+
symbol = data["symbol"]
|
291
|
+
ts = data["ts"]
|
292
|
+
if symbol not in latest_data or ts > latest_data[symbol]['ts']:
|
293
|
+
latest_data[symbol] = {'ts': ts, 'last': float(data['data']['la'])}
|
294
|
+
await asyncio.sleep(0.01)
|
293
295
|
# Convert to DataFrame
|
294
296
|
self.tickerdf = pd.DataFrame.from_dict(latest_data, orient="index")
|
295
297
|
if not self.tickerdf.empty:
|
@@ -300,7 +302,7 @@ class BitunixSignal:
|
|
300
302
|
except Exception as e:
|
301
303
|
logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
|
302
304
|
logger.info(traceback.print_exc())
|
303
|
-
await asyncio.sleep(0.
|
305
|
+
await asyncio.sleep(0.01)
|
304
306
|
logger.info(f"ProcessTickerData: exitied out of the loop, exiting app")
|
305
307
|
os._exit(1) # Exit the program
|
306
308
|
|
@@ -308,13 +310,8 @@ class BitunixSignal:
|
|
308
310
|
#websocket data to update bid and ask
|
309
311
|
async def StoreDepthData(self, message):
|
310
312
|
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
|
-
|
313
|
+
await self.depth_que.put(message)
|
314
|
+
|
318
315
|
# Function to process the bid, ask
|
319
316
|
async def ProcessDepthData(self):
|
320
317
|
while True:
|
@@ -322,11 +319,14 @@ class BitunixSignal:
|
|
322
319
|
latest_data = {}
|
323
320
|
reversed_items = await self.drain_queue(self.depth_que)
|
324
321
|
while reversed_items:
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
322
|
+
message = reversed_items.popleft()
|
323
|
+
data = json.loads(message)
|
324
|
+
if data.get('symbol') and data.get('ch') == 'depth_book1':
|
325
|
+
symbol = data["symbol"]
|
326
|
+
ts = data["ts"]
|
327
|
+
if symbol not in latest_data or ts > latest_data[symbol]['ts']:
|
328
|
+
latest_data[symbol] = {'ts': ts, 'bid': float(data['data']['b'][0][0]), 'ask': float(data['data']['a'][0][0])}
|
329
|
+
await asyncio.sleep(0.01)
|
330
330
|
# Convert to DataFrame
|
331
331
|
self.depthdf = pd.DataFrame.from_dict(latest_data, orient="index")
|
332
332
|
if not self.depthdf.empty:
|
@@ -335,7 +335,7 @@ class BitunixSignal:
|
|
335
335
|
except Exception as e:
|
336
336
|
logger.info(f"Function: ProcessTickerData, {e}, {e.args}, {type(e).__name__}")
|
337
337
|
logger.info(traceback.print_exc())
|
338
|
-
await asyncio.sleep(0.
|
338
|
+
await asyncio.sleep(0.01)
|
339
339
|
logger.info(f"ProcessDepthData: exitied out of the loop, exiting app")
|
340
340
|
os._exit(1) # Exit the program
|
341
341
|
|
@@ -563,12 +563,17 @@ class BitunixSignal:
|
|
563
563
|
self.signaldf_filtered = self.tickerObjects.signaldf_filtered
|
564
564
|
|
565
565
|
if not self.positiondf.empty and not self.signaldf_full.empty:
|
566
|
-
columns=['symbol', f"{period}_trend", f"{period}_cb", f"{period}_barcolor",
|
566
|
+
columns=['symbol', f"{period}_trend", f"{period}_cb", f"{period}_barcolor",
|
567
|
+
f"{period}_bos",
|
568
|
+
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
569
|
columns2=["qty", "side", "unrealizedPNL", "realizedPNL", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
|
568
570
|
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",
|
571
|
+
columnOrder= ['symbol', "side", "unrealizedPNL", "realizedPNL", f"{period}_trend", f"{period}_cb", f"{period}_barcolor",
|
572
|
+
f"{period}_bos",
|
573
|
+
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
574
|
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}
|
575
|
+
f"{period}_bos",
|
576
|
+
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
577
|
f"{period}_trend",f"{period}_cb", f"{period}_barcolor"]], left_on="symbol", right_index=True, how="left")[columnOrder]
|
573
578
|
self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf2)
|
574
579
|
else:
|
@@ -582,7 +587,8 @@ class BitunixSignal:
|
|
582
587
|
# Assign to self.signaldf for HTML rendering
|
583
588
|
self.signaldf = self.signaldf_filtered[[
|
584
589
|
"symbol", f"{period}_trend",f"{period}_cb", f"{period}_barcolor",
|
585
|
-
f"{period}
|
590
|
+
f"{period}_bos",
|
591
|
+
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
592
|
'lastcolor', 'bidcolor', 'askcolor', 'bid', 'last', 'ask',
|
587
593
|
f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
|
588
594
|
]].sort_values(by=[f'{period}_cb'], ascending=[False])
|
@@ -644,7 +650,7 @@ class BitunixSignal:
|
|
644
650
|
#open position upto a max of max_auto_trades from the signal list
|
645
651
|
df=self.signaldf.copy(deep=False)
|
646
652
|
for index, row in df.iterrows():
|
647
|
-
side = "BUY" if row[f'{period}_barcolor'] == self.green else "SELL" if row[f'{period}_barcolor'] == self.red else ""
|
653
|
+
side = "BUY" if row[f'{period}_barcolor'] == self.green and row[f'{period}_trend'] == "BUY" else "SELL" if row[f'{period}_barcolor'] == self.red and row[f'{period}_trend'] == "SELL" else ""
|
648
654
|
if side != "":
|
649
655
|
select = True
|
650
656
|
self.pendingPositions = await self.bitunixApi.GetPendingPositionData({'symbol': row.symbol})
|
@@ -693,7 +699,7 @@ class BitunixSignal:
|
|
693
699
|
total_pnl = unrealized_pnl + realized_pnl
|
694
700
|
side=row['side']
|
695
701
|
|
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']
|
702
|
+
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
703
|
required_cols = set(requiredCols)
|
698
704
|
|
699
705
|
# Close position that fall the below criteria
|
@@ -879,6 +885,41 @@ class BitunixSignal:
|
|
879
885
|
)
|
880
886
|
continue
|
881
887
|
|
888
|
+
# TrendLine
|
889
|
+
if self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_CLOSE:
|
890
|
+
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_trendline'] == "SELL" and total_pnl>0:
|
891
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
892
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
893
|
+
|
894
|
+
self.notifications.add_notification(
|
895
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to {period} trendline for {row.symbol} with {row.qty} qty @ {price})'
|
896
|
+
)
|
897
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
898
|
+
positionId=row.positionId,
|
899
|
+
ticker=row.symbol,
|
900
|
+
qty=row.qty,
|
901
|
+
price=price,
|
902
|
+
side=row.side,
|
903
|
+
tradeSide="CLOSE"
|
904
|
+
)
|
905
|
+
continue
|
906
|
+
|
907
|
+
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_trendline'] == "BUY" and total_pnl>0:
|
908
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
909
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
910
|
+
self.notifications.add_notification(
|
911
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to {period} trendline for {row.symbol} with {row.qty} qty @ {price})'
|
912
|
+
)
|
913
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
914
|
+
positionId=row.positionId,
|
915
|
+
ticker=row.symbol,
|
916
|
+
qty=row.qty,
|
917
|
+
price=price,
|
918
|
+
side=row.side,
|
919
|
+
tradeSide="CLOSE"
|
920
|
+
)
|
921
|
+
continue
|
922
|
+
|
882
923
|
# Close on weak trend after open
|
883
924
|
if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_CLOSE:
|
884
925
|
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_adx'] == "WEAK":
|
@@ -914,8 +955,8 @@ class BitunixSignal:
|
|
914
955
|
continue
|
915
956
|
|
916
957
|
# candle reversed
|
917
|
-
if self.settings.CANDLE_TREND_STUDY and self.settings.
|
918
|
-
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_barcolor'] == self.red and self.signaldf_full.at[row.symbol, f'{period}_candle_trend'] == "
|
958
|
+
if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_REVERSAL_CHECK_ON_CLOSE:
|
959
|
+
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_barcolor'] == self.red and self.signaldf_full.at[row.symbol, f'{period}_cb'] > 1 and self.signaldf_full.at[row.symbol, f'{period}_candle_trend'] == "BULLISH":
|
919
960
|
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
920
961
|
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
921
962
|
|
@@ -932,7 +973,7 @@ class BitunixSignal:
|
|
932
973
|
)
|
933
974
|
continue
|
934
975
|
|
935
|
-
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_barcolor'] == self.green
|
976
|
+
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_barcolor'] == self.green and self.signaldf_full.at[row.symbol, f'{period}_cb'] > 1 and self.signaldf_full.at[row.symbol, f'{period}_candle_trend'] == "BULLISH":
|
936
977
|
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
937
978
|
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
938
979
|
|
@@ -951,7 +992,7 @@ class BitunixSignal:
|
|
951
992
|
|
952
993
|
await asyncio.sleep(0)
|
953
994
|
|
954
|
-
|
995
|
+
self.lastAutoTradeTime = time.time()
|
955
996
|
except Exception as e:
|
956
997
|
stack = traceback.extract_stack()
|
957
998
|
function_name = stack[-1].name
|
@@ -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):
|
@@ -96,6 +63,20 @@ class Interval:
|
|
96
63
|
# Get the last color and its consecutive count
|
97
64
|
self.signal_strength = df['Consecutive'].iloc[-1]
|
98
65
|
|
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
|
+
|
99
80
|
# Calculate the Moving Averages
|
100
81
|
if self.settings.EMA_STUDY:
|
101
82
|
df['ma_fast'] = talib.EMA(df['close'], timeperiod=self.settings.MA_FAST)
|
@@ -149,6 +130,14 @@ class Interval:
|
|
149
130
|
else:
|
150
131
|
self.ema_open_signal = "HOLD"
|
151
132
|
self.ema_close_signal = "HOLD"
|
133
|
+
|
134
|
+
if self.settings.EMA_CLOSE_ON_FAST_MEDIUM:
|
135
|
+
if df['close'].iloc[-1] > df['ma_fast'].iloc[-1] and df['ma_fast'].iloc[-1] > df['ma_medium'].iloc[-1]:
|
136
|
+
self.ema_close_signal = "BUY"
|
137
|
+
elif df['close'].iloc[-1] < df['ma_fast'].iloc[-1] and df['ma_fast'].iloc[-1] < df['ma_medium'].iloc[-1]:
|
138
|
+
self.ema_close_signal = "SELL"
|
139
|
+
else:
|
140
|
+
self.ema_close_signal = "HOLD"
|
152
141
|
else:
|
153
142
|
# Drop EMA columns if not used
|
154
143
|
df.drop(['ma_fast', 'ma_medium', 'ma_slow', 'ma_slope', 'ma_angle'], axis=1, inplace=True, errors='ignore')
|
@@ -258,14 +247,28 @@ class Interval:
|
|
258
247
|
|
259
248
|
#Trendline
|
260
249
|
if self.settings.TRENDLINE_STUDY:
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
250
|
+
loopback = len(df)
|
251
|
+
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
|
+
if df_sr is not None and len(df_sr) >= 1:
|
253
|
+
df['support_line'] = df_sr['support_line']
|
254
|
+
df['resistance_line'] = df_sr['resistance_line']
|
255
|
+
df.fillna({'support_line':0}, inplace=True)
|
256
|
+
df.fillna({'resistance_line':0}, inplace=True)
|
257
|
+
if df is not None and len(df) >= 2:
|
258
|
+
if self.settings.TRENDLINE_BREAKOUT:
|
259
|
+
if df['close'].iloc[-2] > df['support_line'].iloc[-2] and df['open'].iloc[-1] > df['support_line'].iloc[-1] and df['close'].iloc[-1] > df['open'].iloc[-1]:
|
260
|
+
self.trendline_signal = "BUY"
|
261
|
+
elif df['close'].iloc[-2] < df['resistance_line'].iloc[-2] and df['open'].iloc[-1] < df['resistance_line'].iloc[-1] and df['close'].iloc[-1] < df['open'].iloc[-1]:
|
262
|
+
self.trendline_signal = "SELL"
|
263
|
+
else:
|
264
|
+
self.trendline_signal = "HOLD"
|
265
|
+
else:
|
266
|
+
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]:
|
267
|
+
self.trendline_signal = "SELL"
|
268
|
+
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]:
|
269
|
+
self.trendline_signal = "BUY"
|
270
|
+
else:
|
271
|
+
self.trendline_signal = "HOLD"
|
269
272
|
|
270
273
|
# Calculate the ADX
|
271
274
|
if self.settings.ADX_STUDY:
|
@@ -311,10 +314,12 @@ class Interval:
|
|
311
314
|
|
312
315
|
# Check for BUY signal
|
313
316
|
buy_conditions = (
|
317
|
+
(self.settings.BOS_STUDY and self.bos_signal == "BUY") or
|
314
318
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY") or
|
315
319
|
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY") or
|
316
320
|
(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")
|
321
|
+
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY") or
|
322
|
+
(self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "BUY")
|
318
323
|
)
|
319
324
|
additional_buy_conditions = (
|
320
325
|
(not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") or
|
@@ -323,10 +328,12 @@ class Interval:
|
|
323
328
|
|
324
329
|
# Check for SELL signal
|
325
330
|
sell_conditions = (
|
331
|
+
(self.settings.BOS_STUDY and self.bos_signal == "SELL") or
|
326
332
|
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL") or
|
327
333
|
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "SELL") or
|
328
334
|
(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")
|
335
|
+
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "SELL") or
|
336
|
+
(self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "SELL")
|
330
337
|
)
|
331
338
|
additional_sell_conditions = (
|
332
339
|
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") or
|
@@ -341,16 +348,34 @@ class Interval:
|
|
341
348
|
else:
|
342
349
|
self.current_signal = "HOLD"
|
343
350
|
else:
|
344
|
-
# If EMA is enabled and crossing and MACD is enabled and crossing and BBM is enabled and crossing and RSI is enbabled and crossing
|
345
|
-
# and
|
346
|
-
# ADX is enabled and strong and candle trend is enabled and bullish
|
347
|
-
# then BUY or SELL
|
348
351
|
buy_conditions = (
|
349
|
-
(
|
350
|
-
|
351
|
-
|
352
|
-
(
|
352
|
+
(self.settings.BOS_STUDY and self.bos_signal == "BUY")
|
353
|
+
or not self.settings.BOS_STUDY
|
354
|
+
) and (
|
355
|
+
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY")
|
356
|
+
or not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN
|
357
|
+
) and (
|
358
|
+
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY")
|
359
|
+
or not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN
|
360
|
+
) and (
|
361
|
+
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY")
|
362
|
+
or not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN
|
363
|
+
) and (
|
364
|
+
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY")
|
365
|
+
or not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN
|
366
|
+
) and (
|
367
|
+
(self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "BUY")
|
368
|
+
or not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_OPEN
|
353
369
|
)
|
370
|
+
if not any([
|
371
|
+
self.settings.EMA_STUDY, self.settings.MACD_STUDY, self.settings.BBM_STUDY,
|
372
|
+
self.settings.RSI_STUDY, self.settings.TRENDLINE_STUDY
|
373
|
+
]) and not any([
|
374
|
+
self.settings.EMA_CHECK_ON_OPEN, self.settings.MACD_CHECK_ON_OPEN, self.settings.BBM_CHECK_ON_OPEN,
|
375
|
+
self.settings.RSI_CHECK_ON_OPEN, self.settings.TRENDLINE_CHECK_ON_OPEN
|
376
|
+
]):
|
377
|
+
buy_conditions = False
|
378
|
+
|
354
379
|
additional_buy_conditions = (
|
355
380
|
(not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
|
356
381
|
(not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BULLISH")
|
@@ -358,14 +383,36 @@ class Interval:
|
|
358
383
|
|
359
384
|
# Check for SELL signal
|
360
385
|
sell_conditions = (
|
361
|
-
(
|
362
|
-
|
363
|
-
|
364
|
-
(
|
386
|
+
(self.settings.BOS_STUDY and self.bos_signal == "SELL")
|
387
|
+
or not self.settings.BOS_STUDY
|
388
|
+
) and (
|
389
|
+
(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "SELL")
|
390
|
+
or not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN
|
391
|
+
) and (
|
392
|
+
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "SELL")
|
393
|
+
or not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN
|
394
|
+
) and (
|
395
|
+
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "SELL")
|
396
|
+
or not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN
|
397
|
+
) and (
|
398
|
+
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "SELL")
|
399
|
+
or not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN
|
400
|
+
) and (
|
401
|
+
(self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "SELL")
|
402
|
+
or not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_OPEN
|
365
403
|
)
|
404
|
+
if not any([
|
405
|
+
self.settings.EMA_STUDY, self.settings.MACD_STUDY, self.settings.BBM_STUDY,
|
406
|
+
self.settings.RSI_STUDY, self.settings.TRENDLINE_STUDY
|
407
|
+
]) and not any([
|
408
|
+
self.settings.EMA_CHECK_ON_OPEN, self.settings.MACD_CHECK_ON_OPEN, self.settings.BBM_CHECK_ON_OPEN,
|
409
|
+
self.settings.RSI_CHECK_ON_OPEN, self.settings.TRENDLINE_CHECK_ON_OPEN
|
410
|
+
]):
|
411
|
+
sell_conditions = False
|
412
|
+
|
366
413
|
additional_sell_conditions = (
|
367
|
-
(
|
368
|
-
(
|
414
|
+
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") or
|
415
|
+
(self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BEARISH")
|
369
416
|
)
|
370
417
|
|
371
418
|
# Determine current signal
|
@@ -379,7 +426,7 @@ class Interval:
|
|
379
426
|
|
380
427
|
except Exception as e:
|
381
428
|
logger.info(f"Function: calculate_study, {e}, {e.args}, {type(e).__name__}")
|
382
|
-
logger.info(traceback.
|
429
|
+
logger.info(traceback.print_exc())
|
383
430
|
retval = df.to_dict('records')
|
384
431
|
del df
|
385
432
|
gc.collect()
|
@@ -531,7 +578,7 @@ class Ticker:
|
|
531
578
|
|
532
579
|
except Exception as e:
|
533
580
|
logger.info(f"Function: create_bar_with_last_and_ts, {e}, {e.args}, {type(e).__name__}")
|
534
|
-
logger.info(traceback.
|
581
|
+
logger.info(traceback.print_exc())
|
535
582
|
|
536
583
|
|
537
584
|
class Tickers:
|
@@ -639,14 +686,16 @@ class Tickers:
|
|
639
686
|
results = executor.map(self.process_ticker_candle, tuples_list, chunksize=10)
|
640
687
|
for args, result in zip(tuples_list, results):
|
641
688
|
for intervalId, interval in result[3].items():
|
642
|
-
if not interval._data is None:
|
689
|
+
if not interval._data is None and len(interval._data) >= self.settings.BARS:
|
643
690
|
args[0].get_interval_ticks(intervalId)._data = interval._data
|
644
691
|
args[0].get_interval_ticks(intervalId).current_signal = interval.current_signal
|
692
|
+
args[0].get_interval_ticks(intervalId).bos_signal = interval.bos_signal
|
645
693
|
args[0].get_interval_ticks(intervalId).ema_open_signal = interval.ema_open_signal
|
646
694
|
args[0].get_interval_ticks(intervalId).ema_close_signal = interval.ema_close_signal
|
647
695
|
args[0].get_interval_ticks(intervalId).macd_signal = interval.macd_signal
|
648
696
|
args[0].get_interval_ticks(intervalId).bbm_signal = interval.bbm_signal
|
649
697
|
args[0].get_interval_ticks(intervalId).rsi_signal = interval.rsi_signal
|
698
|
+
args[0].get_interval_ticks(intervalId).trendline_signal, = interval.trendline_signal,
|
650
699
|
args[0].get_interval_ticks(intervalId).candle_trend = interval.candle_trend
|
651
700
|
args[0].get_interval_ticks(intervalId).adx_signal = interval.adx_signal
|
652
701
|
args[0].get_interval_ticks(intervalId).signal_strength = interval.signal_strength
|
@@ -678,11 +727,14 @@ class Tickers:
|
|
678
727
|
f"{period}_trend": intervalObj.current_signal,
|
679
728
|
f"{period}_cb": intervalObj.signal_strength,
|
680
729
|
f"{period}_barcolor": lastcandle['barcolor'],
|
730
|
+
f"{period}_bos": intervalObj.bos_signal,
|
731
|
+
f"{period}_ema_open": intervalObj.ema_open_signal,
|
681
732
|
f"{period}_ema_open": intervalObj.ema_open_signal,
|
682
733
|
f"{period}_ema_close": intervalObj.ema_close_signal,
|
683
734
|
f"{period}_macd":intervalObj.macd_signal,
|
684
735
|
f"{period}_bbm":intervalObj.bbm_signal,
|
685
736
|
f"{period}_rsi":intervalObj.rsi_signal,
|
737
|
+
f"{period}_trendline": intervalObj.trendline_signal,
|
686
738
|
f"{period}_adx":intervalObj.adx_signal,
|
687
739
|
f"{period}_candle_trend":intervalObj.candle_trend,
|
688
740
|
'bid' : bid,
|
@@ -704,7 +756,8 @@ class Tickers:
|
|
704
756
|
'lastcolor': "", 'bidcolor': "", 'askcolor': "", 'bid': 0.0, 'ask': 0.0, 'last': 0.0,
|
705
757
|
f"{period}_cb":0, f"{period}_barcolor": "", f"{period}_trend": "",
|
706
758
|
f"{period}_open": 0.0, f"{period}_close": 0.0, f"{period}_high": 0.0, f"{period}_low": 0.0,
|
707
|
-
f"{period}
|
759
|
+
f"{period}_bos": "",
|
760
|
+
f"{period}_ema": "", f"{period}_macd": "", f"{period}_bbm": "", f"{period}_rsi": "", f"{period}_trendline": "", f"{period}_candle_trend": "", f"{period}_adx": ""
|
708
761
|
}
|
709
762
|
df.fillna(fill_values, inplace=True)
|
710
763
|
df.set_index("symbol", inplace=True, drop=False)
|
@@ -713,19 +766,21 @@ class Tickers:
|
|
713
766
|
trending_conditions = [
|
714
767
|
(
|
715
768
|
(df[f'{period}_trend']=='BUY') &
|
716
|
-
(df[f'{period}_cb']>1)
|
717
|
-
|
769
|
+
(df[f'{period}_cb']>1)
|
770
|
+
#&
|
771
|
+
#(df[f'{period}_barcolor']==self.green)
|
718
772
|
),
|
719
773
|
(
|
720
774
|
(df[f'{period}_trend']=='SELL') &
|
721
|
-
(df[f'{period}_cb']>1)
|
722
|
-
|
775
|
+
(df[f'{period}_cb']>1)
|
776
|
+
#&
|
777
|
+
#(df[f'{period}_barcolor']==self.red)
|
723
778
|
)
|
724
779
|
]
|
725
780
|
self.signaldf_filtered = df[np.any(trending_conditions, axis=0)].copy()
|
726
781
|
except Exception as e:
|
727
782
|
logger.info(f"Function: getCurrentData, {e}, {e.args}, {type(e).__name__}")
|
728
|
-
logger.info(traceback.
|
783
|
+
logger.info(traceback.print_exc())
|
729
784
|
finally:
|
730
785
|
del df, current_data
|
731
786
|
gc.collect()
|
@@ -6,11 +6,14 @@ import sqlite3
|
|
6
6
|
class Settings(BaseSettings):
|
7
7
|
# Start autotrading on start
|
8
8
|
AUTOTRADE: bool = Field(default=False)
|
9
|
+
|
10
|
+
#Ticker list
|
11
|
+
TICKERS:str = Field(default="")
|
12
|
+
THRESHOLD: float = Field(default=5.0, ge=0.0)
|
13
|
+
MIN_VOLUME: int = Field(default=10_000_000, ge=0)
|
9
14
|
|
10
15
|
# Trading Parameters
|
11
16
|
LEVERAGE: int = Field(default=20, ge=1, le=100)
|
12
|
-
THRESHOLD: float = Field(default=5.0, ge=0.0)
|
13
|
-
MIN_VOLUME: int = Field(default=10_000_000, ge=0)
|
14
17
|
ORDER_AMOUNT_PERCENTAGE: float = Field(default=0.01, ge=0.0, le=100)
|
15
18
|
MAX_AUTO_TRADES: int = Field(default=10, ge=0)
|
16
19
|
PROFIT_AMOUNT: float = Field(default=0.25, ge=0.0)
|
@@ -34,6 +37,7 @@ class Settings(BaseSettings):
|
|
34
37
|
# Technical Indicators
|
35
38
|
OPEN_ON_ANY_SIGNAL: bool = Field(default=True)
|
36
39
|
|
40
|
+
BOS_STUDY: bool = Field(default=True)
|
37
41
|
EMA_CHART: bool = Field(default=True)
|
38
42
|
EMA_STUDY: bool = Field(default=True)
|
39
43
|
EMA_CROSSING: bool = Field(default=False)
|
@@ -59,9 +63,10 @@ class Settings(BaseSettings):
|
|
59
63
|
RSI_CHECK_ON_OPEN: bool = Field(default=False)
|
60
64
|
RSI_CHECK_ON_CLOSE: bool = Field(default=False)
|
61
65
|
|
66
|
+
TRENDLINE_PEAK_DISTANCE: int = Field(default=1, ge=0, le=30)
|
62
67
|
TRENDLINE_CHART: bool = Field(default=True)
|
63
68
|
TRENDLINE_STUDY: bool = Field(default=True)
|
64
|
-
|
69
|
+
TRENDLINE_BREAKOUT: bool = Field(default=False)
|
65
70
|
TRENDLINE_CHECK_ON_OPEN: bool = Field(default=False)
|
66
71
|
TRENDLINE_CHECK_ON_CLOSE: bool = Field(default=False)
|
67
72
|
|
@@ -71,7 +76,7 @@ class Settings(BaseSettings):
|
|
71
76
|
|
72
77
|
CANDLE_TREND_STUDY: bool = Field(default=True)
|
73
78
|
CANDLE_TREND_CHECK_ON_OPEN: bool = Field(default=False)
|
74
|
-
|
79
|
+
CANDLE_TREND_REVERSAL_CHECK_ON_CLOSE: bool = Field(default=False)
|
75
80
|
|
76
81
|
# Time Intervals
|
77
82
|
SCREEN_REFRESH_INTERVAL: int = Field(default=1, ge=1)
|
@@ -100,6 +105,7 @@ class Settings(BaseSettings):
|
|
100
105
|
# Specify the file name for loading environment variables
|
101
106
|
env_file = os.path.dirname(os.path.abspath(__file__))+"/config.txt"
|
102
107
|
|
108
|
+
|
103
109
|
@classmethod
|
104
110
|
def load_from_db(cls, db_path: str, table_name: str = "settings"):
|
105
111
|
# Connect to SQLite database
|
@@ -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=JIJumU3nYcO_NRS5xMqrPeisMIiy_TM0EOEA5nr44cQ,67993
|
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=n2GyvfH2YMuZ31Q_IcLd4U3ZOEIOLhLwMIR8iI5Gp5A,41460
|
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=aBBHdkMnw4ODzcwQl1CCx_r1O08hShDRGlSA6JXt5lg,5024
|
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.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
|