bitunix-automated-crypto-trading 3.3.2__tar.gz → 3.3.4__tar.gz
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-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/PKG-INFO +1 -1
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/README.md +8 -3
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/BitunixApi.py +92 -43
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/BitunixSignal.py +77 -2
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/TickerManager.py +40 -72
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/config.py +5 -4
- bitunix_automated_crypto_trading-3.3.4/bitunix_automated_crypto_trading/version.py +1 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading.egg-info/PKG-INFO +1 -1
- bitunix_automated_crypto_trading-3.3.2/bitunix_automated_crypto_trading/version.py +0 -1
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/AsyncThreadRunner.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/BitunixWebSocket.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/NotificationManager.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/SupportResistance.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/ThreadManager.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/__init__.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/bitunix.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading/logger.py +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading.egg-info/SOURCES.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading.egg-info/dependency_links.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading.egg-info/entry_points.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading.egg-info/requires.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/bitunix_automated_crypto_trading.egg-info/top_level.txt +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/setup.cfg +0 -0
- {bitunix_automated_crypto_trading-3.3.2 → bitunix_automated_crypto_trading-3.3.4}/setup.py +0 -0
@@ -57,12 +57,16 @@ The platform can be configured through the `config.py` file or `config.txt`. Key
|
|
57
57
|
- `MIN_VOLUME`: Ticker selection based on Minimum trading volume
|
58
58
|
- `ORDER_AMOUNT_PERCENTAGE`: Order size as percentage of portfolio
|
59
59
|
- `MAX_AUTO_TRADES`: Maximum number of automated trades
|
60
|
+
- `PROFIT_AMOUNT`: Target profit amount
|
61
|
+
- `LOSS_AMOUNT`: Maximum loss amount
|
60
62
|
- `PROFIT_PERCENTAGE`: Target profit ROI percentage
|
61
63
|
- `LOSS_PERCENTAGE`: Maximum loss ROI percentage
|
64
|
+
- `BOT_TAKE_PROFIT_STOP_LOSS`: True or False
|
65
|
+
if false then take profit and stop loss will be placed by the bot when TP or SL is reached, if true then take profit and stop loss will be placed when the trade is opened
|
66
|
+
- `BOT_TRAIL_STOP_LOSS`: True or False
|
67
|
+
if set to True then bot will move the stop loss to breakeven when the trade is reaches 50% of the target profit and then move the stop loss to the 50% of the target profit when the trade reaches 90% of the target profit.
|
62
68
|
- `PROFIT_LOSS_PRICE_TYPE`: "MARK_PRICE" or "LAST_PRICE"
|
63
69
|
- `PROFIT_LOSS_ORDER_TYPE`: "MARKET" or "LIMIT"
|
64
|
-
- `PROFIT_AMOUNT`: Target profit amount
|
65
|
-
- `LOSS_AMOUNT`: Maximum loss amount
|
66
70
|
- `OPTION_MOVING_AVERAGE`: Moving average period (1h, 1d, 15m, 5m, 1m)
|
67
71
|
- `DELAY_IN_MINUTES_FOR_SAME_TICKER_TRADES`: Delay in minutes to prohibit trading the same ticker again
|
68
72
|
- `BARS`: Number of bars to use for study and charting
|
@@ -177,6 +181,7 @@ The platform can be configured through the `config.py` file or `config.txt`. Key
|
|
177
181
|
- You can control the study like Moving Average, MACD, Bollinger Band, RSI or close proximity to high or low of the candle using the env file
|
178
182
|
- You can control the trading strategy using the CalculateStudy function in TickerManager.py and AutoTradeProcess function in BitunixSignal.py
|
179
183
|
- Changes are activated by unchecking and checking the AutoTrade checkbox
|
184
|
+
- Trailing TP and SL in the direction of profit, if BOT_TRAIL_STOP_LOSS to True and BOT_TAKE_PROFIT_STOP_LOSS to False in the config file
|
180
185
|
|
181
186
|
## Auto Trading
|
182
187
|
|
@@ -228,7 +233,7 @@ The platform can be configured through the `config.py` file or `config.txt`. Key
|
|
228
233
|
- check python version should be 3.13 or above
|
229
234
|
- python3 --version
|
230
235
|
- python -m pip install --upgrade pip
|
231
|
-
-
|
236
|
+
- ta-lib, install it from https://ta-lib.org/install/#executable-installer-recommended
|
232
237
|
- change the tags/3.1.8 to the latest version in the script below and run it
|
233
238
|
- wget https://github.com/tcj2001/bitunix-automated-crypto-trading/archive/refs/tags/3.1.8.zip
|
234
239
|
- unzip zip file and copy the content inside C:\bitunix\Ver1.0.zip\bitunix-automated-crypto-trading-Ver1.0 to c:\bitunix
|
@@ -4,6 +4,7 @@ import time
|
|
4
4
|
import json
|
5
5
|
import hashlib
|
6
6
|
import asyncio
|
7
|
+
import re
|
7
8
|
import requests
|
8
9
|
from urllib.parse import urlencode
|
9
10
|
from typing import Dict, Any
|
@@ -33,17 +34,19 @@ class BitunixApi:
|
|
33
34
|
self.depth_Url='https://fapi.bitunix.com/api/v1/futures/market/depth'
|
34
35
|
self.placeOrder_Url="https://fapi.bitunix.com/api/v1/futures/trade/place_order"
|
35
36
|
self.flashClose_Url="https://fapi.bitunix.com/api/v1/futures/trade/flash_close_position"
|
37
|
+
self.get_order_url="https://fapi.bitunix.com/api/v1/futures/trade/get_order_detail"
|
36
38
|
self.pending_order_url="https://fapi.bitunix.com/api/v1/futures/trade/get_pending_orders"
|
37
39
|
self.cancelOrder_Url="https://fapi.bitunix.com/api/v1/futures/trade/cancel_orders"
|
40
|
+
self.pending_tpsl_order_url="https://fapi.bitunix.com/api/v1/futures/tpsl/get_pending_orders"
|
41
|
+
self.place_tpsl_order_url="https://fapi.bitunix.com/api/v1/futures/tpsl/place_order"
|
42
|
+
self.modify_tpsl_order_url="https://fapi.bitunix.com/api/v1/futures/tpsl/modify_order"
|
43
|
+
self.cancel_tpsl_Order_Url="https://fapi.bitunix.com/api/v1/futures/tpsl/cancel_order"
|
38
44
|
self.Trade_history_Url="https://fapi.bitunix.com/api/v1/futures/trade/get_history_trades"
|
39
45
|
self.position_history_Url="https://fapi.bitunix.com/api/v1/futures/position/get_history_positions"
|
40
46
|
|
41
47
|
async def update_settings(self, settings):
|
42
48
|
self.settings = settings
|
43
49
|
|
44
|
-
async def create_timestamp(self):
|
45
|
-
return str(int(time.time()*1000))
|
46
|
-
|
47
50
|
async def is_near_high_of_day(self, current_value, high_of_day, threshold_percentage=5):
|
48
51
|
# Calculate the difference as a percentage of the high of the day
|
49
52
|
difference_percentage = ((high_of_day - current_value) / high_of_day) * 100
|
@@ -56,70 +59,80 @@ class BitunixApi:
|
|
56
59
|
# Check if the difference is within the threshold
|
57
60
|
return difference_percentage <= threshold_percentage
|
58
61
|
|
62
|
+
#signature generation related methods
|
63
|
+
async def _create_timestamp(self):
|
64
|
+
return str(int(time.time()*1000))
|
59
65
|
|
60
66
|
async def _generate_nonce(self) -> str:
|
61
67
|
random_bytes = secrets.token_bytes(32)
|
62
68
|
return base64.b64encode(random_bytes).decode('utf-8')
|
63
69
|
|
70
|
+
async def _generate_sign_api(self, nonce, timestamp, api_key, secret_key, method, data):
|
71
|
+
query_params = ""
|
72
|
+
body = ""
|
73
|
+
if data:
|
74
|
+
if method.lower() == "get":
|
75
|
+
data = {k: v for k, v in data.items() if v is not None}
|
76
|
+
query_params = '&'.join([f"{k}={v}" for k, v in sorted(data.items())])
|
77
|
+
query_params = re.sub(r'[^a-zA-Z0-9]', '', query_params)
|
78
|
+
if method.lower() == "post":
|
79
|
+
# body = str(data).replace(" ", "")
|
80
|
+
body = str(data)
|
64
81
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
# First SHA256 encryption
|
73
|
-
digest = hashlib.sha256(message.encode()).hexdigest()
|
74
|
-
|
75
|
-
# Second SHA256 encryption
|
76
|
-
sign = hashlib.sha256((digest + self.secret_key).encode()).hexdigest()
|
77
|
-
|
78
|
-
return sign
|
79
|
-
|
80
|
-
async def _get(self, endpoint: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
81
|
-
response = self.session.get(endpoint, params=params)
|
82
|
-
response.raise_for_status()
|
83
|
-
return response.json()
|
82
|
+
digest_input = nonce + timestamp + api_key + query_params + body
|
83
|
+
# print(f"digest_input={digest_input}")
|
84
|
+
digest = hashlib.sha256((digest_input).encode()).hexdigest()
|
85
|
+
# print(f"digest={digest}")
|
86
|
+
sign_input = digest + secret_key
|
87
|
+
# print(f"sign_input={sign_input}")
|
88
|
+
sign = hashlib.sha256((sign_input).encode()).hexdigest()
|
84
89
|
|
90
|
+
return sign
|
91
|
+
|
85
92
|
async def _get_authenticated(self, endpoint: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
86
|
-
timestamp = await self.
|
93
|
+
timestamp = await self._create_timestamp()
|
87
94
|
nonce = await self._generate_nonce()
|
88
95
|
|
96
|
+
signature = await self._generate_sign_api(nonce, timestamp, self.api_key, self.secret_key, "get", params)
|
97
|
+
|
89
98
|
headers = {
|
90
99
|
"api-key": self.api_key,
|
100
|
+
"nonce": nonce,
|
91
101
|
"timestamp": timestamp,
|
92
|
-
"
|
102
|
+
"sign": signature, # Placeholder for signature
|
103
|
+
"language": "en-US",
|
104
|
+
"Content-Type": "application/json"
|
93
105
|
}
|
94
|
-
|
95
|
-
query_string = urlencode(sorted(params.items())).replace('=','').replace('&','') if params else ""
|
96
|
-
signature = await self.sign_request(nonce, timestamp, query_params=query_string)
|
97
|
-
headers["sign"] = signature
|
98
106
|
|
99
107
|
response = self.session.get(endpoint, params=params, headers=headers)
|
100
108
|
response.raise_for_status()
|
101
109
|
return response.json()
|
102
110
|
|
103
111
|
async def _post_authenticated(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
104
|
-
timestamp = await self.
|
112
|
+
timestamp = await self._create_timestamp()
|
105
113
|
nonce = await self._generate_nonce()
|
106
114
|
|
115
|
+
body_string = json.dumps(data, separators=(',',':'))
|
116
|
+
signature = await self._generate_sign_api(nonce, timestamp, self.api_key, self.secret_key, "post", body_string)
|
117
|
+
|
107
118
|
headers = {
|
108
119
|
"api-key": self.api_key,
|
109
120
|
"timestamp": timestamp,
|
110
121
|
"nonce": nonce,
|
122
|
+
"sign": signature,
|
111
123
|
"Content-Type": "application/json"
|
112
124
|
}
|
113
|
-
|
114
|
-
body_string = json.dumps(data, separators=(',', ':'))
|
115
|
-
signature = await self.sign_request(nonce, timestamp, body=body_string)
|
116
|
-
headers["sign"] = signature
|
117
125
|
|
118
126
|
response = self.session.post(endpoint, data=body_string, headers=headers)
|
119
127
|
self.logger.info(f"Response: {body_string} {response.json()}")
|
120
128
|
response.raise_for_status()
|
121
129
|
return response.json()
|
122
130
|
|
131
|
+
async def _get(self, endpoint: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
132
|
+
response = self.session.get(endpoint, params=params)
|
133
|
+
response.raise_for_status()
|
134
|
+
return response.json()
|
135
|
+
|
123
136
|
async def PlaceOrder(self, ticker, qty, price, side, positionId=0, tradeSide="OPEN",reduceOnly=False, tpPrice=0, tpOrderPrice=0, slPrice=0, slOrderPrice=0):
|
124
137
|
datajs = None
|
125
138
|
if tpPrice == 0 and slPrice == 0:
|
@@ -143,8 +156,8 @@ class BitunixApi:
|
|
143
156
|
"symbol": ticker,
|
144
157
|
"tradeSide":tradeSide,
|
145
158
|
"slPrice": slPrice,
|
146
|
-
"slStopType": self.settings.
|
147
|
-
"slOrderType": self.settings.
|
159
|
+
"slStopType": self.settings.SL_STOP_TYPE,
|
160
|
+
"slOrderType": self.settings.SL_ORDER_TYPE,
|
148
161
|
"slOrderPrice": slOrderPrice,
|
149
162
|
"positionId":positionId
|
150
163
|
}
|
@@ -158,8 +171,8 @@ class BitunixApi:
|
|
158
171
|
"symbol": ticker,
|
159
172
|
"tradeSide":tradeSide,
|
160
173
|
"tpPrice": tpPrice,
|
161
|
-
"tpStopType": self.settings.
|
162
|
-
"tpOrderType": self.settings.
|
174
|
+
"tpStopType": self.settings.TP_STOP_TYPE,
|
175
|
+
"tpOrderType": self.settings.TP_ORDER_TYPE,
|
163
176
|
"tpOrderPrice":tpOrderPrice,
|
164
177
|
"positionId":positionId
|
165
178
|
}
|
@@ -173,12 +186,12 @@ class BitunixApi:
|
|
173
186
|
"symbol": ticker,
|
174
187
|
"tradeSide":tradeSide,
|
175
188
|
"tpPrice": tpPrice,
|
176
|
-
"tpStopType": self.settings.
|
177
|
-
"tpOrderType": self.settings.
|
189
|
+
"tpStopType": self.settings.TP_STOP_TYPE,
|
190
|
+
"tpOrderType": self.settings.TP_ORDER_TYPE,
|
178
191
|
"tpOrderPrice":tpOrderPrice,
|
179
192
|
"slPrice": slPrice,
|
180
|
-
"slStopType": self.settings.
|
181
|
-
"slOrderType": self.settings.
|
193
|
+
"slStopType": self.settings.SL_STOP_TYPE,
|
194
|
+
"slOrderType": self.settings.SL_ORDER_TYPE,
|
182
195
|
"slOrderPrice": slOrderPrice,
|
183
196
|
"positionId":positionId
|
184
197
|
}
|
@@ -201,13 +214,20 @@ class BitunixApi:
|
|
201
214
|
datajs = await self._post_authenticated(self.cancelOrder_Url,data)
|
202
215
|
return datajs
|
203
216
|
|
204
|
-
async def GetTradeHistoryData(self):
|
205
|
-
tradeHistory=await self._get_authenticated(self.Trade_history_Url)
|
217
|
+
async def GetTradeHistoryData(self, dictparm={}):
|
218
|
+
tradeHistory=await self._get_authenticated(self.Trade_history_Url, dictparm)
|
206
219
|
if tradeHistory['code']==0:
|
207
220
|
return tradeHistory['data']
|
208
221
|
else:
|
209
222
|
self.logger.info(tradeHistory['msg'])
|
210
223
|
|
224
|
+
async def GetOrderData(self, dictparm={}):
|
225
|
+
orders=await self._get_authenticated(self.get_order_url, dictparm)
|
226
|
+
if orders['code']==0:
|
227
|
+
return orders['data']
|
228
|
+
else:
|
229
|
+
self.logger.info(orders['msg'])
|
230
|
+
|
211
231
|
async def GetPendingOrderData(self,dictparm={}):
|
212
232
|
orders=await self._get_authenticated(self.pending_order_url, dictparm)
|
213
233
|
if orders['code']==0:
|
@@ -215,6 +235,35 @@ class BitunixApi:
|
|
215
235
|
else:
|
216
236
|
self.logger.info(orders['msg'])
|
217
237
|
|
238
|
+
async def GetPendingTpSlOrderData(self,dictparm={}):
|
239
|
+
orders=await self._get_authenticated(self.pending_tpsl_order_url, dictparm)
|
240
|
+
if orders['code']==0:
|
241
|
+
return orders['data']
|
242
|
+
else:
|
243
|
+
self.logger.info(orders['msg'])
|
244
|
+
|
245
|
+
async def PlaceTpSlOrder(self,dictparm={}):
|
246
|
+
orders=await self._post_authenticated(self.place_tpsl_order_url, dictparm)
|
247
|
+
if orders['code']==0:
|
248
|
+
return orders['data']
|
249
|
+
else:
|
250
|
+
self.logger.info(orders['msg'])
|
251
|
+
|
252
|
+
async def ModifyTpSlOrder(self,dictparm={}):
|
253
|
+
orders=await self._post_authenticated(self.modify_tpsl_order_url, dictparm)
|
254
|
+
if orders['code']==0:
|
255
|
+
return orders['data']
|
256
|
+
else:
|
257
|
+
self.logger.info(orders['msg'])
|
258
|
+
|
259
|
+
async def CancelTpSlOrder(self, symbol, orderId):
|
260
|
+
data = {
|
261
|
+
"symbol": symbol,
|
262
|
+
"orderId":orderId
|
263
|
+
}
|
264
|
+
datajs = await self._post_authenticated(self.cancel_tpsl_Order_Url,data)
|
265
|
+
return datajs
|
266
|
+
|
218
267
|
async def GetPendingPositionData(self, dictparm={}):
|
219
268
|
positions=await self._get_authenticated(self.pending_positions_URL, dictparm)
|
220
269
|
if positions['code']==0:
|
@@ -646,6 +646,7 @@ class BitunixSignal:
|
|
646
646
|
async def get_duration_minutes_unixtime(self, trade_time_unix):
|
647
647
|
"""Calculates the duration in minutes from trade time to current time."""
|
648
648
|
current_time_unix = int(time.time()) # Get current Unix timestamp
|
649
|
+
print(f"Current time (Unix): {current_time_unix}, Trade time (Unix): {trade_time_unix}")
|
649
650
|
duration_seconds = current_time_unix - trade_time_unix
|
650
651
|
duration_minutes = duration_seconds / 60 # Convert seconds to minutes
|
651
652
|
return round(duration_minutes)
|
@@ -678,6 +679,12 @@ class BitunixSignal:
|
|
678
679
|
increment = Decimal(f'1e-{decimal_places}')
|
679
680
|
return float(value + increment)
|
680
681
|
|
682
|
+
async def decrement_by_last_decimal(self, value_str):
|
683
|
+
value = Decimal(value_str)
|
684
|
+
# Count number of decimal places
|
685
|
+
decimal_places = abs(value.as_tuple().exponent)
|
686
|
+
decrement = Decimal(f'1e-{decimal_places}')
|
687
|
+
return float(value - decrement)
|
681
688
|
|
682
689
|
async def calculate_candles(self, timeframe, duration_minutes):
|
683
690
|
|
@@ -752,12 +759,12 @@ class BitunixSignal:
|
|
752
759
|
tpPrice = price + float(self.settings.PROFIT_AMOUNT)/qty if side == "BUY" else price - float(self.settings.PROFIT_AMOUNT)/qty
|
753
760
|
tpPrice = Decimal(await self.str_precision(tpPrice))
|
754
761
|
tpPrice =float(str(tpPrice.quantize(Decimal(f'1e-{decimal_places}'))))
|
755
|
-
tpOrderPrice = await self.
|
762
|
+
tpOrderPrice = await self.decrement_by_last_decimal(str(tpPrice))
|
756
763
|
if self.settings.PROFIT_PERCENTAGE > 0 or self.settings.PROFIT_AMOUNT > 0:
|
757
764
|
tpPrice = price * (1 + float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE) if side == "BUY" else price * (1 - float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE)
|
758
765
|
tpPrice = Decimal(await self.str_precision(tpPrice))
|
759
766
|
tpPrice = float(str(tpPrice.quantize(Decimal(f'1e-{decimal_places}'))))
|
760
|
-
tpOrderPrice = await self.
|
767
|
+
tpOrderPrice = await self.decrement_by_last_decimal(await self.str_precision(tpPrice))
|
761
768
|
|
762
769
|
slPrice=0
|
763
770
|
slOrderPrice=0
|
@@ -838,6 +845,74 @@ class BitunixSignal:
|
|
838
845
|
select = False
|
839
846
|
|
840
847
|
if select and int(self.settings.MAX_AUTO_TRADES)!=0:
|
848
|
+
|
849
|
+
if self.settings.BOT_TRAIL_STOP_LOSS and self.settings.PROFIT_PERCENTAGE > 0 and self.settings.LOSS_PERCENTAGE > 0:
|
850
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
851
|
+
price = (bid if side == "BUY" else ask if side == "SELL" else last) if bid<=last<=ask else last
|
852
|
+
decimal_places = abs(Decimal(str(price)).as_tuple().exponent)
|
853
|
+
|
854
|
+
symbol = row['symbol']
|
855
|
+
positionId = row['positionId']
|
856
|
+
avgOpenPrice = float(row['avgOpenPrice'])
|
857
|
+
qty = float(row['qty'])
|
858
|
+
|
859
|
+
tpPrice = None
|
860
|
+
tporderId = None
|
861
|
+
old_tpPrice = None
|
862
|
+
tpStopType = self.settings.SL_STOP_TYPE
|
863
|
+
tpOrderType = self.settings.SL_ORDER_TYPE
|
864
|
+
|
865
|
+
slPrice = None
|
866
|
+
slorderId = None
|
867
|
+
old_slPrice = None
|
868
|
+
slStopType = self.settings.SL_STOP_TYPE
|
869
|
+
slOrderType = self.settings.SL_ORDER_TYPE
|
870
|
+
|
871
|
+
datajs = await self.bitunixApi.GetPendingTpSlOrderData({'symbol': symbol})
|
872
|
+
if datajs:
|
873
|
+
for order in datajs:
|
874
|
+
if order['tpPrice'] is not None:
|
875
|
+
tporderId = order['id']
|
876
|
+
tpStopType = order['tpStopType']
|
877
|
+
tpOrderType = order['tpOrderType']
|
878
|
+
tpPrice = float(order['tpPrice'])
|
879
|
+
old_tpPrice = tpPrice
|
880
|
+
if order['slPrice'] is not None:
|
881
|
+
slorderId = order['id']
|
882
|
+
slStopType = order['slStopType']
|
883
|
+
slOrderType = order['slOrderType']
|
884
|
+
slPrice = float(order['slPrice'])
|
885
|
+
old_slPrice = slPrice
|
886
|
+
|
887
|
+
# move TP and SL in the direction of profit
|
888
|
+
midpoint = tpPrice / (1 + self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE) if side == "BUY" else tpPrice / (1 - self.settings.PROFIT_PERCENTAGE/100/self.settings.LEVERAGE) if tpPrice is not None else None
|
889
|
+
if price > midpoint and side == "BUY" or price < midpoint and side == "SELL":
|
890
|
+
|
891
|
+
tpPrice = price * (1 + float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE) if side == "BUY" else price * (1 - float(self.settings.PROFIT_PERCENTAGE) / 100 / self.settings.LEVERAGE)
|
892
|
+
tpPrice = Decimal(await self.str_precision(tpPrice))
|
893
|
+
tpPrice =float(str(tpPrice.quantize(Decimal(f'1e-{decimal_places}'))))
|
894
|
+
tpOrderPrice = await self.decrement_by_last_decimal(str(tpPrice))
|
895
|
+
|
896
|
+
slPrice = price * (1 - float(self.settings.LOSS_PERCENTAGE) / 100 / self.settings.LEVERAGE) if side == "BUY" else price * (1 + float(self.settings.LOSS_PERCENTAGE) / 100 / self.settings.LEVERAGE)
|
897
|
+
slPrice = Decimal(await self.str_precision(slPrice))
|
898
|
+
slPrice = float(str(slPrice.quantize(Decimal(f'1e-{decimal_places}'))))
|
899
|
+
slOrderPrice = await self.increment_by_last_decimal(await self.str_precision(slPrice))
|
900
|
+
|
901
|
+
self.notifications.add_notification(
|
902
|
+
f'{colors.CYAN} {row.symbol} TP/SL midpoint is at {midpoint}'
|
903
|
+
)
|
904
|
+
datajs3 = await self.bitunixApi.ModifyTpSlOrder({'orderId':slorderId,'slPrice':str(slPrice),'slQty':str(qty),'slStopType':slStopType,'slOrderType':slOrderType})
|
905
|
+
if datajs3 is not None:
|
906
|
+
self.notifications.add_notification(
|
907
|
+
f'{colors.CYAN} Stop Loss order for {row.symbol} moved from {old_slPrice} to {slPrice}'
|
908
|
+
)
|
909
|
+
datajs2 = await self.bitunixApi.ModifyTpSlOrder({'orderId':tporderId,'tpPrice':str(tpPrice), 'tpOrderPrice':str(tpOrderPrice), 'tpQty':str(qty),'tpStopType':tpStopType,'tpOrderType':tpOrderType})
|
910
|
+
if datajs2 is not None:
|
911
|
+
self.notifications.add_notification(
|
912
|
+
f'{colors.CYAN} Take Profit order for {row.symbol} moved from {old_tpPrice} to {tpPrice}'
|
913
|
+
)
|
914
|
+
|
915
|
+
|
841
916
|
if self.settings.BOT_TAKE_PROFIT_STOP_LOSS:
|
842
917
|
# check take profit amount or accept loss amount
|
843
918
|
if float(self.settings.LOSS_AMOUNT) > 0 and total_pnl < -float(self.settings.LOSS_AMOUNT):
|
@@ -319,20 +319,16 @@ class Interval:
|
|
319
319
|
df.replace([np.inf, -np.inf], 0, inplace=True)
|
320
320
|
|
321
321
|
if self.settings.OPEN_ON_ANY_SIGNAL:
|
322
|
-
# If EMA is enabled and crossing or MACD is enabled and crossing or BBM is enabled and crossing or RSI is enbabled and crossing
|
323
|
-
# and
|
324
|
-
# ADX is enabled and strong and candle trend is enabled and bullish
|
325
|
-
# then BUY or SELL
|
326
322
|
|
327
323
|
# Check for BUY signal
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
324
|
+
ema_open = (self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY")
|
325
|
+
macd_open = (self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY")
|
326
|
+
bbm_open = (self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY")
|
327
|
+
rsi_open = (self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY")
|
328
|
+
bos_open = (self.settings.BOS_STUDY and self.settings.BOS_CHECK_ON_OPEN and self.bos_signal == "BUY")
|
329
|
+
trendline_open = (self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "BUY")
|
330
|
+
|
331
|
+
buy_conditions = ema_open or macd_open or bbm_open or rsi_open or bos_open or trendline_open
|
336
332
|
additional_buy_conditions = (
|
337
333
|
(not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
|
338
334
|
(not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BULLISH")
|
@@ -340,14 +336,14 @@ class Interval:
|
|
340
336
|
|
341
337
|
|
342
338
|
# Check for SELL signal
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
339
|
+
ema_close = (self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_CLOSE and self.ema_close_signal == "SELL")
|
340
|
+
macd_close = (self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_CLOSE and self.macd_signal == "SELL")
|
341
|
+
bbm_close = (self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_CLOSE and self.bbm_signal == "SELL")
|
342
|
+
rsi_close = (self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_CLOSE and self.rsi_signal == "SELL")
|
343
|
+
bos_close = (self.settings.BOS_STUDY and self.settings.BOS_CHECK_ON_CLOSE and self.bos_signal == "SELL")
|
344
|
+
trendline_close = (self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_CLOSE and self.trendline_signal == "SELL")
|
345
|
+
|
346
|
+
sell_conditions = ema_close or macd_close or bbm_close or rsi_close or bos_close or trendline_close
|
351
347
|
additional_sell_conditions = (
|
352
348
|
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") and
|
353
349
|
(self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BEARISH")
|
@@ -361,68 +357,40 @@ class Interval:
|
|
361
357
|
else:
|
362
358
|
self.current_signal = "HOLD"
|
363
359
|
else:
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
or not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN
|
370
|
-
) and (
|
371
|
-
(self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY")
|
372
|
-
or not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN
|
373
|
-
) and (
|
374
|
-
(self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY")
|
360
|
+
ema_open =(self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_OPEN and self.ema_open_signal == "BUY") \
|
361
|
+
or not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN
|
362
|
+
macd_open = (self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_OPEN and self.macd_signal == "BUY") \
|
363
|
+
or not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN
|
364
|
+
bbm_open = (self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_OPEN and self.bbm_signal == "BUY") \
|
375
365
|
or not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN
|
376
|
-
|
377
|
-
(self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY")
|
366
|
+
rsi_open = (self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_OPEN and self.rsi_signal == "BUY") \
|
378
367
|
or not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN
|
379
|
-
|
380
|
-
|
368
|
+
bos_open = (self.settings.BOS_STUDY and self.settings.BOS_CHECK_ON_OPEN and self.bos_signal == "BUY") \
|
369
|
+
or not self.settings.BOS_STUDY or not self.settings.BOS_CHECK_ON_OPEN
|
370
|
+
trendline_open = (self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "BUY") \
|
381
371
|
or not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_OPEN
|
382
|
-
)
|
383
|
-
if not any([
|
384
|
-
self.settings.EMA_STUDY, self.settings.MACD_STUDY, self.settings.BBM_STUDY,
|
385
|
-
self.settings.RSI_STUDY, self.settings.TRENDLINE_STUDY
|
386
|
-
]) and not any([
|
387
|
-
self.settings.EMA_CHECK_ON_OPEN, self.settings.MACD_CHECK_ON_OPEN, self.settings.BBM_CHECK_ON_OPEN,
|
388
|
-
self.settings.RSI_CHECK_ON_OPEN, self.settings.TRENDLINE_CHECK_ON_OPEN
|
389
|
-
]):
|
390
|
-
buy_conditions = False
|
391
372
|
|
373
|
+
buy_conditions = ema_open and macd_open and bbm_open and rsi_open and bos_open and trendline_open
|
392
374
|
additional_buy_conditions = (
|
393
375
|
(not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
|
394
376
|
(not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BULLISH")
|
395
377
|
)
|
396
378
|
|
397
379
|
# Check for SELL signal
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
or not self.settings.
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
or not self.settings.
|
410
|
-
|
411
|
-
|
412
|
-
or not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN
|
413
|
-
) and (
|
414
|
-
(self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_OPEN and self.trendline_signal == "SELL")
|
415
|
-
or not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_OPEN
|
416
|
-
)
|
417
|
-
if not any([
|
418
|
-
self.settings.EMA_STUDY, self.settings.MACD_STUDY, self.settings.BBM_STUDY,
|
419
|
-
self.settings.RSI_STUDY, self.settings.TRENDLINE_STUDY
|
420
|
-
]) and not any([
|
421
|
-
self.settings.EMA_CHECK_ON_OPEN, self.settings.MACD_CHECK_ON_OPEN, self.settings.BBM_CHECK_ON_OPEN,
|
422
|
-
self.settings.RSI_CHECK_ON_OPEN, self.settings.TRENDLINE_CHECK_ON_OPEN
|
423
|
-
]):
|
424
|
-
sell_conditions = False
|
425
|
-
|
380
|
+
ema_close= (self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_CLOSE and self.ema_close_signal == "SELL") \
|
381
|
+
or not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_CLOSE
|
382
|
+
macd_close = (self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_CLOSE and self.macd_signal == "SELL") \
|
383
|
+
or not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_CLOSE
|
384
|
+
bbm_close = (self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_CLOSE and self.bbm_signal == "SELL") \
|
385
|
+
or not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_CLOSE
|
386
|
+
rsi_close = (self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_CLOSE and self.rsi_signal == "SELL") \
|
387
|
+
or not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_CLOSE
|
388
|
+
bos_close = (self.settings.BOS_STUDY and self.settings.BOS_CHECK_ON_CLOSE and self.bos_signal == "SELL") \
|
389
|
+
or not self.settings.BOS_STUDY or not self.settings.BOS_CHECK_ON_CLOSE
|
390
|
+
trendline_close = (self.settings.TRENDLINE_STUDY and self.settings.TRENDLINE_CHECK_ON_CLOSE and self.trendline_signal == "SELL") \
|
391
|
+
or not self.settings.TRENDLINE_STUDY or not self.settings.TRENDLINE_CHECK_ON_CLOSE
|
392
|
+
|
393
|
+
sell_conditions = ema_close and macd_close and bbm_close and rsi_close and bos_close and trendline_close
|
426
394
|
additional_sell_conditions = (
|
427
395
|
(self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_OPEN and self.adx_signal == "STRONG") or
|
428
396
|
(self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BEARISH")
|
@@ -22,10 +22,11 @@ class Settings(BaseSettings):
|
|
22
22
|
PROFIT_AMOUNT: float = Field(default=0, ge=0.0)
|
23
23
|
LOSS_AMOUNT: float = Field(default=5.0, ge=0.0)
|
24
24
|
BOT_TAKE_PROFIT_STOP_LOSS: bool = Field(default=False)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
BOT_TRAIL_STOP_LOSS: bool = Field(default=False)
|
26
|
+
TP_STOP_TYPE: str = Field(default="MARK_PRICE")
|
27
|
+
TP_ORDER_TYPE: str = Field(default="LIMIT")
|
28
|
+
SL_STOP_TYPE: str = Field(default="MARK_PRICE")
|
29
|
+
SL_ORDER_TYPE: str = Field(default="MARKET")
|
29
30
|
OPTION_MOVING_AVERAGE: str = Field(default="1h")
|
30
31
|
DELAY_IN_MINUTES_FOR_SAME_TICKER_TRADES: int = Field(default=15, ge=0)
|
31
32
|
BARS: int = Field(default=100, ge=1)
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "3.3.4"
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "3.3.2"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|