bitunix-automated-crypto-trading 2.6.7__py3-none-any.whl → 2.6.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bitunix_automated_crypto_trading/AsyncThreadRunner.py +81 -81
- bitunix_automated_crypto_trading/BitunixApi.py +278 -278
- bitunix_automated_crypto_trading/BitunixSignal.py +1099 -1099
- bitunix_automated_crypto_trading/BitunixWebSocket.py +254 -254
- bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +74 -74
- bitunix_automated_crypto_trading/NotificationManager.py +23 -23
- bitunix_automated_crypto_trading/ThreadManager.py +68 -68
- bitunix_automated_crypto_trading/TickerManager.py +635 -635
- bitunix_automated_crypto_trading/bitunix.py +597 -594
- bitunix_automated_crypto_trading/config.py +90 -90
- bitunix_automated_crypto_trading/logger.py +84 -84
- {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/METADATA +36 -36
- bitunix_automated_crypto_trading-2.6.8.dist-info/RECORD +17 -0
- bitunix_automated_crypto_trading/config.txt +0 -60
- bitunix_automated_crypto_trading/sampleenv.txt +0 -5
- bitunix_automated_crypto_trading/static/chart.css +0 -28
- bitunix_automated_crypto_trading/static/chart.js +0 -362
- bitunix_automated_crypto_trading/static/modal.css +0 -68
- bitunix_automated_crypto_trading/static/modal.js +0 -147
- bitunix_automated_crypto_trading/static/script.js +0 -166
- bitunix_automated_crypto_trading/static/styles.css +0 -118
- bitunix_automated_crypto_trading/templates/charts.html +0 -98
- bitunix_automated_crypto_trading/templates/login.html +0 -19
- bitunix_automated_crypto_trading/templates/main.html +0 -551
- bitunix_automated_crypto_trading/templates/modal-chart.html +0 -26
- bitunix_automated_crypto_trading/templates/modal-config.html +0 -34
- bitunix_automated_crypto_trading/templates/modal-logs.html +0 -15
- bitunix_automated_crypto_trading-2.6.7.dist-info/RECORD +0 -31
- {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/WHEEL +0 -0
- {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/entry_points.txt +0 -0
- {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/top_level.txt +0 -0
@@ -1,1099 +1,1099 @@
|
|
1
|
-
import numpy as np
|
2
|
-
import pandas as pd
|
3
|
-
import json
|
4
|
-
import asyncio
|
5
|
-
from datetime import datetime, timedelta, timezone
|
6
|
-
from collections import defaultdict
|
7
|
-
import traceback
|
8
|
-
import time
|
9
|
-
import pytz
|
10
|
-
from concurrent.futures import ProcessPoolExecutor
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
logger = Logger(__name__).get_logger()
|
17
|
-
colors = Colors()
|
18
|
-
import gc
|
19
|
-
from concurrent.futures import ProcessPoolExecutor
|
20
|
-
|
21
|
-
cst = pytz.timezone('US/Central')
|
22
|
-
|
23
|
-
class BitunixSignal:
|
24
|
-
def __init__(self, api_key, secret_key, settings, threadManager, notifications, bitunixApi):
|
25
|
-
self.api_key = api_key
|
26
|
-
self.secret_key = secret_key
|
27
|
-
self.settings=settings
|
28
|
-
self.threadManager = threadManager
|
29
|
-
self.notifications = notifications
|
30
|
-
self.bitunixApi = bitunixApi
|
31
|
-
|
32
|
-
#Ticker object
|
33
|
-
self.tickerObjects = Tickers(self.settings)
|
34
|
-
|
35
|
-
#these are used for html rendering as well as storing
|
36
|
-
self.signaldf= pd.DataFrame()
|
37
|
-
self.positiondf= pd.DataFrame()
|
38
|
-
self.positiondf2= pd.DataFrame()
|
39
|
-
self.portfoliodf=pd.DataFrame()
|
40
|
-
self.orderdf=pd.DataFrame()
|
41
|
-
self.tickerdf=pd.DataFrame()
|
42
|
-
self.inspectdf=pd.DataFrame()
|
43
|
-
self.tradesdf=pd.DataFrame()
|
44
|
-
self.positionHistorydf=pd.DataFrame()
|
45
|
-
|
46
|
-
self.portfoliodfrenderer=None
|
47
|
-
self.positiondfrenderer=None
|
48
|
-
self.orderdfrenderer=None
|
49
|
-
self.signaldfrenderer=None
|
50
|
-
self.allsignaldfrenderer=None
|
51
|
-
self.positionHistorydfrenderer=None
|
52
|
-
|
53
|
-
self.portfoliodfStyle=None
|
54
|
-
self.positiondfStyle=None
|
55
|
-
self.orderdfStyle=None
|
56
|
-
self.signaldfStyle=None
|
57
|
-
self.allsignaldfStyle=None
|
58
|
-
self.positionHistorydfStyle=None
|
59
|
-
|
60
|
-
#postion, order, etc
|
61
|
-
self.pendingOrders=None
|
62
|
-
self.pendingPositions=None
|
63
|
-
self.portfolioData=None
|
64
|
-
self.tradeHistoryData=None
|
65
|
-
self.positionHistoryData=None
|
66
|
-
|
67
|
-
#this contain all data
|
68
|
-
self.signaldf_full = pd.DataFrame()
|
69
|
-
self.signaldf_filtered = pd.DataFrame()
|
70
|
-
|
71
|
-
#websockets
|
72
|
-
self.bitunixPrivateWebSocketClient = BitunixPrivateWebSocketClient(self.api_key, self.secret_key)
|
73
|
-
if self.settings.USE_PUBLIC_WEBSOCKET:
|
74
|
-
self.bitunixPublicDepthWebSocketClient = BitunixPublicWebSocketClient(self.api_key, self.secret_key, "depth")
|
75
|
-
self.bitunixPublicTickerWebSocketClient = BitunixPublicWebSocketClient(self.api_key, self.secret_key, "ticker")
|
76
|
-
|
77
|
-
self.tickerList=[]
|
78
|
-
|
79
|
-
self.green="#A5DFDF"
|
80
|
-
self.red="#FFB1C1"
|
81
|
-
|
82
|
-
self.profit=0
|
83
|
-
|
84
|
-
self.lastAutoTradeTime = time.time()
|
85
|
-
self.lastTickerDataTime = time.time()
|
86
|
-
|
87
|
-
async def update_settings(self, settings):
|
88
|
-
self.settings = settings
|
89
|
-
self.tickerObjects.update_settings(settings)
|
90
|
-
|
91
|
-
async def load_tickers(self):
|
92
|
-
symbols = await self.bitunixApi.GetTickerList(float(self.settings.THRESHOLD), float(self.settings.MIN_VOLUME))
|
93
|
-
self.pendingPositions= await self.bitunixApi.GetPendingPositionData()
|
94
|
-
self.pendingOrders= await self.bitunixApi.GetPendingOrderData()
|
95
|
-
olist=[]
|
96
|
-
plist=[]
|
97
|
-
if self.pendingPositions:
|
98
|
-
plist = [entry['symbol'] for entry in self.pendingPositions]
|
99
|
-
if self.pendingOrders['orderList']:
|
100
|
-
olist = [entry['symbol'] for entry in self.pendingOrders['orderList']]
|
101
|
-
newlist=olist+plist+list(set(symbols))
|
102
|
-
self.tickerList=newlist[:300]
|
103
|
-
#self.tickerList=['POPCATUSDT','MANAUSDT']
|
104
|
-
|
105
|
-
[await self.add_ticker_to_tickerObjects(sym) for sym in self.tickerList]
|
106
|
-
self.notifications.add_notification(f"{len(self.tickerList)} ticker list loaded")
|
107
|
-
|
108
|
-
async def add_ticker_to_tickerObjects(self, symbol):
|
109
|
-
if not self.tickerObjects.get(symbol):
|
110
|
-
self.tickerObjects.add(symbol)
|
111
|
-
|
112
|
-
async def start_jobs(self):
|
113
|
-
#setup renderers
|
114
|
-
await asyncio.create_task(self.DefinehtmlRenderers())
|
115
|
-
|
116
|
-
#async thread that runs forever jobs
|
117
|
-
self.GetportfolioDataTask = AsyncThreadRunner(self.GetportfolioData, interval=int(self.settings.PORTFOLIO_API_INTERVAL))
|
118
|
-
self.GetportfolioDataTask.start_thread(thread_name="GetportfolioData")
|
119
|
-
|
120
|
-
self.GetPendingPositionDataTask = AsyncThreadRunner(self.GetPendingPositionData, interval=int(self.settings.PENDING_POSITIONS_API_INTERVAL))
|
121
|
-
self.GetPendingPositionDataTask.start_thread(thread_name="GetPendingPositionData")
|
122
|
-
|
123
|
-
self.GetPendingOrderDataTask = AsyncThreadRunner(self.GetPendingOrderData, interval=int(self.settings.PENDING_ORDERS_API_INTERVAL))
|
124
|
-
self.GetPendingOrderDataTask.start_thread(thread_name="GetPendingOrderData")
|
125
|
-
|
126
|
-
self.GetTradeHistoryDataTask = AsyncThreadRunner(self.GetTradeHistoryData, interval=int(self.settings.TRADE_HISTORY_API_INTERVAL))
|
127
|
-
self.GetTradeHistoryDataTask.start_thread(thread_name="GetTradeHistoryData")
|
128
|
-
|
129
|
-
self.GetPositionHistoryDataTask = AsyncThreadRunner(self.GetPositionHistoryData, interval=int(self.settings.POSITION_HISTORY_API_INTERVAL))
|
130
|
-
self.GetPositionHistoryDataTask.start_thread(thread_name="GetPositionHistoryData")
|
131
|
-
|
132
|
-
#run restartable asynch thread
|
133
|
-
await self.restartable_jobs()
|
134
|
-
|
135
|
-
async def restart_jobs(self):
|
136
|
-
|
137
|
-
#stop websocket async thread jobs
|
138
|
-
await self.bitunixPrivateWebSocketClient.stop_websocket()
|
139
|
-
await self.ProcessPrivateDataTask.stop_thread()
|
140
|
-
|
141
|
-
if self.settings.USE_PUBLIC_WEBSOCKET:
|
142
|
-
await self.bitunixPublicDepthWebSocketClient.stop_websocket()
|
143
|
-
await self.UpdateDepthDataTask.stop_thread()
|
144
|
-
|
145
|
-
await self.bitunixPublicTickerWebSocketClient.stop_websocket()
|
146
|
-
await self.UpdateTickerDataTask.stop_thread()
|
147
|
-
|
148
|
-
#kill the loop to restart public websocket
|
149
|
-
#not using for now
|
150
|
-
#await self.restartPublicWebsocketTask.stop_thread()
|
151
|
-
|
152
|
-
#stop onetime / periodic async thread jobs
|
153
|
-
await self.LoadKlineHistoryTask.stop_thread()
|
154
|
-
await self.GetTickerDataTask.stop_thread()
|
155
|
-
await self.AutoTradeProcessTask.stop_thread()
|
156
|
-
|
157
|
-
#start jobs
|
158
|
-
await self.load_tickers()
|
159
|
-
await self.restartable_jobs()
|
160
|
-
|
161
|
-
async def restartable_jobs(self):
|
162
|
-
#start cancelable async jobs
|
163
|
-
#websocket jobs
|
164
|
-
self.ProcessPrivateDataTask = AsyncThreadRunner(self.bitunixPrivateWebSocketClient.run_websocket, 0, self.ProcessPrivateData)
|
165
|
-
self.ProcessPrivateDataTask.start_thread(thread_name="ProcessPrivateData")
|
166
|
-
|
167
|
-
if self.settings.USE_PUBLIC_WEBSOCKET:
|
168
|
-
|
169
|
-
self.bitunixPublicDepthWebSocketClient.tickerList = self.tickerList
|
170
|
-
self.UpdateDepthDataTask = AsyncThreadRunner(self.bitunixPublicDepthWebSocketClient.run_websocket, 0, self.UpdateDepthData)
|
171
|
-
self.UpdateDepthDataTask.start_thread(thread_name="UpdateDepthData")
|
172
|
-
|
173
|
-
self.bitunixPublicTickerWebSocketClient.tickerList = self.tickerList
|
174
|
-
self.UpdateTickerDataTask = AsyncThreadRunner(self.bitunixPublicTickerWebSocketClient.run_websocket, 0, self.UpdateTickerData)
|
175
|
-
self.UpdateTickerDataTask.start_thread(thread_name="UpdateTickerData")
|
176
|
-
|
177
|
-
#normal processes
|
178
|
-
self.LoadKlineHistoryTask = AsyncThreadRunner(self.LoadKlineHistory, interval=0) # run only once
|
179
|
-
self.LoadKlineHistoryTask.start_thread(thread_name="LoadKlineHistory")
|
180
|
-
|
181
|
-
self.GetTickerDataTask = AsyncThreadRunner(self.GetTickerData, interval=int(self.settings.TICKER_DATA_API_INTERVAL))
|
182
|
-
self.GetTickerDataTask.start_thread(thread_name="GetTickerData")
|
183
|
-
|
184
|
-
self.AutoTradeProcessTask = AsyncThreadRunner(self.AutoTradeProcess, interval=int(self.settings.SIGNAL_CHECK_INTERVAL))
|
185
|
-
self.AutoTradeProcessTask.start_thread(thread_name="AutoTradeProcess")
|
186
|
-
|
187
|
-
#start the loop to restart public websocket
|
188
|
-
#if self.settings.USE_PUBLIC_WEBSOCKET:
|
189
|
-
# self.restartPublicWebsocketTask = AsyncThreadRunner(self.restartPublicWebsocket, interval=0)
|
190
|
-
# self.restartPublicWebsocketTask.start_thread(thread_name="restartPublicWebsocket")
|
191
|
-
|
192
|
-
#this is a normal task runing in a async thread, that can be cancelled
|
193
|
-
# this runs in a async thread to stop and start the public websocket, as we found some lagging when it runs continously
|
194
|
-
#not used now
|
195
|
-
async def restartPublicWebsocket(self):
|
196
|
-
while True:
|
197
|
-
await asyncio.sleep(int(self.settings.PUBLIC_WEBSOCKET_RESTART_INTERVAL))
|
198
|
-
|
199
|
-
if self.settings.VERBOSE_LOGGING:
|
200
|
-
self.notifications.add_notification('Restarting public websocket')
|
201
|
-
logger.info(f"Restarting public websocket")
|
202
|
-
|
203
|
-
if self.settings.USE_PUBLIC_WEBSOCKET:
|
204
|
-
await self.UpdateDepthDataTask.stop_thread()
|
205
|
-
await self.UpdateTickerDataTask.stop_thread()
|
206
|
-
|
207
|
-
await asyncio.sleep(30)
|
208
|
-
|
209
|
-
if self.settings.USE_PUBLIC_WEBSOCKET:
|
210
|
-
self.bitunixPublicDepthWebSocketClient.tickerList = self.tickerList
|
211
|
-
self.UpdateDepthDataTask = AsyncThreadRunner(self.bitunixPublicDepthWebSocketClient.run_websocket, 0, self.UpdateDepthData)
|
212
|
-
self.UpdateDepthDataTask.start_thread(thread_name="UpdateDepthData")
|
213
|
-
|
214
|
-
self.bitunixPublicTickerWebSocketClient.tickerList = self.tickerList
|
215
|
-
self.UpdateTickerDataTask = AsyncThreadRunner(self.bitunixPublicTickerWebSocketClient.run_websocket, 0, self.UpdateTickerData)
|
216
|
-
self.UpdateTickerDataTask.start_thread(thread_name="UpdateTickerData")
|
217
|
-
|
218
|
-
if self.settings.VERBOSE_LOGGING:
|
219
|
-
self.notifications.add_notification('Restared public websocket')
|
220
|
-
|
221
|
-
###########################################################################################################
|
222
|
-
async def DefinehtmlRenderers(self):
|
223
|
-
period = self.settings.OPTION_MOVING_AVERAGE
|
224
|
-
#html rendering setup
|
225
|
-
self.portfoliodfrenderer = DataFrameHtmlRenderer()
|
226
|
-
self.positiondfrenderer = DataFrameHtmlRenderer(hide_columns=["positionId", "lastcolor","bidcolor","askcolor",f"{period}_barcolor"], \
|
227
|
-
color_column_mapping={"bid": "bidcolor",
|
228
|
-
"last": "lastcolor",
|
229
|
-
"ask": "askcolor",
|
230
|
-
f"{period}_cb": f"{period}_barcolor"
|
231
|
-
})
|
232
|
-
self.orderdfrenderer = DataFrameHtmlRenderer()
|
233
|
-
self.signaldfrenderer = DataFrameHtmlRenderer(hide_columns=["1d_barcolor","1h_barcolor","15m_barcolor","5m_barcolor","1m_barcolor","lastcolor","bidcolor","askcolor"], \
|
234
|
-
color_column_mapping={"bid": "bidcolor",
|
235
|
-
"last": "lastcolor",
|
236
|
-
"ask": "askcolor",
|
237
|
-
"1d_cb": "1d_barcolor",
|
238
|
-
"1h_cb": "1h_barcolor",
|
239
|
-
"15m_cb": "15m_barcolor",
|
240
|
-
"5m_cb": "5m_barcolor",
|
241
|
-
"1m_cb": "1m_barcolor"
|
242
|
-
})
|
243
|
-
|
244
|
-
#html rendering setup
|
245
|
-
self.allsignaldfrenderer = DataFrameHtmlRenderer(hide_columns=["1d_barcolor","1h_barcolor","15m_barcolor","5m_barcolor","1m_barcolor","lastcolor","bidcolor","askcolor"], \
|
246
|
-
color_column_mapping={"bid": "bidcolor",
|
247
|
-
"last": "lastcolor",
|
248
|
-
"ask": "askcolor",
|
249
|
-
"1d_cb": "1d_barcolor",
|
250
|
-
"1h_cb": "1h_barcolor",
|
251
|
-
"15m_cb": "15m_barcolor",
|
252
|
-
"5m_cb": "5m_barcolor",
|
253
|
-
"1m_cb": "1m_barcolor"
|
254
|
-
})
|
255
|
-
self.positionHistorydfrenderer = DataFrameHtmlRenderer()
|
256
|
-
|
257
|
-
|
258
|
-
###########################################################################################################
|
259
|
-
#load kline history
|
260
|
-
async def LoadKlineHistory(self):
|
261
|
-
start = time.time()
|
262
|
-
intervals = self.tickerObjects.get_intervalIds()
|
263
|
-
for ticker in self.tickerList:
|
264
|
-
for intervalId in intervals:
|
265
|
-
data = await self.bitunixApi.GetKlineHistory(ticker, intervalId, self.settings.BARS)
|
266
|
-
if data is not None:
|
267
|
-
self.tickerObjects.load_kline_history(ticker, intervalId, self.settings.BARS, data)
|
268
|
-
if self.settings.VERBOSE_LOGGING:
|
269
|
-
logger.info(f"kline_history: elapsed time {time.time()-start}")
|
270
|
-
self.notifications.add_notification("Kline history loaded")
|
271
|
-
|
272
|
-
#api data
|
273
|
-
async def GetTickerData(self):
|
274
|
-
start=time.time()
|
275
|
-
|
276
|
-
# Get the current time and set the seconds and microseconds to zero
|
277
|
-
current_time = datetime.now()
|
278
|
-
current_minute = current_time.replace(second=0, microsecond=0)
|
279
|
-
ts = int(current_minute.timestamp())*1000
|
280
|
-
|
281
|
-
#api used insted of websocket
|
282
|
-
data = await self.bitunixApi.GetTickerData()
|
283
|
-
self.tickerdf = pd.DataFrame()
|
284
|
-
if data:
|
285
|
-
|
286
|
-
# Create a DataFrame from the data
|
287
|
-
self.tickerdf = pd.DataFrame(data, columns=["symbol", "last"])
|
288
|
-
|
289
|
-
#remove not required symbols
|
290
|
-
self.tickerdf.loc[~self.tickerdf['symbol'].isin(self.tickerObjects.symbols()), :] = None
|
291
|
-
self.tickerdf.dropna(inplace=True)
|
292
|
-
|
293
|
-
self.tickerdf['ts']=ts
|
294
|
-
self.tickerdf["tickerObj"] = self.tickerdf["symbol"].map(self.tickerObjects.get_tickerDict())
|
295
|
-
self.tuples_list = list(zip(self.tickerdf["tickerObj"], self.tickerdf["last"].astype(float), self.tickerdf["ts"]))
|
296
|
-
self.tickerObjects.form_candle(self.tuples_list)
|
297
|
-
|
298
|
-
self.lastTickerDataTime = time.time()
|
299
|
-
if self.settings.VERBOSE_LOGGING:
|
300
|
-
logger.info(f"GetTickerData: elapsed time {time.time()-start}")
|
301
|
-
|
302
|
-
|
303
|
-
#websocket data
|
304
|
-
async def UpdateTickerData(self, message):
|
305
|
-
if message=="":
|
306
|
-
return
|
307
|
-
try:
|
308
|
-
data = json.loads(message)
|
309
|
-
if 'symbol' in data and data['ch'] in ['ticker']:
|
310
|
-
symbol = data['symbol']
|
311
|
-
ts = data['ts']
|
312
|
-
last= float(data['data']['la'])
|
313
|
-
highest= float(data['data']['h'])
|
314
|
-
lowest= float(data['data']['l'])
|
315
|
-
volume= float(data['data']['b'])
|
316
|
-
volumeInCurrency= float(data['data']['q'])
|
317
|
-
tickerObj = self.tickerObjects.get(symbol)
|
318
|
-
if tickerObj:
|
319
|
-
tickerObj.set_24hrData(highest,lowest,volume,volumeInCurrency)
|
320
|
-
tickerObj.form_candle(last, ts)
|
321
|
-
del tickerObj
|
322
|
-
gc.collect()
|
323
|
-
del data
|
324
|
-
gc.collect()
|
325
|
-
except Exception as e:
|
326
|
-
logger.info(f"Function: UpdateTickerData, {e}, {e.args}, {type(e).__name__}")
|
327
|
-
if self.settings.VERBOSE_LOGGING:
|
328
|
-
logger.info(f"Function: UpdateTickerData, time:{ts}, symbol:{symbol}, highest:{highest}, lowest:{lowest}, volume:{volume}, volumeInCurrency:{volumeInCurrency}")
|
329
|
-
|
330
|
-
#websocket data
|
331
|
-
async def UpdateDepthData(self, message):
|
332
|
-
if message=="":
|
333
|
-
return
|
334
|
-
try:
|
335
|
-
data = json.loads(message)
|
336
|
-
if 'symbol' in data and data['ch'] in ['depth_book1']:
|
337
|
-
symbol = data['symbol']
|
338
|
-
ts = data['ts']
|
339
|
-
bid = float(data['data']['b'][0][0])
|
340
|
-
ask = float(data['data']['a'][0][0])
|
341
|
-
tickerObj = self.tickerObjects.get(symbol)
|
342
|
-
if tickerObj:
|
343
|
-
tickerObj.set_bid(bid)
|
344
|
-
tickerObj.set_ask(ask)
|
345
|
-
del tickerObj
|
346
|
-
gc.collect()
|
347
|
-
del data
|
348
|
-
gc.collect()
|
349
|
-
except Exception as e:
|
350
|
-
logger.info(f"Function: UpdateDepthData, {e}, {e.args}, {type(e).__name__}")
|
351
|
-
if self.settings.VERBOSE_LOGGING:
|
352
|
-
logger.info(f"Function: UpdateDepthData, time:{ts}, symbol:{symbol}, bid:{bid}, ask:{ask}")
|
353
|
-
|
354
|
-
# this is called to update last price, as the websocket is lagging
|
355
|
-
# this is only called for the tickers in the pendingpositions
|
356
|
-
# and for first few records in the signaldf
|
357
|
-
async def apply_last_data(self, symbols):
|
358
|
-
start=time.time()
|
359
|
-
try:
|
360
|
-
# Get the current time and set the seconds and microseconds to zero
|
361
|
-
current_time = datetime.now()
|
362
|
-
current_minute = current_time.replace(second=0, microsecond=0)
|
363
|
-
ts = int(current_minute.timestamp())*1000
|
364
|
-
|
365
|
-
data= await self.bitunixApi.GetTickerslastPrice(symbols)
|
366
|
-
if data is None:
|
367
|
-
return
|
368
|
-
tickerdf = pd.DataFrame(data, columns=["symbol", "markPrice", "lastPrice", "open", "last", "quoteVol", "baseVol", "high", "low"])
|
369
|
-
tickerdf['ts']=ts
|
370
|
-
tickerdf["tickerObj"] = tickerdf["symbol"].map(self.tickerObjects.get_tickerDict())
|
371
|
-
tuples_list = list(zip(tickerdf["tickerObj"], tickerdf["last"].astype(float), tickerdf["ts"]))
|
372
|
-
self.tickerObjects.form_candle(tuples_list)
|
373
|
-
del data, tickerdf, tuples_list
|
374
|
-
gc.collect()
|
375
|
-
except Exception as e:
|
376
|
-
logger.info(e)
|
377
|
-
if self.settings.VERBOSE_LOGGING:
|
378
|
-
logger.info(f"apply_last_data: elapsed time {time.time()-start}")
|
379
|
-
|
380
|
-
# this is called to update bid and ask,
|
381
|
-
# as it is time consuming to call the api for each ticker,
|
382
|
-
# this is only called for the tickers in the pendingpositions
|
383
|
-
# and for first few records in the signaldf
|
384
|
-
async def apply_depth_data(self, ticker):
|
385
|
-
ddata = await self.bitunixApi.GetDepthData(ticker,"1")
|
386
|
-
if ddata is not None and 'bids' in ddata:
|
387
|
-
bid = float(ddata['bids'][0][0])
|
388
|
-
ask = float(ddata['asks'][0][0])
|
389
|
-
tickerObj=self.tickerObjects.get(ticker)
|
390
|
-
if tickerObj:
|
391
|
-
tickerObj.set_bid(bid)
|
392
|
-
tickerObj.set_ask(ask)
|
393
|
-
del tickerObj
|
394
|
-
gc.collect()
|
395
|
-
await asyncio.sleep(0)
|
396
|
-
del ddata
|
397
|
-
gc.collect()
|
398
|
-
|
399
|
-
###########################################################################################################
|
400
|
-
|
401
|
-
async def GetportfolioData(self):
|
402
|
-
start=time.time()
|
403
|
-
try:
|
404
|
-
self.portfolioData = await self.bitunixApi.GetportfolioData()
|
405
|
-
if self.portfolioData:
|
406
|
-
self.portfoliodf=pd.DataFrame(self.portfolioData,index=[0])[["marginCoin","available","margin","crossUnrealizedPNL"]]
|
407
|
-
else:
|
408
|
-
self.portfolioData = pd.DataFrame()
|
409
|
-
self.portfoliodfStyle= self.portfoliodfrenderer.render_html(self.portfoliodf)
|
410
|
-
|
411
|
-
except Exception as e:
|
412
|
-
logger.info(f"Function: GetportfolioData, {e}, {e.args}, {type(e).__name__}")
|
413
|
-
if self.settings.VERBOSE_LOGGING:
|
414
|
-
logger.info(f"GetportfolioData: elapsed time {time.time()-start}")
|
415
|
-
|
416
|
-
async def GetPendingPositionData(self):
|
417
|
-
start=time.time()
|
418
|
-
try:
|
419
|
-
self.pendingPositions = await self.bitunixApi.GetPendingPositionData()
|
420
|
-
if self.pendingPositions:
|
421
|
-
self.positiondf = pd.DataFrame(self.pendingPositions, columns=["positionId", "symbol", "side", "unrealizedPNL", "realizedPNL", "qty", "ctime", "avgOpenPrice"])
|
422
|
-
self.positiondf['bid']=0.0
|
423
|
-
self.positiondf['bidcolor']=""
|
424
|
-
self.positiondf['last']=0.0
|
425
|
-
self.positiondf['lastcolor']=""
|
426
|
-
self.positiondf['ask']=0.0
|
427
|
-
self.positiondf['askcolor']=""
|
428
|
-
self.positiondf['ctime']=pd.to_datetime(self.positiondf['ctime'].astype(float), unit='ms').dt.tz_localize('UTC').dt.tz_convert(cst).dt.strftime('%Y-%m-%d %H:%M:%S')
|
429
|
-
|
430
|
-
try:
|
431
|
-
self.positiondf = self.positiondf.assign(
|
432
|
-
bid=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).get_bid()),
|
433
|
-
bidcolor=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).bidcolor),
|
434
|
-
last=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).get_last()),
|
435
|
-
lastcolor=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).lastcolor),
|
436
|
-
ask=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).get_ask()),
|
437
|
-
askcolor=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).askcolor)
|
438
|
-
)
|
439
|
-
#self.positiondf['roi']= round((self.positiondf['last'].astype(float) * self.positiondf['qty'].astype(float) - \
|
440
|
-
# self.positiondf['avgOpenPrice'].astype(float) * self.positiondf['qty'].astype(float)) / \
|
441
|
-
# (self.positiondf['avgOpenPrice'].astype(float) * self.positiondf['qty'].astype(float) / (self.settings.LEVERAGE/100)) * 10000 , 2)
|
442
|
-
except Exception as e:
|
443
|
-
pass
|
444
|
-
self.positiondf['charts'] = self.positiondf.apply(self.add_charts_button, axis=1)
|
445
|
-
self.positiondf['bitunix'] = self.positiondf.apply(self.add_bitunix_button, axis=1)
|
446
|
-
self.positiondf['action'] = self.positiondf.apply(self.add_close_button, axis=1)
|
447
|
-
self.positiondf['add'] = self.positiondf.apply(self.add_add_button, axis=1)
|
448
|
-
self.positiondf['reduce'] = self.positiondf.apply(self.add_reduce_button, axis=1)
|
449
|
-
|
450
|
-
self.positiondf['bid'] = self.positiondf['bid'].astype('float64')
|
451
|
-
self.positiondf['last'] = self.positiondf['last'].astype('float64')
|
452
|
-
self.positiondf['ask'] = self.positiondf['ask'].astype('float64')
|
453
|
-
else:
|
454
|
-
self.positiondf = pd.DataFrame()
|
455
|
-
#self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf)
|
456
|
-
|
457
|
-
#if not self.settings.USE_PUBLIC_WEBSOCKET:
|
458
|
-
#get bid las ask using api for the symbols in pending psotion
|
459
|
-
if not self.positiondf.empty:
|
460
|
-
if self.settings.USE_PUBLIC_WEBSOCKET:
|
461
|
-
await asyncio.create_task(self.apply_last_data(','.join(self.positiondf['symbol'].astype(str).tolist())))
|
462
|
-
await asyncio.gather(
|
463
|
-
*[
|
464
|
-
asyncio.create_task(self.apply_depth_data(row['symbol']))
|
465
|
-
for index, row in self.positiondf.iterrows()
|
466
|
-
]
|
467
|
-
)
|
468
|
-
|
469
|
-
|
470
|
-
except Exception as e:
|
471
|
-
logger.info(f"Function: GetPendingPositionData, {e}, {e.args}, {type(e).__name__}")
|
472
|
-
if self.settings.VERBOSE_LOGGING:
|
473
|
-
logger.info(f"GetPendingPositionData: elapsed time {time.time()-start}")
|
474
|
-
|
475
|
-
async def GetPendingOrderData(self):
|
476
|
-
start=time.time()
|
477
|
-
try:
|
478
|
-
self.pendingOrders = await self.bitunixApi.GetPendingOrderData()
|
479
|
-
if self.pendingOrders and 'orderList' in self.pendingOrders:
|
480
|
-
self.orderdf = pd.DataFrame(self.pendingOrders['orderList'], columns=["orderId", "symbol", "qty", "side", "price", "ctime", "status", "reduceOnly"])
|
481
|
-
self.orderdf['rtime']=pd.to_datetime(self.orderdf['ctime'].astype(float), unit='ms').dt.tz_localize('UTC').dt.tz_convert(cst).dt.strftime('%Y-%m-%d %H:%M:%S')
|
482
|
-
self.orderdf['charts'] = self.orderdf.apply(self.add_charts_button, axis=1)
|
483
|
-
self.orderdf['bitunix'] = self.orderdf.apply(self.add_bitunix_button, axis=1)
|
484
|
-
self.orderdf['action'] = self.orderdf.apply(self.add_order_close_button, axis=1)
|
485
|
-
else:
|
486
|
-
self.orderdf = pd.DataFrame()
|
487
|
-
self.orderdfStyle= self.orderdfrenderer.render_html(self.orderdf)
|
488
|
-
|
489
|
-
except Exception as e:
|
490
|
-
logger.info(f"Function: GetPendingOrderData, {e}, {e.args}, {type(e).__name__}")
|
491
|
-
if self.settings.VERBOSE_LOGGING:
|
492
|
-
logger.info(f"GetPendingOrderData: elapsed time {time.time()-start}")
|
493
|
-
|
494
|
-
async def GetPositionHistoryData(self):
|
495
|
-
start=time.time()
|
496
|
-
try:
|
497
|
-
self.positionHistoryData = await self.bitunixApi.GetPositionHistoryData()
|
498
|
-
if self.positionHistoryData and 'positionList' in self.positionHistoryData:
|
499
|
-
self.positionHistorydf = pd.DataFrame(self.positionHistoryData['positionList'], columns=["symbol", "side","realizedPNL", "ctime", "maxQty", "closePrice","fee", "funding"])
|
500
|
-
self.positionHistorydf['ctime'] = pd.to_datetime(self.positionHistorydf['ctime'].astype(float), unit='ms').dt.tz_localize('UTC').dt.tz_convert(cst).dt.strftime('%Y-%m-%d %H:%M:%S')
|
501
|
-
self.positionHistorydf['charts'] = self.positionHistorydf.apply(self.add_charts_button, axis=1)
|
502
|
-
self.positionHistorydf['bitunix'] = self.positionHistorydf.apply(self.add_bitunix_button, axis=1)
|
503
|
-
|
504
|
-
else:
|
505
|
-
self.positionHistorydf = pd.DataFrame()
|
506
|
-
self.positionHistorydfStyle= self.positionHistorydfrenderer.render_html(self.positionHistorydf)
|
507
|
-
|
508
|
-
except Exception as e:
|
509
|
-
logger.info(f"Function: GetPositionHistoryData, {e}, {e.args}, {type(e).__name__}")
|
510
|
-
if self.settings.VERBOSE_LOGGING:
|
511
|
-
logger.info(f"GetPositionHistoryData: elapsed time {time.time()-start}")
|
512
|
-
|
513
|
-
async def GetTradeHistoryData(self):
|
514
|
-
start=time.time()
|
515
|
-
try:
|
516
|
-
self.tradeHistoryData = await self.bitunixApi.GetTradeHistoryData()
|
517
|
-
if self.tradeHistoryData and 'tradeList' in self.tradeHistoryData:
|
518
|
-
self.tradesdf = pd.DataFrame(self.tradeHistoryData['tradeList'], columns=["symbol", "ctime", "qty", "side", "price","realizedPNL","reduceOnly"])
|
519
|
-
self.tradesdf['rtime'] = pd.to_datetime(self.tradesdf['ctime'].astype(float), unit='ms').dt.tz_localize('UTC').dt.tz_convert(cst).dt.strftime('%Y-%m-%d %H:%M:%S')
|
520
|
-
grouped_trades = self.tradesdf.groupby("symbol")
|
521
|
-
for symbol, tickerObj in self.tickerObjects.get_tickerDict().items():
|
522
|
-
if symbol in grouped_trades.groups:
|
523
|
-
# Filter trades for the current symbol and convert them to a list of dicts
|
524
|
-
tickerObj.trades = grouped_trades.get_group(symbol).to_dict("records")
|
525
|
-
except Exception as e:
|
526
|
-
logger.info(f"Function: GetTradeHistoryData, {e}, {e.args}, {type(e).__name__}")
|
527
|
-
if self.settings.VERBOSE_LOGGING:
|
528
|
-
logger.info(f"GetTradeHistoryData: elapsed time {time.time()-start}")
|
529
|
-
|
530
|
-
|
531
|
-
###########################################################################################################
|
532
|
-
|
533
|
-
async def BuySellList(self, period):
|
534
|
-
try:
|
535
|
-
#ticker in positions and orders
|
536
|
-
inuse1=[]
|
537
|
-
inuse2=[]
|
538
|
-
if not self.positiondf.empty:
|
539
|
-
inuse1 = self.positiondf['symbol'].to_list()
|
540
|
-
if not self.orderdf.empty:
|
541
|
-
inuse2 = self.orderdf['symbol'].to_list()
|
542
|
-
inuseTickers = set(inuse1 + inuse2)
|
543
|
-
|
544
|
-
# Extract buy/sell ticker data
|
545
|
-
self.tickerObjects.getCurrentData(period)
|
546
|
-
|
547
|
-
self.signaldf_full = self.tickerObjects.signaldf_full
|
548
|
-
|
549
|
-
if self.signaldf_full.empty:
|
550
|
-
self.positiondf2 = self.positiondf
|
551
|
-
self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf)
|
552
|
-
return
|
553
|
-
|
554
|
-
self.signaldf_full['charts'] = self.signaldf_full.apply(self.add_charts_button, axis=1)
|
555
|
-
self.signaldf_full['bitunix'] = self.signaldf_full.apply(self.add_bitunix_button, axis=1)
|
556
|
-
|
557
|
-
|
558
|
-
self.allsignaldfStyle= self.allsignaldfrenderer.render_html(self.signaldf_full)
|
559
|
-
|
560
|
-
self.signaldf_filtered = self.tickerObjects.signaldf_filtered
|
561
|
-
|
562
|
-
if not self.positiondf.empty and not self.signaldf_full.empty:
|
563
|
-
columns=['symbol', f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema", 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"]
|
564
|
-
columns2=["qty", "side", "unrealizedPNL", "realizedPNL", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
|
565
|
-
if set(columns).issubset(self.signaldf_full.columns) and set(columns2).issubset(self.positiondf.columns):
|
566
|
-
columnOrder= ['symbol', "side", "unrealizedPNL", "realizedPNL", f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema", 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"]
|
567
|
-
self.positiondf2 = pd.merge(self.positiondf, self.signaldf_full[["symbol", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
|
568
|
-
f"{period}_ema", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_adx", f"{period}_candle_trend",
|
569
|
-
f"{period}_trend",f"{period}_cb", f"{period}_barcolor"]], left_on="symbol", right_index=True, how="left")[columnOrder]
|
570
|
-
self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf2)
|
571
|
-
else:
|
572
|
-
self.positiondf2 = pd.DataFrame()
|
573
|
-
|
574
|
-
if not self.signaldf_filtered.empty:
|
575
|
-
#remove those that are in positon and orders
|
576
|
-
self.signaldf_filtered = self.signaldf_filtered[~(self.signaldf_filtered['symbol'].isin(inuseTickers))]
|
577
|
-
|
578
|
-
if not self.signaldf_filtered.empty:
|
579
|
-
# Assign to self.signaldf for HTML rendering
|
580
|
-
self.signaldf = self.signaldf_filtered[[
|
581
|
-
"symbol", f"{period}_trend",f"{period}_cb", f"{period}_barcolor",
|
582
|
-
f"{period}_ema", f"{period}_macd", f"{period}_bbm", f"{period}_rsi",f"{period}_adx",f"{period}_candle_trend",
|
583
|
-
'lastcolor', 'bidcolor', 'askcolor', 'bid', 'ask', 'last',
|
584
|
-
f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
|
585
|
-
]].sort_values(by=[f'{period}_cb'], ascending=[False])
|
586
|
-
|
587
|
-
# Add buttons
|
588
|
-
self.signaldf['charts'] = self.signaldf.apply(self.add_charts_button, axis=1)
|
589
|
-
self.signaldf['bitunix'] = self.signaldf.apply(self.add_bitunix_button, axis=1)
|
590
|
-
self.signaldf['buy'] = self.signaldf.apply(self.add_buy_button, axis=1)
|
591
|
-
self.signaldf['sell'] = self.signaldf.apply(self.add_sell_button, axis=1)
|
592
|
-
else:
|
593
|
-
self.signaldf = pd.DataFrame()
|
594
|
-
self.signaldfStyle= self.signaldfrenderer.render_html(self.signaldf)
|
595
|
-
|
596
|
-
#if not self.settings.USE_PUBLIC_WEBSOCKET:
|
597
|
-
#get bid las ask using api for max_auto_trades rows
|
598
|
-
if not self.signaldf.empty:
|
599
|
-
m = min(self.signaldf.shape[0], int(self.settings.MAX_AUTO_TRADES))
|
600
|
-
if self.settings.USE_PUBLIC_WEBSOCKET:
|
601
|
-
await asyncio.create_task(self.apply_last_data(','.join(self.signaldf['symbol'][:m].astype(str).tolist())))
|
602
|
-
await asyncio.gather(
|
603
|
-
*[
|
604
|
-
asyncio.create_task(self.apply_depth_data(row['symbol']))
|
605
|
-
for index, row in self.signaldf[:m].iterrows()
|
606
|
-
]
|
607
|
-
)
|
608
|
-
|
609
|
-
except Exception as e:
|
610
|
-
logger.info(f"Function: BuySellList, {e}, {e.args}, {type(e).__name__}")
|
611
|
-
logger.info(traceback.print_exc())
|
612
|
-
del inuse1, inuse2, inuseTickers
|
613
|
-
gc.collect()
|
614
|
-
|
615
|
-
async def AutoTradeProcess(self):
|
616
|
-
if self.settings.VERBOSE_LOGGING:
|
617
|
-
logger.info(f"AutoTradeProcess started")
|
618
|
-
start=time.time()
|
619
|
-
|
620
|
-
period = self.settings.OPTION_MOVING_AVERAGE
|
621
|
-
try:
|
622
|
-
|
623
|
-
#calulate current data at selected period, this create signaldf
|
624
|
-
await self.BuySellList(period)
|
625
|
-
|
626
|
-
if not self.settings.AUTOTRADE:
|
627
|
-
return
|
628
|
-
##############################################################################################################################
|
629
|
-
# open long or short postition
|
630
|
-
##############################################################################################################################
|
631
|
-
count=0
|
632
|
-
self.pendingPositions = await self.bitunixApi.GetPendingPositionData({})
|
633
|
-
if self.pendingPositions:
|
634
|
-
count=count+len(self.pendingPositions)
|
635
|
-
self.pendingOrders = await self.bitunixApi.GetPendingOrderData({})
|
636
|
-
if self.pendingOrders:
|
637
|
-
count=count+len(self.pendingOrders['orderList'])
|
638
|
-
|
639
|
-
if count < int(self.settings.MAX_AUTO_TRADES):
|
640
|
-
if not self.signaldf.empty:
|
641
|
-
#open position upto a max of max_auto_trades from the signal list
|
642
|
-
df=self.signaldf.copy(deep=False)
|
643
|
-
for index, row in df.iterrows():
|
644
|
-
side = "BUY" if row[f'{period}_barcolor'] == self.green else "SELL" if row[f'{period}_barcolor'] == self.red else ""
|
645
|
-
if side != "":
|
646
|
-
select = True
|
647
|
-
self.pendingPositions = await self.bitunixApi.GetPendingPositionData({'symbol': row.symbol})
|
648
|
-
if self.pendingPositions and len(self.pendingPositions) == 1:
|
649
|
-
select = False
|
650
|
-
self.pendingOrders = await self.bitunixApi.GetPendingOrderData({'symbol': row.symbol})
|
651
|
-
if self.pendingOrders and len(self.pendingOrders['orderList']) == 1:
|
652
|
-
select = False
|
653
|
-
if select:
|
654
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
655
|
-
price = (bid if side == "BUY" else ask if side == "SELL" else last) if bid<=last<=ask else last
|
656
|
-
balance = float(self.portfoliodf["available"].iloc[0]) + float(self.portfoliodf["crossUnrealizedPNL"].iloc[0])
|
657
|
-
qty= str(max(balance * (float(self.settings.ORDER_AMOUNT_PERCENTAGE) / 100) / price * int(self.settings.LEVERAGE),mtv))
|
658
|
-
|
659
|
-
self.notifications.add_notification(
|
660
|
-
f'{colors.YELLOW} Opening {"long" if side=="BUY" else "short"} position for {row.symbol} with {qty} qty @ {price})'
|
661
|
-
)
|
662
|
-
datajs = await self.bitunixApi.PlaceOrder(row.symbol, qty, price, side)
|
663
|
-
count=count+1
|
664
|
-
if count >= int(self.settings.MAX_AUTO_TRADES):
|
665
|
-
break
|
666
|
-
await asyncio.sleep(0)
|
667
|
-
del df
|
668
|
-
gc.collect()
|
669
|
-
|
670
|
-
##############################################################################################################################
|
671
|
-
# close long or short postition
|
672
|
-
##############################################################################################################################
|
673
|
-
|
674
|
-
# Close orders that are open for a while
|
675
|
-
current_time = time.time() * 1000
|
676
|
-
df=self.orderdf.copy(deep=False)
|
677
|
-
for index, row in df.iterrows():
|
678
|
-
if current_time - int(row.ctime) > 60000:
|
679
|
-
self.notifications.add_notification(
|
680
|
-
f'{colors.LBLUE} Canceling order {row.orderId}, {row.symbol} {row.qty} created at {row.rtime} '
|
681
|
-
)
|
682
|
-
datajs = await self.bitunixApi.CancelOrder(row.symbol, row.orderId)
|
683
|
-
await asyncio.sleep(0)
|
684
|
-
|
685
|
-
if not self.positiondf.empty:
|
686
|
-
df=self.positiondf.copy(deep=False)
|
687
|
-
for index, row in df.iterrows():
|
688
|
-
unrealized_pnl = float(row.unrealizedPNL)
|
689
|
-
realized_pnl = float(row.realizedPNL)
|
690
|
-
total_pnl = unrealized_pnl + realized_pnl
|
691
|
-
side=row['side']
|
692
|
-
|
693
|
-
requiredCols=[f'{period}_open', f'{period}_close', f'{period}_high', f'{period}_low', f'{period}_ema', f'{period}_macd', f'{period}_bbm', f'{period}_rsi', f'{period}_candle_trend', f'{period}_trend', f'{period}_cb', f'{period}_barcolor']
|
694
|
-
required_cols = set(requiredCols)
|
695
|
-
|
696
|
-
# Close position that fall the below criteria
|
697
|
-
if not self.signaldf_full.columns.empty and self.signaldf_full['symbol'].isin([row.symbol]).any() and required_cols.issubset(set(self.signaldf_full.columns)):
|
698
|
-
|
699
|
-
# Check orders
|
700
|
-
select = True
|
701
|
-
self.pendingOrders = await self.bitunixApi.GetPendingOrderData({'symbol': row.symbol})
|
702
|
-
if self.pendingOrders and len(self.pendingOrders['orderList']) == 1:
|
703
|
-
select = False
|
704
|
-
|
705
|
-
if select and int(self.settings.MAX_AUTO_TRADES)!=0:
|
706
|
-
|
707
|
-
# check take portit or accept loss
|
708
|
-
if float(self.settings.LOSS_AMOUNT) > 0 and total_pnl < -float(self.settings.LOSS_AMOUNT):
|
709
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
710
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
711
|
-
|
712
|
-
self.notifications.add_notification(
|
713
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to stop loss for {row.symbol} with {row.qty} qty @ {price})'
|
714
|
-
)
|
715
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
716
|
-
positionId=row.positionId,
|
717
|
-
ticker=row.symbol,
|
718
|
-
qty=row.qty,
|
719
|
-
price=price,
|
720
|
-
side=row.side,
|
721
|
-
tradeSide="CLOSE"
|
722
|
-
)
|
723
|
-
continue
|
724
|
-
|
725
|
-
if float(self.settings.PROFIT_AMOUNT) > 0 and total_pnl > float(self.settings.PROFIT_AMOUNT):
|
726
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
727
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
728
|
-
|
729
|
-
self.notifications.add_notification(
|
730
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to take profit for {row.symbol} with {row.qty} qty @ {price})'
|
731
|
-
)
|
732
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
733
|
-
positionId=row.positionId,
|
734
|
-
ticker=row.symbol,
|
735
|
-
qty=row.qty,
|
736
|
-
price=price,
|
737
|
-
side=row.side,
|
738
|
-
tradeSide="CLOSE"
|
739
|
-
)
|
740
|
-
continue
|
741
|
-
|
742
|
-
# Moving average comparison between fast and medium
|
743
|
-
if self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_CLOSE:
|
744
|
-
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_ema'] == "SELL":
|
745
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
746
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
747
|
-
|
748
|
-
self.notifications.add_notification(
|
749
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to MA {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
750
|
-
)
|
751
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
752
|
-
positionId=row.positionId,
|
753
|
-
ticker=row.symbol,
|
754
|
-
qty=row.qty,
|
755
|
-
price=price,
|
756
|
-
side=row.side,
|
757
|
-
tradeSide="CLOSE"
|
758
|
-
)
|
759
|
-
continue
|
760
|
-
|
761
|
-
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_ema'] == "BUY":
|
762
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
763
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
764
|
-
self.notifications.add_notification(
|
765
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to MA {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
766
|
-
)
|
767
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
768
|
-
positionId=row.positionId,
|
769
|
-
ticker=row.symbol,
|
770
|
-
qty=row.qty,
|
771
|
-
price=price,
|
772
|
-
side=row.side,
|
773
|
-
tradeSide="CLOSE"
|
774
|
-
)
|
775
|
-
continue
|
776
|
-
|
777
|
-
# MACD comparison between MACD and Signal
|
778
|
-
if self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_CLOSE:
|
779
|
-
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_macd'] == "SELL":
|
780
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
781
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
782
|
-
self.notifications.add_notification(
|
783
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to MACD {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
784
|
-
)
|
785
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
786
|
-
positionId=row.positionId,
|
787
|
-
ticker=row.symbol,
|
788
|
-
qty=row.qty,
|
789
|
-
price=price,
|
790
|
-
side=row.side,
|
791
|
-
tradeSide="CLOSE"
|
792
|
-
)
|
793
|
-
continue
|
794
|
-
|
795
|
-
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_macd'] == "BUY":
|
796
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
797
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
798
|
-
self.notifications.add_notification(
|
799
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to MACD {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
800
|
-
)
|
801
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
802
|
-
positionId=row.positionId,
|
803
|
-
ticker=row.symbol,
|
804
|
-
qty=row.qty,
|
805
|
-
price=price,
|
806
|
-
side=row.side,
|
807
|
-
tradeSide="CLOSE"
|
808
|
-
)
|
809
|
-
continue
|
810
|
-
|
811
|
-
# Bollinger Band comparison between open and BBM
|
812
|
-
if self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_CLOSE:
|
813
|
-
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_bbm'] == "SELL":
|
814
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
815
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
816
|
-
self.notifications.add_notification(
|
817
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to BBM {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
818
|
-
)
|
819
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
820
|
-
positionId=row.positionId,
|
821
|
-
ticker=row.symbol,
|
822
|
-
qty=row.qty,
|
823
|
-
price=price,
|
824
|
-
side=row.side,
|
825
|
-
tradeSide="CLOSE"
|
826
|
-
)
|
827
|
-
continue
|
828
|
-
|
829
|
-
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_bbm'] == "BUY":
|
830
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
831
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
832
|
-
self.notifications.add_notification(
|
833
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to BBM {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
834
|
-
)
|
835
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
836
|
-
positionId=row.positionId,
|
837
|
-
ticker=row.symbol,
|
838
|
-
qty=row.qty,
|
839
|
-
price=price,
|
840
|
-
side=row.side,
|
841
|
-
tradeSide="CLOSE"
|
842
|
-
)
|
843
|
-
continue
|
844
|
-
|
845
|
-
# RSI comparison
|
846
|
-
if self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_CLOSE:
|
847
|
-
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_rsi'] == "SELL":
|
848
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
849
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
850
|
-
self.notifications.add_notification(
|
851
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to RSI {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
852
|
-
)
|
853
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
854
|
-
positionId=row.positionId,
|
855
|
-
ticker=row.symbol,
|
856
|
-
qty=row.qty,
|
857
|
-
price=price,
|
858
|
-
side=row.side,
|
859
|
-
tradeSide="CLOSE"
|
860
|
-
)
|
861
|
-
continue
|
862
|
-
|
863
|
-
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_rsi'] == "BUY":
|
864
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
865
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
866
|
-
self.notifications.add_notification(
|
867
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to RSI {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
868
|
-
)
|
869
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
870
|
-
positionId=row.positionId,
|
871
|
-
ticker=row.symbol,
|
872
|
-
qty=row.qty,
|
873
|
-
price=price,
|
874
|
-
side=row.side,
|
875
|
-
tradeSide="CLOSE"
|
876
|
-
)
|
877
|
-
continue
|
878
|
-
|
879
|
-
# Close on weak trend after open
|
880
|
-
if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_CLOSE:
|
881
|
-
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_adx'] == "WEAK":
|
882
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
883
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
884
|
-
self.notifications.add_notification(
|
885
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to WEAK ADX 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}_adx'] == "WEAK":
|
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 WEAK ADX 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
|
-
|
913
|
-
# candle reversed
|
914
|
-
if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_CLOSE:
|
915
|
-
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'] == "BEARISH":
|
916
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
917
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
918
|
-
|
919
|
-
self.notifications.add_notification(
|
920
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to bearish candle reversal for {row.symbol} with {row.qty} qty @ {price})'
|
921
|
-
)
|
922
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
923
|
-
positionId=row.positionId,
|
924
|
-
ticker=row.symbol,
|
925
|
-
qty=row.qty,
|
926
|
-
price=price,
|
927
|
-
side=row.side,
|
928
|
-
tradeSide="CLOSE"
|
929
|
-
)
|
930
|
-
continue
|
931
|
-
|
932
|
-
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}_candle_trend'] == "BULLISH":
|
933
|
-
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
934
|
-
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
935
|
-
|
936
|
-
self.notifications.add_notification(
|
937
|
-
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to bullish candle reversal for {row.symbol} with {row.qty} qty @ {price})'
|
938
|
-
)
|
939
|
-
datajs = await self.bitunixApi.PlaceOrder(
|
940
|
-
positionId=row.positionId,
|
941
|
-
ticker=row.symbol,
|
942
|
-
qty=row.qty,
|
943
|
-
price=price,
|
944
|
-
side=row.side,
|
945
|
-
tradeSide="CLOSE"
|
946
|
-
)
|
947
|
-
continue
|
948
|
-
|
949
|
-
await asyncio.sleep(0)
|
950
|
-
|
951
|
-
self.lastAutoTradeTime = time.time()
|
952
|
-
except Exception as e:
|
953
|
-
stack = traceback.extract_stack()
|
954
|
-
function_name = stack[-1].name
|
955
|
-
logger.info(f"Function: {function_name}, {e}, {e.args}, {type(e).__name__}")
|
956
|
-
logger.info(traceback.print_exc())
|
957
|
-
|
958
|
-
if self.settings.VERBOSE_LOGGING:
|
959
|
-
logger.info(f"AutoTradeProcess: elapsed time {time.time()-start}")
|
960
|
-
|
961
|
-
del df
|
962
|
-
gc.collect()
|
963
|
-
|
964
|
-
|
965
|
-
async def GetTickerBidLastAsk(self, symbol):
|
966
|
-
tdata = await self.bitunixApi.GetTickerslastPrice(symbol)
|
967
|
-
if tdata:
|
968
|
-
last=float(tdata[0]['lastPrice'])
|
969
|
-
else:
|
970
|
-
last=0.0
|
971
|
-
ddata = await self.bitunixApi.GetDepthData(symbol,"1")
|
972
|
-
if tdata:
|
973
|
-
bid = float(ddata['bids'][0][0])
|
974
|
-
ask = float(ddata['asks'][0][0])
|
975
|
-
else:
|
976
|
-
bid=0.0
|
977
|
-
ask=0.0
|
978
|
-
pdata = await self.bitunixApi.GetTickersPair(symbol)
|
979
|
-
if pdata:
|
980
|
-
mtv=float(pdata[0]['minTradeVolume'])
|
981
|
-
else:
|
982
|
-
mtv=0.0
|
983
|
-
tickerObj = self.tickerObjects.get(symbol)
|
984
|
-
if tickerObj:
|
985
|
-
tickerObj.set_last(last)
|
986
|
-
tickerObj.set_bid(bid)
|
987
|
-
tickerObj.set_ask(ask)
|
988
|
-
tickerObj.set_mtv(mtv)
|
989
|
-
del tdata, ddata, pdata, tickerObj
|
990
|
-
gc.collect()
|
991
|
-
return last,bid,ask,mtv
|
992
|
-
|
993
|
-
async def ProcessPrivateData(self, message):
|
994
|
-
if message=="":
|
995
|
-
return
|
996
|
-
try:
|
997
|
-
feed = json.loads(message)
|
998
|
-
if 'ch' not in feed:
|
999
|
-
return
|
1000
|
-
|
1001
|
-
channel = feed['ch']
|
1002
|
-
data = feed['data']
|
1003
|
-
|
1004
|
-
if channel == 'order':
|
1005
|
-
|
1006
|
-
symbol = data['symbol']
|
1007
|
-
qty = data['qty']
|
1008
|
-
side = data['side']
|
1009
|
-
price = data['price']
|
1010
|
-
event = data['event']
|
1011
|
-
orderStatus = data['orderStatus']
|
1012
|
-
self.notifications.add_notification(
|
1013
|
-
f'{colors.LBLUE} {orderStatus} {"short" if side=="Sell" else "long"} order for {symbol} with {qty} qty (event: {event})'
|
1014
|
-
)
|
1015
|
-
|
1016
|
-
elif channel == 'balance':
|
1017
|
-
|
1018
|
-
self.available = data['available']
|
1019
|
-
self.margin = data['margin']
|
1020
|
-
# logger.info(feed)
|
1021
|
-
|
1022
|
-
elif channel == 'position':
|
1023
|
-
|
1024
|
-
ts = int(feed['ts'])
|
1025
|
-
symbol = data['symbol']
|
1026
|
-
qty = float(data['qty'])
|
1027
|
-
side = data['side']
|
1028
|
-
positionId = data['positionId']
|
1029
|
-
event = data['event']
|
1030
|
-
entryValue = float(data['entryValue'])
|
1031
|
-
price = entryValue / qty if entryValue != 0 and qty != 0 else 0
|
1032
|
-
|
1033
|
-
if event == "OPEN":
|
1034
|
-
self.notifications.add_notification(
|
1035
|
-
f'{colors.PURPLE} Opened {side} position for {symbol} with {qty} qty @ {price}'
|
1036
|
-
)
|
1037
|
-
|
1038
|
-
elif event == "CLOSE":
|
1039
|
-
datajs = await self.bitunixApi.GetPositionHistoryData({'positionId': positionId})
|
1040
|
-
if datajs and len(datajs['positionList']) == 1:
|
1041
|
-
position = datajs['positionList'][0]
|
1042
|
-
profit = float(position['realizedPNL'])
|
1043
|
-
price = float(position['closePrice'])
|
1044
|
-
qty = float(position['maxQty'])
|
1045
|
-
self.profit += profit
|
1046
|
-
self.notifications.add_notification(
|
1047
|
-
f'{colors.GREEN if profit>0 else colors.RED} Closed {side} position for {symbol} with {qty} qty @ {price} and {"profit" if profit>0 else "loss"} of {profit}'
|
1048
|
-
)
|
1049
|
-
del datajs
|
1050
|
-
gc.collect()
|
1051
|
-
self.tickerObjects.get(symbol).trades.append({'ctime': ts, 'symbol': symbol, 'qty': qty, 'side': side, 'price': price})
|
1052
|
-
del feed
|
1053
|
-
gc.collect()
|
1054
|
-
except Exception as e:
|
1055
|
-
logger.info(f"Function: ProcessPrivateData, {e}, {e.args}, {type(e).__name__}")
|
1056
|
-
|
1057
|
-
def color_cells(val, color):
|
1058
|
-
return f'background-color: {color}' if val else ''
|
1059
|
-
|
1060
|
-
def add_charts_button(self, row):
|
1061
|
-
return f'<button onclick="handleChartsButton(\'{row["symbol"]}\')">charts</button>'
|
1062
|
-
|
1063
|
-
def add_bitunix_button(self, row):
|
1064
|
-
return f'<button onclick="handleBitunixButton(\'{row["symbol"]}\')">show</button>'
|
1065
|
-
|
1066
|
-
def add_buy_button(self, row):
|
1067
|
-
return f'<button onclick="handleBuyButton(\'{row["symbol"]}\',\'{row["last"]}\')">buy</button>'
|
1068
|
-
|
1069
|
-
def add_add_button(self, row):
|
1070
|
-
return f'<button onclick="handleAddButton(\'{row["symbol"]}\',\'{row["last"]}\')">add</button>'
|
1071
|
-
|
1072
|
-
def add_reduce_button(self, row):
|
1073
|
-
return f'<button onclick="handleReduceButton(\'{row["symbol"]}\',\'{row["positionId"]}\',\'{row["qty"]}\',\'{row["last"]}\')">reduce</button>'
|
1074
|
-
|
1075
|
-
def add_sell_button(self, row):
|
1076
|
-
return f'<button onclick="handleSellButton(\'{row["symbol"]}\',\'{row["last"]}\')">sell</button>'
|
1077
|
-
|
1078
|
-
def add_close_button(self, row):
|
1079
|
-
return f'<button onclick="handleCloseButton(\'{row["symbol"]}\',\'{row["positionId"]}\',\'{row["qty"]}\',\'{row["unrealizedPNL"]}\',\'{row["realizedPNL"]}\')">FlashClose</button>'
|
1080
|
-
|
1081
|
-
def add_order_close_button(self, row):
|
1082
|
-
return f'<button onclick="handleOrderCloseButton(\'{row["symbol"]}\',\'{row["orderId"]}\')">close</button>'
|
1083
|
-
|
1084
|
-
async def send_message_to_websocket(self,message):
|
1085
|
-
async def send_to_all():
|
1086
|
-
for ws in self.websocket_connections:
|
1087
|
-
await ws.send_text(message)
|
1088
|
-
asyncio.run(send_to_all())
|
1089
|
-
|
1090
|
-
async def get_portfolio_tradable_balance(self):
|
1091
|
-
return float(self.portfoliodf["available"])+float(self.portfoliodf["crossUnrealizedPNL"])
|
1092
|
-
|
1093
|
-
async def convert_defaultdict_to_dict(self, d):
|
1094
|
-
if isinstance(d, defaultdict):
|
1095
|
-
d = {k: self.convert_defaultdict_to_dict(v) for k, v in d.items()}
|
1096
|
-
return d
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1
|
+
import numpy as np
|
2
|
+
import pandas as pd
|
3
|
+
import json
|
4
|
+
import asyncio
|
5
|
+
from datetime import datetime, timedelta, timezone
|
6
|
+
from collections import defaultdict
|
7
|
+
import traceback
|
8
|
+
import time
|
9
|
+
import pytz
|
10
|
+
from concurrent.futures import ProcessPoolExecutor
|
11
|
+
from BitunixWebSocket import BitunixPrivateWebSocketClient, BitunixPublicWebSocketClient
|
12
|
+
from AsyncThreadRunner import AsyncThreadRunner
|
13
|
+
from TickerManager import Tickers, Ticker, Interval
|
14
|
+
from DataFrameHtmlRenderer import DataFrameHtmlRenderer
|
15
|
+
from logger import Logger, Colors
|
16
|
+
logger = Logger(__name__).get_logger()
|
17
|
+
colors = Colors()
|
18
|
+
import gc
|
19
|
+
from concurrent.futures import ProcessPoolExecutor
|
20
|
+
|
21
|
+
cst = pytz.timezone('US/Central')
|
22
|
+
|
23
|
+
class BitunixSignal:
|
24
|
+
def __init__(self, api_key, secret_key, settings, threadManager, notifications, bitunixApi):
|
25
|
+
self.api_key = api_key
|
26
|
+
self.secret_key = secret_key
|
27
|
+
self.settings=settings
|
28
|
+
self.threadManager = threadManager
|
29
|
+
self.notifications = notifications
|
30
|
+
self.bitunixApi = bitunixApi
|
31
|
+
|
32
|
+
#Ticker object
|
33
|
+
self.tickerObjects = Tickers(self.settings)
|
34
|
+
|
35
|
+
#these are used for html rendering as well as storing
|
36
|
+
self.signaldf= pd.DataFrame()
|
37
|
+
self.positiondf= pd.DataFrame()
|
38
|
+
self.positiondf2= pd.DataFrame()
|
39
|
+
self.portfoliodf=pd.DataFrame()
|
40
|
+
self.orderdf=pd.DataFrame()
|
41
|
+
self.tickerdf=pd.DataFrame()
|
42
|
+
self.inspectdf=pd.DataFrame()
|
43
|
+
self.tradesdf=pd.DataFrame()
|
44
|
+
self.positionHistorydf=pd.DataFrame()
|
45
|
+
|
46
|
+
self.portfoliodfrenderer=None
|
47
|
+
self.positiondfrenderer=None
|
48
|
+
self.orderdfrenderer=None
|
49
|
+
self.signaldfrenderer=None
|
50
|
+
self.allsignaldfrenderer=None
|
51
|
+
self.positionHistorydfrenderer=None
|
52
|
+
|
53
|
+
self.portfoliodfStyle=None
|
54
|
+
self.positiondfStyle=None
|
55
|
+
self.orderdfStyle=None
|
56
|
+
self.signaldfStyle=None
|
57
|
+
self.allsignaldfStyle=None
|
58
|
+
self.positionHistorydfStyle=None
|
59
|
+
|
60
|
+
#postion, order, etc
|
61
|
+
self.pendingOrders=None
|
62
|
+
self.pendingPositions=None
|
63
|
+
self.portfolioData=None
|
64
|
+
self.tradeHistoryData=None
|
65
|
+
self.positionHistoryData=None
|
66
|
+
|
67
|
+
#this contain all data
|
68
|
+
self.signaldf_full = pd.DataFrame()
|
69
|
+
self.signaldf_filtered = pd.DataFrame()
|
70
|
+
|
71
|
+
#websockets
|
72
|
+
self.bitunixPrivateWebSocketClient = BitunixPrivateWebSocketClient(self.api_key, self.secret_key)
|
73
|
+
if self.settings.USE_PUBLIC_WEBSOCKET:
|
74
|
+
self.bitunixPublicDepthWebSocketClient = BitunixPublicWebSocketClient(self.api_key, self.secret_key, "depth")
|
75
|
+
self.bitunixPublicTickerWebSocketClient = BitunixPublicWebSocketClient(self.api_key, self.secret_key, "ticker")
|
76
|
+
|
77
|
+
self.tickerList=[]
|
78
|
+
|
79
|
+
self.green="#A5DFDF"
|
80
|
+
self.red="#FFB1C1"
|
81
|
+
|
82
|
+
self.profit=0
|
83
|
+
|
84
|
+
self.lastAutoTradeTime = time.time()
|
85
|
+
self.lastTickerDataTime = time.time()
|
86
|
+
|
87
|
+
async def update_settings(self, settings):
|
88
|
+
self.settings = settings
|
89
|
+
self.tickerObjects.update_settings(settings)
|
90
|
+
|
91
|
+
async def load_tickers(self):
|
92
|
+
symbols = await self.bitunixApi.GetTickerList(float(self.settings.THRESHOLD), float(self.settings.MIN_VOLUME))
|
93
|
+
self.pendingPositions= await self.bitunixApi.GetPendingPositionData()
|
94
|
+
self.pendingOrders= await self.bitunixApi.GetPendingOrderData()
|
95
|
+
olist=[]
|
96
|
+
plist=[]
|
97
|
+
if self.pendingPositions:
|
98
|
+
plist = [entry['symbol'] for entry in self.pendingPositions]
|
99
|
+
if self.pendingOrders['orderList']:
|
100
|
+
olist = [entry['symbol'] for entry in self.pendingOrders['orderList']]
|
101
|
+
newlist=olist+plist+list(set(symbols))
|
102
|
+
self.tickerList=newlist[:300]
|
103
|
+
#self.tickerList=['POPCATUSDT','MANAUSDT']
|
104
|
+
|
105
|
+
[await self.add_ticker_to_tickerObjects(sym) for sym in self.tickerList]
|
106
|
+
self.notifications.add_notification(f"{len(self.tickerList)} ticker list loaded")
|
107
|
+
|
108
|
+
async def add_ticker_to_tickerObjects(self, symbol):
|
109
|
+
if not self.tickerObjects.get(symbol):
|
110
|
+
self.tickerObjects.add(symbol)
|
111
|
+
|
112
|
+
async def start_jobs(self):
|
113
|
+
#setup renderers
|
114
|
+
await asyncio.create_task(self.DefinehtmlRenderers())
|
115
|
+
|
116
|
+
#async thread that runs forever jobs
|
117
|
+
self.GetportfolioDataTask = AsyncThreadRunner(self.GetportfolioData, interval=int(self.settings.PORTFOLIO_API_INTERVAL))
|
118
|
+
self.GetportfolioDataTask.start_thread(thread_name="GetportfolioData")
|
119
|
+
|
120
|
+
self.GetPendingPositionDataTask = AsyncThreadRunner(self.GetPendingPositionData, interval=int(self.settings.PENDING_POSITIONS_API_INTERVAL))
|
121
|
+
self.GetPendingPositionDataTask.start_thread(thread_name="GetPendingPositionData")
|
122
|
+
|
123
|
+
self.GetPendingOrderDataTask = AsyncThreadRunner(self.GetPendingOrderData, interval=int(self.settings.PENDING_ORDERS_API_INTERVAL))
|
124
|
+
self.GetPendingOrderDataTask.start_thread(thread_name="GetPendingOrderData")
|
125
|
+
|
126
|
+
self.GetTradeHistoryDataTask = AsyncThreadRunner(self.GetTradeHistoryData, interval=int(self.settings.TRADE_HISTORY_API_INTERVAL))
|
127
|
+
self.GetTradeHistoryDataTask.start_thread(thread_name="GetTradeHistoryData")
|
128
|
+
|
129
|
+
self.GetPositionHistoryDataTask = AsyncThreadRunner(self.GetPositionHistoryData, interval=int(self.settings.POSITION_HISTORY_API_INTERVAL))
|
130
|
+
self.GetPositionHistoryDataTask.start_thread(thread_name="GetPositionHistoryData")
|
131
|
+
|
132
|
+
#run restartable asynch thread
|
133
|
+
await self.restartable_jobs()
|
134
|
+
|
135
|
+
async def restart_jobs(self):
|
136
|
+
|
137
|
+
#stop websocket async thread jobs
|
138
|
+
await self.bitunixPrivateWebSocketClient.stop_websocket()
|
139
|
+
await self.ProcessPrivateDataTask.stop_thread()
|
140
|
+
|
141
|
+
if self.settings.USE_PUBLIC_WEBSOCKET:
|
142
|
+
await self.bitunixPublicDepthWebSocketClient.stop_websocket()
|
143
|
+
await self.UpdateDepthDataTask.stop_thread()
|
144
|
+
|
145
|
+
await self.bitunixPublicTickerWebSocketClient.stop_websocket()
|
146
|
+
await self.UpdateTickerDataTask.stop_thread()
|
147
|
+
|
148
|
+
#kill the loop to restart public websocket
|
149
|
+
#not using for now
|
150
|
+
#await self.restartPublicWebsocketTask.stop_thread()
|
151
|
+
|
152
|
+
#stop onetime / periodic async thread jobs
|
153
|
+
await self.LoadKlineHistoryTask.stop_thread()
|
154
|
+
await self.GetTickerDataTask.stop_thread()
|
155
|
+
await self.AutoTradeProcessTask.stop_thread()
|
156
|
+
|
157
|
+
#start jobs
|
158
|
+
await self.load_tickers()
|
159
|
+
await self.restartable_jobs()
|
160
|
+
|
161
|
+
async def restartable_jobs(self):
|
162
|
+
#start cancelable async jobs
|
163
|
+
#websocket jobs
|
164
|
+
self.ProcessPrivateDataTask = AsyncThreadRunner(self.bitunixPrivateWebSocketClient.run_websocket, 0, self.ProcessPrivateData)
|
165
|
+
self.ProcessPrivateDataTask.start_thread(thread_name="ProcessPrivateData")
|
166
|
+
|
167
|
+
if self.settings.USE_PUBLIC_WEBSOCKET:
|
168
|
+
|
169
|
+
self.bitunixPublicDepthWebSocketClient.tickerList = self.tickerList
|
170
|
+
self.UpdateDepthDataTask = AsyncThreadRunner(self.bitunixPublicDepthWebSocketClient.run_websocket, 0, self.UpdateDepthData)
|
171
|
+
self.UpdateDepthDataTask.start_thread(thread_name="UpdateDepthData")
|
172
|
+
|
173
|
+
self.bitunixPublicTickerWebSocketClient.tickerList = self.tickerList
|
174
|
+
self.UpdateTickerDataTask = AsyncThreadRunner(self.bitunixPublicTickerWebSocketClient.run_websocket, 0, self.UpdateTickerData)
|
175
|
+
self.UpdateTickerDataTask.start_thread(thread_name="UpdateTickerData")
|
176
|
+
|
177
|
+
#normal processes
|
178
|
+
self.LoadKlineHistoryTask = AsyncThreadRunner(self.LoadKlineHistory, interval=0) # run only once
|
179
|
+
self.LoadKlineHistoryTask.start_thread(thread_name="LoadKlineHistory")
|
180
|
+
|
181
|
+
self.GetTickerDataTask = AsyncThreadRunner(self.GetTickerData, interval=int(self.settings.TICKER_DATA_API_INTERVAL))
|
182
|
+
self.GetTickerDataTask.start_thread(thread_name="GetTickerData")
|
183
|
+
|
184
|
+
self.AutoTradeProcessTask = AsyncThreadRunner(self.AutoTradeProcess, interval=int(self.settings.SIGNAL_CHECK_INTERVAL))
|
185
|
+
self.AutoTradeProcessTask.start_thread(thread_name="AutoTradeProcess")
|
186
|
+
|
187
|
+
#start the loop to restart public websocket
|
188
|
+
#if self.settings.USE_PUBLIC_WEBSOCKET:
|
189
|
+
# self.restartPublicWebsocketTask = AsyncThreadRunner(self.restartPublicWebsocket, interval=0)
|
190
|
+
# self.restartPublicWebsocketTask.start_thread(thread_name="restartPublicWebsocket")
|
191
|
+
|
192
|
+
#this is a normal task runing in a async thread, that can be cancelled
|
193
|
+
# this runs in a async thread to stop and start the public websocket, as we found some lagging when it runs continously
|
194
|
+
#not used now
|
195
|
+
async def restartPublicWebsocket(self):
|
196
|
+
while True:
|
197
|
+
await asyncio.sleep(int(self.settings.PUBLIC_WEBSOCKET_RESTART_INTERVAL))
|
198
|
+
|
199
|
+
if self.settings.VERBOSE_LOGGING:
|
200
|
+
self.notifications.add_notification('Restarting public websocket')
|
201
|
+
logger.info(f"Restarting public websocket")
|
202
|
+
|
203
|
+
if self.settings.USE_PUBLIC_WEBSOCKET:
|
204
|
+
await self.UpdateDepthDataTask.stop_thread()
|
205
|
+
await self.UpdateTickerDataTask.stop_thread()
|
206
|
+
|
207
|
+
await asyncio.sleep(30)
|
208
|
+
|
209
|
+
if self.settings.USE_PUBLIC_WEBSOCKET:
|
210
|
+
self.bitunixPublicDepthWebSocketClient.tickerList = self.tickerList
|
211
|
+
self.UpdateDepthDataTask = AsyncThreadRunner(self.bitunixPublicDepthWebSocketClient.run_websocket, 0, self.UpdateDepthData)
|
212
|
+
self.UpdateDepthDataTask.start_thread(thread_name="UpdateDepthData")
|
213
|
+
|
214
|
+
self.bitunixPublicTickerWebSocketClient.tickerList = self.tickerList
|
215
|
+
self.UpdateTickerDataTask = AsyncThreadRunner(self.bitunixPublicTickerWebSocketClient.run_websocket, 0, self.UpdateTickerData)
|
216
|
+
self.UpdateTickerDataTask.start_thread(thread_name="UpdateTickerData")
|
217
|
+
|
218
|
+
if self.settings.VERBOSE_LOGGING:
|
219
|
+
self.notifications.add_notification('Restared public websocket')
|
220
|
+
|
221
|
+
###########################################################################################################
|
222
|
+
async def DefinehtmlRenderers(self):
|
223
|
+
period = self.settings.OPTION_MOVING_AVERAGE
|
224
|
+
#html rendering setup
|
225
|
+
self.portfoliodfrenderer = DataFrameHtmlRenderer()
|
226
|
+
self.positiondfrenderer = DataFrameHtmlRenderer(hide_columns=["positionId", "lastcolor","bidcolor","askcolor",f"{period}_barcolor"], \
|
227
|
+
color_column_mapping={"bid": "bidcolor",
|
228
|
+
"last": "lastcolor",
|
229
|
+
"ask": "askcolor",
|
230
|
+
f"{period}_cb": f"{period}_barcolor"
|
231
|
+
})
|
232
|
+
self.orderdfrenderer = DataFrameHtmlRenderer()
|
233
|
+
self.signaldfrenderer = DataFrameHtmlRenderer(hide_columns=["1d_barcolor","1h_barcolor","15m_barcolor","5m_barcolor","1m_barcolor","lastcolor","bidcolor","askcolor"], \
|
234
|
+
color_column_mapping={"bid": "bidcolor",
|
235
|
+
"last": "lastcolor",
|
236
|
+
"ask": "askcolor",
|
237
|
+
"1d_cb": "1d_barcolor",
|
238
|
+
"1h_cb": "1h_barcolor",
|
239
|
+
"15m_cb": "15m_barcolor",
|
240
|
+
"5m_cb": "5m_barcolor",
|
241
|
+
"1m_cb": "1m_barcolor"
|
242
|
+
})
|
243
|
+
|
244
|
+
#html rendering setup
|
245
|
+
self.allsignaldfrenderer = DataFrameHtmlRenderer(hide_columns=["1d_barcolor","1h_barcolor","15m_barcolor","5m_barcolor","1m_barcolor","lastcolor","bidcolor","askcolor"], \
|
246
|
+
color_column_mapping={"bid": "bidcolor",
|
247
|
+
"last": "lastcolor",
|
248
|
+
"ask": "askcolor",
|
249
|
+
"1d_cb": "1d_barcolor",
|
250
|
+
"1h_cb": "1h_barcolor",
|
251
|
+
"15m_cb": "15m_barcolor",
|
252
|
+
"5m_cb": "5m_barcolor",
|
253
|
+
"1m_cb": "1m_barcolor"
|
254
|
+
})
|
255
|
+
self.positionHistorydfrenderer = DataFrameHtmlRenderer()
|
256
|
+
|
257
|
+
|
258
|
+
###########################################################################################################
|
259
|
+
#load kline history
|
260
|
+
async def LoadKlineHistory(self):
|
261
|
+
start = time.time()
|
262
|
+
intervals = self.tickerObjects.get_intervalIds()
|
263
|
+
for ticker in self.tickerList:
|
264
|
+
for intervalId in intervals:
|
265
|
+
data = await self.bitunixApi.GetKlineHistory(ticker, intervalId, self.settings.BARS)
|
266
|
+
if data is not None:
|
267
|
+
self.tickerObjects.load_kline_history(ticker, intervalId, self.settings.BARS, data)
|
268
|
+
if self.settings.VERBOSE_LOGGING:
|
269
|
+
logger.info(f"kline_history: elapsed time {time.time()-start}")
|
270
|
+
self.notifications.add_notification("Kline history loaded")
|
271
|
+
|
272
|
+
#api data
|
273
|
+
async def GetTickerData(self):
|
274
|
+
start=time.time()
|
275
|
+
|
276
|
+
# Get the current time and set the seconds and microseconds to zero
|
277
|
+
current_time = datetime.now()
|
278
|
+
current_minute = current_time.replace(second=0, microsecond=0)
|
279
|
+
ts = int(current_minute.timestamp())*1000
|
280
|
+
|
281
|
+
#api used insted of websocket
|
282
|
+
data = await self.bitunixApi.GetTickerData()
|
283
|
+
self.tickerdf = pd.DataFrame()
|
284
|
+
if data:
|
285
|
+
|
286
|
+
# Create a DataFrame from the data
|
287
|
+
self.tickerdf = pd.DataFrame(data, columns=["symbol", "last"])
|
288
|
+
|
289
|
+
#remove not required symbols
|
290
|
+
self.tickerdf.loc[~self.tickerdf['symbol'].isin(self.tickerObjects.symbols()), :] = None
|
291
|
+
self.tickerdf.dropna(inplace=True)
|
292
|
+
|
293
|
+
self.tickerdf['ts']=ts
|
294
|
+
self.tickerdf["tickerObj"] = self.tickerdf["symbol"].map(self.tickerObjects.get_tickerDict())
|
295
|
+
self.tuples_list = list(zip(self.tickerdf["tickerObj"], self.tickerdf["last"].astype(float), self.tickerdf["ts"]))
|
296
|
+
self.tickerObjects.form_candle(self.tuples_list)
|
297
|
+
|
298
|
+
self.lastTickerDataTime = time.time()
|
299
|
+
if self.settings.VERBOSE_LOGGING:
|
300
|
+
logger.info(f"GetTickerData: elapsed time {time.time()-start}")
|
301
|
+
|
302
|
+
|
303
|
+
#websocket data
|
304
|
+
async def UpdateTickerData(self, message):
|
305
|
+
if message=="":
|
306
|
+
return
|
307
|
+
try:
|
308
|
+
data = json.loads(message)
|
309
|
+
if 'symbol' in data and data['ch'] in ['ticker']:
|
310
|
+
symbol = data['symbol']
|
311
|
+
ts = data['ts']
|
312
|
+
last= float(data['data']['la'])
|
313
|
+
highest= float(data['data']['h'])
|
314
|
+
lowest= float(data['data']['l'])
|
315
|
+
volume= float(data['data']['b'])
|
316
|
+
volumeInCurrency= float(data['data']['q'])
|
317
|
+
tickerObj = self.tickerObjects.get(symbol)
|
318
|
+
if tickerObj:
|
319
|
+
tickerObj.set_24hrData(highest,lowest,volume,volumeInCurrency)
|
320
|
+
tickerObj.form_candle(last, ts)
|
321
|
+
del tickerObj
|
322
|
+
gc.collect()
|
323
|
+
del data
|
324
|
+
gc.collect()
|
325
|
+
except Exception as e:
|
326
|
+
logger.info(f"Function: UpdateTickerData, {e}, {e.args}, {type(e).__name__}")
|
327
|
+
if self.settings.VERBOSE_LOGGING:
|
328
|
+
logger.info(f"Function: UpdateTickerData, time:{ts}, symbol:{symbol}, highest:{highest}, lowest:{lowest}, volume:{volume}, volumeInCurrency:{volumeInCurrency}")
|
329
|
+
|
330
|
+
#websocket data
|
331
|
+
async def UpdateDepthData(self, message):
|
332
|
+
if message=="":
|
333
|
+
return
|
334
|
+
try:
|
335
|
+
data = json.loads(message)
|
336
|
+
if 'symbol' in data and data['ch'] in ['depth_book1']:
|
337
|
+
symbol = data['symbol']
|
338
|
+
ts = data['ts']
|
339
|
+
bid = float(data['data']['b'][0][0])
|
340
|
+
ask = float(data['data']['a'][0][0])
|
341
|
+
tickerObj = self.tickerObjects.get(symbol)
|
342
|
+
if tickerObj:
|
343
|
+
tickerObj.set_bid(bid)
|
344
|
+
tickerObj.set_ask(ask)
|
345
|
+
del tickerObj
|
346
|
+
gc.collect()
|
347
|
+
del data
|
348
|
+
gc.collect()
|
349
|
+
except Exception as e:
|
350
|
+
logger.info(f"Function: UpdateDepthData, {e}, {e.args}, {type(e).__name__}")
|
351
|
+
if self.settings.VERBOSE_LOGGING:
|
352
|
+
logger.info(f"Function: UpdateDepthData, time:{ts}, symbol:{symbol}, bid:{bid}, ask:{ask}")
|
353
|
+
|
354
|
+
# this is called to update last price, as the websocket is lagging
|
355
|
+
# this is only called for the tickers in the pendingpositions
|
356
|
+
# and for first few records in the signaldf
|
357
|
+
async def apply_last_data(self, symbols):
|
358
|
+
start=time.time()
|
359
|
+
try:
|
360
|
+
# Get the current time and set the seconds and microseconds to zero
|
361
|
+
current_time = datetime.now()
|
362
|
+
current_minute = current_time.replace(second=0, microsecond=0)
|
363
|
+
ts = int(current_minute.timestamp())*1000
|
364
|
+
|
365
|
+
data= await self.bitunixApi.GetTickerslastPrice(symbols)
|
366
|
+
if data is None:
|
367
|
+
return
|
368
|
+
tickerdf = pd.DataFrame(data, columns=["symbol", "markPrice", "lastPrice", "open", "last", "quoteVol", "baseVol", "high", "low"])
|
369
|
+
tickerdf['ts']=ts
|
370
|
+
tickerdf["tickerObj"] = tickerdf["symbol"].map(self.tickerObjects.get_tickerDict())
|
371
|
+
tuples_list = list(zip(tickerdf["tickerObj"], tickerdf["last"].astype(float), tickerdf["ts"]))
|
372
|
+
self.tickerObjects.form_candle(tuples_list)
|
373
|
+
del data, tickerdf, tuples_list
|
374
|
+
gc.collect()
|
375
|
+
except Exception as e:
|
376
|
+
logger.info(e)
|
377
|
+
if self.settings.VERBOSE_LOGGING:
|
378
|
+
logger.info(f"apply_last_data: elapsed time {time.time()-start}")
|
379
|
+
|
380
|
+
# this is called to update bid and ask,
|
381
|
+
# as it is time consuming to call the api for each ticker,
|
382
|
+
# this is only called for the tickers in the pendingpositions
|
383
|
+
# and for first few records in the signaldf
|
384
|
+
async def apply_depth_data(self, ticker):
|
385
|
+
ddata = await self.bitunixApi.GetDepthData(ticker,"1")
|
386
|
+
if ddata is not None and 'bids' in ddata:
|
387
|
+
bid = float(ddata['bids'][0][0])
|
388
|
+
ask = float(ddata['asks'][0][0])
|
389
|
+
tickerObj=self.tickerObjects.get(ticker)
|
390
|
+
if tickerObj:
|
391
|
+
tickerObj.set_bid(bid)
|
392
|
+
tickerObj.set_ask(ask)
|
393
|
+
del tickerObj
|
394
|
+
gc.collect()
|
395
|
+
await asyncio.sleep(0)
|
396
|
+
del ddata
|
397
|
+
gc.collect()
|
398
|
+
|
399
|
+
###########################################################################################################
|
400
|
+
|
401
|
+
async def GetportfolioData(self):
|
402
|
+
start=time.time()
|
403
|
+
try:
|
404
|
+
self.portfolioData = await self.bitunixApi.GetportfolioData()
|
405
|
+
if self.portfolioData:
|
406
|
+
self.portfoliodf=pd.DataFrame(self.portfolioData,index=[0])[["marginCoin","available","margin","crossUnrealizedPNL"]]
|
407
|
+
else:
|
408
|
+
self.portfolioData = pd.DataFrame()
|
409
|
+
self.portfoliodfStyle= self.portfoliodfrenderer.render_html(self.portfoliodf)
|
410
|
+
|
411
|
+
except Exception as e:
|
412
|
+
logger.info(f"Function: GetportfolioData, {e}, {e.args}, {type(e).__name__}")
|
413
|
+
if self.settings.VERBOSE_LOGGING:
|
414
|
+
logger.info(f"GetportfolioData: elapsed time {time.time()-start}")
|
415
|
+
|
416
|
+
async def GetPendingPositionData(self):
|
417
|
+
start=time.time()
|
418
|
+
try:
|
419
|
+
self.pendingPositions = await self.bitunixApi.GetPendingPositionData()
|
420
|
+
if self.pendingPositions:
|
421
|
+
self.positiondf = pd.DataFrame(self.pendingPositions, columns=["positionId", "symbol", "side", "unrealizedPNL", "realizedPNL", "qty", "ctime", "avgOpenPrice"])
|
422
|
+
self.positiondf['bid']=0.0
|
423
|
+
self.positiondf['bidcolor']=""
|
424
|
+
self.positiondf['last']=0.0
|
425
|
+
self.positiondf['lastcolor']=""
|
426
|
+
self.positiondf['ask']=0.0
|
427
|
+
self.positiondf['askcolor']=""
|
428
|
+
self.positiondf['ctime']=pd.to_datetime(self.positiondf['ctime'].astype(float), unit='ms').dt.tz_localize('UTC').dt.tz_convert(cst).dt.strftime('%Y-%m-%d %H:%M:%S')
|
429
|
+
|
430
|
+
try:
|
431
|
+
self.positiondf = self.positiondf.assign(
|
432
|
+
bid=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).get_bid()),
|
433
|
+
bidcolor=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).bidcolor),
|
434
|
+
last=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).get_last()),
|
435
|
+
lastcolor=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).lastcolor),
|
436
|
+
ask=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).get_ask()),
|
437
|
+
askcolor=self.positiondf['symbol'].map(lambda sym: self.tickerObjects.get(sym).askcolor)
|
438
|
+
)
|
439
|
+
#self.positiondf['roi']= round((self.positiondf['last'].astype(float) * self.positiondf['qty'].astype(float) - \
|
440
|
+
# self.positiondf['avgOpenPrice'].astype(float) * self.positiondf['qty'].astype(float)) / \
|
441
|
+
# (self.positiondf['avgOpenPrice'].astype(float) * self.positiondf['qty'].astype(float) / (self.settings.LEVERAGE/100)) * 10000 , 2)
|
442
|
+
except Exception as e:
|
443
|
+
pass
|
444
|
+
self.positiondf['charts'] = self.positiondf.apply(self.add_charts_button, axis=1)
|
445
|
+
self.positiondf['bitunix'] = self.positiondf.apply(self.add_bitunix_button, axis=1)
|
446
|
+
self.positiondf['action'] = self.positiondf.apply(self.add_close_button, axis=1)
|
447
|
+
self.positiondf['add'] = self.positiondf.apply(self.add_add_button, axis=1)
|
448
|
+
self.positiondf['reduce'] = self.positiondf.apply(self.add_reduce_button, axis=1)
|
449
|
+
|
450
|
+
self.positiondf['bid'] = self.positiondf['bid'].astype('float64')
|
451
|
+
self.positiondf['last'] = self.positiondf['last'].astype('float64')
|
452
|
+
self.positiondf['ask'] = self.positiondf['ask'].astype('float64')
|
453
|
+
else:
|
454
|
+
self.positiondf = pd.DataFrame()
|
455
|
+
#self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf)
|
456
|
+
|
457
|
+
#if not self.settings.USE_PUBLIC_WEBSOCKET:
|
458
|
+
#get bid las ask using api for the symbols in pending psotion
|
459
|
+
if not self.positiondf.empty:
|
460
|
+
if self.settings.USE_PUBLIC_WEBSOCKET:
|
461
|
+
await asyncio.create_task(self.apply_last_data(','.join(self.positiondf['symbol'].astype(str).tolist())))
|
462
|
+
await asyncio.gather(
|
463
|
+
*[
|
464
|
+
asyncio.create_task(self.apply_depth_data(row['symbol']))
|
465
|
+
for index, row in self.positiondf.iterrows()
|
466
|
+
]
|
467
|
+
)
|
468
|
+
|
469
|
+
|
470
|
+
except Exception as e:
|
471
|
+
logger.info(f"Function: GetPendingPositionData, {e}, {e.args}, {type(e).__name__}")
|
472
|
+
if self.settings.VERBOSE_LOGGING:
|
473
|
+
logger.info(f"GetPendingPositionData: elapsed time {time.time()-start}")
|
474
|
+
|
475
|
+
async def GetPendingOrderData(self):
|
476
|
+
start=time.time()
|
477
|
+
try:
|
478
|
+
self.pendingOrders = await self.bitunixApi.GetPendingOrderData()
|
479
|
+
if self.pendingOrders and 'orderList' in self.pendingOrders:
|
480
|
+
self.orderdf = pd.DataFrame(self.pendingOrders['orderList'], columns=["orderId", "symbol", "qty", "side", "price", "ctime", "status", "reduceOnly"])
|
481
|
+
self.orderdf['rtime']=pd.to_datetime(self.orderdf['ctime'].astype(float), unit='ms').dt.tz_localize('UTC').dt.tz_convert(cst).dt.strftime('%Y-%m-%d %H:%M:%S')
|
482
|
+
self.orderdf['charts'] = self.orderdf.apply(self.add_charts_button, axis=1)
|
483
|
+
self.orderdf['bitunix'] = self.orderdf.apply(self.add_bitunix_button, axis=1)
|
484
|
+
self.orderdf['action'] = self.orderdf.apply(self.add_order_close_button, axis=1)
|
485
|
+
else:
|
486
|
+
self.orderdf = pd.DataFrame()
|
487
|
+
self.orderdfStyle= self.orderdfrenderer.render_html(self.orderdf)
|
488
|
+
|
489
|
+
except Exception as e:
|
490
|
+
logger.info(f"Function: GetPendingOrderData, {e}, {e.args}, {type(e).__name__}")
|
491
|
+
if self.settings.VERBOSE_LOGGING:
|
492
|
+
logger.info(f"GetPendingOrderData: elapsed time {time.time()-start}")
|
493
|
+
|
494
|
+
async def GetPositionHistoryData(self):
|
495
|
+
start=time.time()
|
496
|
+
try:
|
497
|
+
self.positionHistoryData = await self.bitunixApi.GetPositionHistoryData()
|
498
|
+
if self.positionHistoryData and 'positionList' in self.positionHistoryData:
|
499
|
+
self.positionHistorydf = pd.DataFrame(self.positionHistoryData['positionList'], columns=["symbol", "side","realizedPNL", "ctime", "maxQty", "closePrice","fee", "funding"])
|
500
|
+
self.positionHistorydf['ctime'] = pd.to_datetime(self.positionHistorydf['ctime'].astype(float), unit='ms').dt.tz_localize('UTC').dt.tz_convert(cst).dt.strftime('%Y-%m-%d %H:%M:%S')
|
501
|
+
self.positionHistorydf['charts'] = self.positionHistorydf.apply(self.add_charts_button, axis=1)
|
502
|
+
self.positionHistorydf['bitunix'] = self.positionHistorydf.apply(self.add_bitunix_button, axis=1)
|
503
|
+
|
504
|
+
else:
|
505
|
+
self.positionHistorydf = pd.DataFrame()
|
506
|
+
self.positionHistorydfStyle= self.positionHistorydfrenderer.render_html(self.positionHistorydf)
|
507
|
+
|
508
|
+
except Exception as e:
|
509
|
+
logger.info(f"Function: GetPositionHistoryData, {e}, {e.args}, {type(e).__name__}")
|
510
|
+
if self.settings.VERBOSE_LOGGING:
|
511
|
+
logger.info(f"GetPositionHistoryData: elapsed time {time.time()-start}")
|
512
|
+
|
513
|
+
async def GetTradeHistoryData(self):
|
514
|
+
start=time.time()
|
515
|
+
try:
|
516
|
+
self.tradeHistoryData = await self.bitunixApi.GetTradeHistoryData()
|
517
|
+
if self.tradeHistoryData and 'tradeList' in self.tradeHistoryData:
|
518
|
+
self.tradesdf = pd.DataFrame(self.tradeHistoryData['tradeList'], columns=["symbol", "ctime", "qty", "side", "price","realizedPNL","reduceOnly"])
|
519
|
+
self.tradesdf['rtime'] = pd.to_datetime(self.tradesdf['ctime'].astype(float), unit='ms').dt.tz_localize('UTC').dt.tz_convert(cst).dt.strftime('%Y-%m-%d %H:%M:%S')
|
520
|
+
grouped_trades = self.tradesdf.groupby("symbol")
|
521
|
+
for symbol, tickerObj in self.tickerObjects.get_tickerDict().items():
|
522
|
+
if symbol in grouped_trades.groups:
|
523
|
+
# Filter trades for the current symbol and convert them to a list of dicts
|
524
|
+
tickerObj.trades = grouped_trades.get_group(symbol).to_dict("records")
|
525
|
+
except Exception as e:
|
526
|
+
logger.info(f"Function: GetTradeHistoryData, {e}, {e.args}, {type(e).__name__}")
|
527
|
+
if self.settings.VERBOSE_LOGGING:
|
528
|
+
logger.info(f"GetTradeHistoryData: elapsed time {time.time()-start}")
|
529
|
+
|
530
|
+
|
531
|
+
###########################################################################################################
|
532
|
+
|
533
|
+
async def BuySellList(self, period):
|
534
|
+
try:
|
535
|
+
#ticker in positions and orders
|
536
|
+
inuse1=[]
|
537
|
+
inuse2=[]
|
538
|
+
if not self.positiondf.empty:
|
539
|
+
inuse1 = self.positiondf['symbol'].to_list()
|
540
|
+
if not self.orderdf.empty:
|
541
|
+
inuse2 = self.orderdf['symbol'].to_list()
|
542
|
+
inuseTickers = set(inuse1 + inuse2)
|
543
|
+
|
544
|
+
# Extract buy/sell ticker data
|
545
|
+
self.tickerObjects.getCurrentData(period)
|
546
|
+
|
547
|
+
self.signaldf_full = self.tickerObjects.signaldf_full
|
548
|
+
|
549
|
+
if self.signaldf_full.empty:
|
550
|
+
self.positiondf2 = self.positiondf
|
551
|
+
self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf)
|
552
|
+
return
|
553
|
+
|
554
|
+
self.signaldf_full['charts'] = self.signaldf_full.apply(self.add_charts_button, axis=1)
|
555
|
+
self.signaldf_full['bitunix'] = self.signaldf_full.apply(self.add_bitunix_button, axis=1)
|
556
|
+
|
557
|
+
|
558
|
+
self.allsignaldfStyle= self.allsignaldfrenderer.render_html(self.signaldf_full)
|
559
|
+
|
560
|
+
self.signaldf_filtered = self.tickerObjects.signaldf_filtered
|
561
|
+
|
562
|
+
if not self.positiondf.empty and not self.signaldf_full.empty:
|
563
|
+
columns=['symbol', f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema", 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"]
|
564
|
+
columns2=["qty", "side", "unrealizedPNL", "realizedPNL", "ctime", "avgOpenPrice", "bid", "bidcolor", "last", "lastcolor", "ask", "askcolor", "charts", "bitunix", "action", "add", "reduce"]
|
565
|
+
if set(columns).issubset(self.signaldf_full.columns) and set(columns2).issubset(self.positiondf.columns):
|
566
|
+
columnOrder= ['symbol', "side", "unrealizedPNL", "realizedPNL", f"{period}_trend", f"{period}_cb", f"{period}_barcolor", f"{period}_ema", 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"]
|
567
|
+
self.positiondf2 = pd.merge(self.positiondf, self.signaldf_full[["symbol", f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
|
568
|
+
f"{period}_ema", f"{period}_macd", f"{period}_bbm", f"{period}_rsi", f"{period}_adx", f"{period}_candle_trend",
|
569
|
+
f"{period}_trend",f"{period}_cb", f"{period}_barcolor"]], left_on="symbol", right_index=True, how="left")[columnOrder]
|
570
|
+
self.positiondfStyle= self.positiondfrenderer.render_html(self.positiondf2)
|
571
|
+
else:
|
572
|
+
self.positiondf2 = pd.DataFrame()
|
573
|
+
|
574
|
+
if not self.signaldf_filtered.empty:
|
575
|
+
#remove those that are in positon and orders
|
576
|
+
self.signaldf_filtered = self.signaldf_filtered[~(self.signaldf_filtered['symbol'].isin(inuseTickers))]
|
577
|
+
|
578
|
+
if not self.signaldf_filtered.empty:
|
579
|
+
# Assign to self.signaldf for HTML rendering
|
580
|
+
self.signaldf = self.signaldf_filtered[[
|
581
|
+
"symbol", f"{period}_trend",f"{period}_cb", f"{period}_barcolor",
|
582
|
+
f"{period}_ema", f"{period}_macd", f"{period}_bbm", f"{period}_rsi",f"{period}_adx",f"{period}_candle_trend",
|
583
|
+
'lastcolor', 'bidcolor', 'askcolor', 'bid', 'ask', 'last',
|
584
|
+
f"{period}_open", f"{period}_close", f"{period}_high", f"{period}_low",
|
585
|
+
]].sort_values(by=[f'{period}_cb'], ascending=[False])
|
586
|
+
|
587
|
+
# Add buttons
|
588
|
+
self.signaldf['charts'] = self.signaldf.apply(self.add_charts_button, axis=1)
|
589
|
+
self.signaldf['bitunix'] = self.signaldf.apply(self.add_bitunix_button, axis=1)
|
590
|
+
self.signaldf['buy'] = self.signaldf.apply(self.add_buy_button, axis=1)
|
591
|
+
self.signaldf['sell'] = self.signaldf.apply(self.add_sell_button, axis=1)
|
592
|
+
else:
|
593
|
+
self.signaldf = pd.DataFrame()
|
594
|
+
self.signaldfStyle= self.signaldfrenderer.render_html(self.signaldf)
|
595
|
+
|
596
|
+
#if not self.settings.USE_PUBLIC_WEBSOCKET:
|
597
|
+
#get bid las ask using api for max_auto_trades rows
|
598
|
+
if not self.signaldf.empty:
|
599
|
+
m = min(self.signaldf.shape[0], int(self.settings.MAX_AUTO_TRADES))
|
600
|
+
if self.settings.USE_PUBLIC_WEBSOCKET:
|
601
|
+
await asyncio.create_task(self.apply_last_data(','.join(self.signaldf['symbol'][:m].astype(str).tolist())))
|
602
|
+
await asyncio.gather(
|
603
|
+
*[
|
604
|
+
asyncio.create_task(self.apply_depth_data(row['symbol']))
|
605
|
+
for index, row in self.signaldf[:m].iterrows()
|
606
|
+
]
|
607
|
+
)
|
608
|
+
|
609
|
+
except Exception as e:
|
610
|
+
logger.info(f"Function: BuySellList, {e}, {e.args}, {type(e).__name__}")
|
611
|
+
logger.info(traceback.print_exc())
|
612
|
+
del inuse1, inuse2, inuseTickers
|
613
|
+
gc.collect()
|
614
|
+
|
615
|
+
async def AutoTradeProcess(self):
|
616
|
+
if self.settings.VERBOSE_LOGGING:
|
617
|
+
logger.info(f"AutoTradeProcess started")
|
618
|
+
start=time.time()
|
619
|
+
|
620
|
+
period = self.settings.OPTION_MOVING_AVERAGE
|
621
|
+
try:
|
622
|
+
|
623
|
+
#calulate current data at selected period, this create signaldf
|
624
|
+
await self.BuySellList(period)
|
625
|
+
|
626
|
+
if not self.settings.AUTOTRADE:
|
627
|
+
return
|
628
|
+
##############################################################################################################################
|
629
|
+
# open long or short postition
|
630
|
+
##############################################################################################################################
|
631
|
+
count=0
|
632
|
+
self.pendingPositions = await self.bitunixApi.GetPendingPositionData({})
|
633
|
+
if self.pendingPositions:
|
634
|
+
count=count+len(self.pendingPositions)
|
635
|
+
self.pendingOrders = await self.bitunixApi.GetPendingOrderData({})
|
636
|
+
if self.pendingOrders:
|
637
|
+
count=count+len(self.pendingOrders['orderList'])
|
638
|
+
|
639
|
+
if count < int(self.settings.MAX_AUTO_TRADES):
|
640
|
+
if not self.signaldf.empty:
|
641
|
+
#open position upto a max of max_auto_trades from the signal list
|
642
|
+
df=self.signaldf.copy(deep=False)
|
643
|
+
for index, row in df.iterrows():
|
644
|
+
side = "BUY" if row[f'{period}_barcolor'] == self.green else "SELL" if row[f'{period}_barcolor'] == self.red else ""
|
645
|
+
if side != "":
|
646
|
+
select = True
|
647
|
+
self.pendingPositions = await self.bitunixApi.GetPendingPositionData({'symbol': row.symbol})
|
648
|
+
if self.pendingPositions and len(self.pendingPositions) == 1:
|
649
|
+
select = False
|
650
|
+
self.pendingOrders = await self.bitunixApi.GetPendingOrderData({'symbol': row.symbol})
|
651
|
+
if self.pendingOrders and len(self.pendingOrders['orderList']) == 1:
|
652
|
+
select = False
|
653
|
+
if select:
|
654
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
655
|
+
price = (bid if side == "BUY" else ask if side == "SELL" else last) if bid<=last<=ask else last
|
656
|
+
balance = float(self.portfoliodf["available"].iloc[0]) + float(self.portfoliodf["crossUnrealizedPNL"].iloc[0])
|
657
|
+
qty= str(max(balance * (float(self.settings.ORDER_AMOUNT_PERCENTAGE) / 100) / price * int(self.settings.LEVERAGE),mtv))
|
658
|
+
|
659
|
+
self.notifications.add_notification(
|
660
|
+
f'{colors.YELLOW} Opening {"long" if side=="BUY" else "short"} position for {row.symbol} with {qty} qty @ {price})'
|
661
|
+
)
|
662
|
+
datajs = await self.bitunixApi.PlaceOrder(row.symbol, qty, price, side)
|
663
|
+
count=count+1
|
664
|
+
if count >= int(self.settings.MAX_AUTO_TRADES):
|
665
|
+
break
|
666
|
+
await asyncio.sleep(0)
|
667
|
+
del df
|
668
|
+
gc.collect()
|
669
|
+
|
670
|
+
##############################################################################################################################
|
671
|
+
# close long or short postition
|
672
|
+
##############################################################################################################################
|
673
|
+
|
674
|
+
# Close orders that are open for a while
|
675
|
+
current_time = time.time() * 1000
|
676
|
+
df=self.orderdf.copy(deep=False)
|
677
|
+
for index, row in df.iterrows():
|
678
|
+
if current_time - int(row.ctime) > 60000:
|
679
|
+
self.notifications.add_notification(
|
680
|
+
f'{colors.LBLUE} Canceling order {row.orderId}, {row.symbol} {row.qty} created at {row.rtime} '
|
681
|
+
)
|
682
|
+
datajs = await self.bitunixApi.CancelOrder(row.symbol, row.orderId)
|
683
|
+
await asyncio.sleep(0)
|
684
|
+
|
685
|
+
if not self.positiondf.empty:
|
686
|
+
df=self.positiondf.copy(deep=False)
|
687
|
+
for index, row in df.iterrows():
|
688
|
+
unrealized_pnl = float(row.unrealizedPNL)
|
689
|
+
realized_pnl = float(row.realizedPNL)
|
690
|
+
total_pnl = unrealized_pnl + realized_pnl
|
691
|
+
side=row['side']
|
692
|
+
|
693
|
+
requiredCols=[f'{period}_open', f'{period}_close', f'{period}_high', f'{period}_low', f'{period}_ema', f'{period}_macd', f'{period}_bbm', f'{period}_rsi', f'{period}_candle_trend', f'{period}_trend', f'{period}_cb', f'{period}_barcolor']
|
694
|
+
required_cols = set(requiredCols)
|
695
|
+
|
696
|
+
# Close position that fall the below criteria
|
697
|
+
if not self.signaldf_full.columns.empty and self.signaldf_full['symbol'].isin([row.symbol]).any() and required_cols.issubset(set(self.signaldf_full.columns)):
|
698
|
+
|
699
|
+
# Check orders
|
700
|
+
select = True
|
701
|
+
self.pendingOrders = await self.bitunixApi.GetPendingOrderData({'symbol': row.symbol})
|
702
|
+
if self.pendingOrders and len(self.pendingOrders['orderList']) == 1:
|
703
|
+
select = False
|
704
|
+
|
705
|
+
if select and int(self.settings.MAX_AUTO_TRADES)!=0:
|
706
|
+
|
707
|
+
# check take portit or accept loss
|
708
|
+
if float(self.settings.LOSS_AMOUNT) > 0 and total_pnl < -float(self.settings.LOSS_AMOUNT):
|
709
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
710
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
711
|
+
|
712
|
+
self.notifications.add_notification(
|
713
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to stop loss for {row.symbol} with {row.qty} qty @ {price})'
|
714
|
+
)
|
715
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
716
|
+
positionId=row.positionId,
|
717
|
+
ticker=row.symbol,
|
718
|
+
qty=row.qty,
|
719
|
+
price=price,
|
720
|
+
side=row.side,
|
721
|
+
tradeSide="CLOSE"
|
722
|
+
)
|
723
|
+
continue
|
724
|
+
|
725
|
+
if float(self.settings.PROFIT_AMOUNT) > 0 and total_pnl > float(self.settings.PROFIT_AMOUNT):
|
726
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
727
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
728
|
+
|
729
|
+
self.notifications.add_notification(
|
730
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to take profit for {row.symbol} with {row.qty} qty @ {price})'
|
731
|
+
)
|
732
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
733
|
+
positionId=row.positionId,
|
734
|
+
ticker=row.symbol,
|
735
|
+
qty=row.qty,
|
736
|
+
price=price,
|
737
|
+
side=row.side,
|
738
|
+
tradeSide="CLOSE"
|
739
|
+
)
|
740
|
+
continue
|
741
|
+
|
742
|
+
# Moving average comparison between fast and medium
|
743
|
+
if self.settings.EMA_STUDY and self.settings.EMA_CHECK_ON_CLOSE:
|
744
|
+
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_ema'] == "SELL":
|
745
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
746
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
747
|
+
|
748
|
+
self.notifications.add_notification(
|
749
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to MA {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
750
|
+
)
|
751
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
752
|
+
positionId=row.positionId,
|
753
|
+
ticker=row.symbol,
|
754
|
+
qty=row.qty,
|
755
|
+
price=price,
|
756
|
+
side=row.side,
|
757
|
+
tradeSide="CLOSE"
|
758
|
+
)
|
759
|
+
continue
|
760
|
+
|
761
|
+
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_ema'] == "BUY":
|
762
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
763
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
764
|
+
self.notifications.add_notification(
|
765
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to MA {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
766
|
+
)
|
767
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
768
|
+
positionId=row.positionId,
|
769
|
+
ticker=row.symbol,
|
770
|
+
qty=row.qty,
|
771
|
+
price=price,
|
772
|
+
side=row.side,
|
773
|
+
tradeSide="CLOSE"
|
774
|
+
)
|
775
|
+
continue
|
776
|
+
|
777
|
+
# MACD comparison between MACD and Signal
|
778
|
+
if self.settings.MACD_STUDY and self.settings.MACD_CHECK_ON_CLOSE:
|
779
|
+
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_macd'] == "SELL":
|
780
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
781
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
782
|
+
self.notifications.add_notification(
|
783
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to MACD {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
784
|
+
)
|
785
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
786
|
+
positionId=row.positionId,
|
787
|
+
ticker=row.symbol,
|
788
|
+
qty=row.qty,
|
789
|
+
price=price,
|
790
|
+
side=row.side,
|
791
|
+
tradeSide="CLOSE"
|
792
|
+
)
|
793
|
+
continue
|
794
|
+
|
795
|
+
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_macd'] == "BUY":
|
796
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
797
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
798
|
+
self.notifications.add_notification(
|
799
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to MACD {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
800
|
+
)
|
801
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
802
|
+
positionId=row.positionId,
|
803
|
+
ticker=row.symbol,
|
804
|
+
qty=row.qty,
|
805
|
+
price=price,
|
806
|
+
side=row.side,
|
807
|
+
tradeSide="CLOSE"
|
808
|
+
)
|
809
|
+
continue
|
810
|
+
|
811
|
+
# Bollinger Band comparison between open and BBM
|
812
|
+
if self.settings.BBM_STUDY and self.settings.BBM_CHECK_ON_CLOSE:
|
813
|
+
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_bbm'] == "SELL":
|
814
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
815
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
816
|
+
self.notifications.add_notification(
|
817
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to BBM {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
818
|
+
)
|
819
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
820
|
+
positionId=row.positionId,
|
821
|
+
ticker=row.symbol,
|
822
|
+
qty=row.qty,
|
823
|
+
price=price,
|
824
|
+
side=row.side,
|
825
|
+
tradeSide="CLOSE"
|
826
|
+
)
|
827
|
+
continue
|
828
|
+
|
829
|
+
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_bbm'] == "BUY":
|
830
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
831
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
832
|
+
self.notifications.add_notification(
|
833
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to BBM {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
834
|
+
)
|
835
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
836
|
+
positionId=row.positionId,
|
837
|
+
ticker=row.symbol,
|
838
|
+
qty=row.qty,
|
839
|
+
price=price,
|
840
|
+
side=row.side,
|
841
|
+
tradeSide="CLOSE"
|
842
|
+
)
|
843
|
+
continue
|
844
|
+
|
845
|
+
# RSI comparison
|
846
|
+
if self.settings.RSI_STUDY and self.settings.RSI_CHECK_ON_CLOSE:
|
847
|
+
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_rsi'] == "SELL":
|
848
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
849
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
850
|
+
self.notifications.add_notification(
|
851
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to RSI {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
852
|
+
)
|
853
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
854
|
+
positionId=row.positionId,
|
855
|
+
ticker=row.symbol,
|
856
|
+
qty=row.qty,
|
857
|
+
price=price,
|
858
|
+
side=row.side,
|
859
|
+
tradeSide="CLOSE"
|
860
|
+
)
|
861
|
+
continue
|
862
|
+
|
863
|
+
if row.side == 'SELL' and self.signaldf_full.at[row.symbol, f'{period}_rsi'] == "BUY":
|
864
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
865
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
866
|
+
self.notifications.add_notification(
|
867
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to RSI {period} crossover for {row.symbol} with {row.qty} qty @ {price})'
|
868
|
+
)
|
869
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
870
|
+
positionId=row.positionId,
|
871
|
+
ticker=row.symbol,
|
872
|
+
qty=row.qty,
|
873
|
+
price=price,
|
874
|
+
side=row.side,
|
875
|
+
tradeSide="CLOSE"
|
876
|
+
)
|
877
|
+
continue
|
878
|
+
|
879
|
+
# Close on weak trend after open
|
880
|
+
if self.settings.ADX_STUDY and self.settings.ADX_CHECK_ON_CLOSE:
|
881
|
+
if row.side == 'BUY' and self.signaldf_full.at[row.symbol, f'{period}_adx'] == "WEAK":
|
882
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
883
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
884
|
+
self.notifications.add_notification(
|
885
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to WEAK ADX 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}_adx'] == "WEAK":
|
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 WEAK ADX 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
|
+
|
913
|
+
# candle reversed
|
914
|
+
if self.settings.CANDLE_TREND_STUDY and self.settings.CANDLE_TREND_CHECK_ON_CLOSE:
|
915
|
+
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'] == "BEARISH":
|
916
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
917
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
918
|
+
|
919
|
+
self.notifications.add_notification(
|
920
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to bearish candle reversal for {row.symbol} with {row.qty} qty @ {price})'
|
921
|
+
)
|
922
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
923
|
+
positionId=row.positionId,
|
924
|
+
ticker=row.symbol,
|
925
|
+
qty=row.qty,
|
926
|
+
price=price,
|
927
|
+
side=row.side,
|
928
|
+
tradeSide="CLOSE"
|
929
|
+
)
|
930
|
+
continue
|
931
|
+
|
932
|
+
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}_candle_trend'] == "BULLISH":
|
933
|
+
last, bid, ask, mtv = await self.GetTickerBidLastAsk(row.symbol)
|
934
|
+
price = (ask if row['side'] == "BUY" else bid if row['side'] == "SELL" else last) if bid<=last<=ask else last
|
935
|
+
|
936
|
+
self.notifications.add_notification(
|
937
|
+
f'{colors.CYAN} Closing {"long" if side=="BUY" else "short"} position due to bullish candle reversal for {row.symbol} with {row.qty} qty @ {price})'
|
938
|
+
)
|
939
|
+
datajs = await self.bitunixApi.PlaceOrder(
|
940
|
+
positionId=row.positionId,
|
941
|
+
ticker=row.symbol,
|
942
|
+
qty=row.qty,
|
943
|
+
price=price,
|
944
|
+
side=row.side,
|
945
|
+
tradeSide="CLOSE"
|
946
|
+
)
|
947
|
+
continue
|
948
|
+
|
949
|
+
await asyncio.sleep(0)
|
950
|
+
|
951
|
+
self.lastAutoTradeTime = time.time()
|
952
|
+
except Exception as e:
|
953
|
+
stack = traceback.extract_stack()
|
954
|
+
function_name = stack[-1].name
|
955
|
+
logger.info(f"Function: {function_name}, {e}, {e.args}, {type(e).__name__}")
|
956
|
+
logger.info(traceback.print_exc())
|
957
|
+
|
958
|
+
if self.settings.VERBOSE_LOGGING:
|
959
|
+
logger.info(f"AutoTradeProcess: elapsed time {time.time()-start}")
|
960
|
+
|
961
|
+
del df
|
962
|
+
gc.collect()
|
963
|
+
|
964
|
+
|
965
|
+
async def GetTickerBidLastAsk(self, symbol):
|
966
|
+
tdata = await self.bitunixApi.GetTickerslastPrice(symbol)
|
967
|
+
if tdata:
|
968
|
+
last=float(tdata[0]['lastPrice'])
|
969
|
+
else:
|
970
|
+
last=0.0
|
971
|
+
ddata = await self.bitunixApi.GetDepthData(symbol,"1")
|
972
|
+
if tdata:
|
973
|
+
bid = float(ddata['bids'][0][0])
|
974
|
+
ask = float(ddata['asks'][0][0])
|
975
|
+
else:
|
976
|
+
bid=0.0
|
977
|
+
ask=0.0
|
978
|
+
pdata = await self.bitunixApi.GetTickersPair(symbol)
|
979
|
+
if pdata:
|
980
|
+
mtv=float(pdata[0]['minTradeVolume'])
|
981
|
+
else:
|
982
|
+
mtv=0.0
|
983
|
+
tickerObj = self.tickerObjects.get(symbol)
|
984
|
+
if tickerObj:
|
985
|
+
tickerObj.set_last(last)
|
986
|
+
tickerObj.set_bid(bid)
|
987
|
+
tickerObj.set_ask(ask)
|
988
|
+
tickerObj.set_mtv(mtv)
|
989
|
+
del tdata, ddata, pdata, tickerObj
|
990
|
+
gc.collect()
|
991
|
+
return last,bid,ask,mtv
|
992
|
+
|
993
|
+
async def ProcessPrivateData(self, message):
|
994
|
+
if message=="":
|
995
|
+
return
|
996
|
+
try:
|
997
|
+
feed = json.loads(message)
|
998
|
+
if 'ch' not in feed:
|
999
|
+
return
|
1000
|
+
|
1001
|
+
channel = feed['ch']
|
1002
|
+
data = feed['data']
|
1003
|
+
|
1004
|
+
if channel == 'order':
|
1005
|
+
|
1006
|
+
symbol = data['symbol']
|
1007
|
+
qty = data['qty']
|
1008
|
+
side = data['side']
|
1009
|
+
price = data['price']
|
1010
|
+
event = data['event']
|
1011
|
+
orderStatus = data['orderStatus']
|
1012
|
+
self.notifications.add_notification(
|
1013
|
+
f'{colors.LBLUE} {orderStatus} {"short" if side=="Sell" else "long"} order for {symbol} with {qty} qty (event: {event})'
|
1014
|
+
)
|
1015
|
+
|
1016
|
+
elif channel == 'balance':
|
1017
|
+
|
1018
|
+
self.available = data['available']
|
1019
|
+
self.margin = data['margin']
|
1020
|
+
# logger.info(feed)
|
1021
|
+
|
1022
|
+
elif channel == 'position':
|
1023
|
+
|
1024
|
+
ts = int(feed['ts'])
|
1025
|
+
symbol = data['symbol']
|
1026
|
+
qty = float(data['qty'])
|
1027
|
+
side = data['side']
|
1028
|
+
positionId = data['positionId']
|
1029
|
+
event = data['event']
|
1030
|
+
entryValue = float(data['entryValue'])
|
1031
|
+
price = entryValue / qty if entryValue != 0 and qty != 0 else 0
|
1032
|
+
|
1033
|
+
if event == "OPEN":
|
1034
|
+
self.notifications.add_notification(
|
1035
|
+
f'{colors.PURPLE} Opened {side} position for {symbol} with {qty} qty @ {price}'
|
1036
|
+
)
|
1037
|
+
|
1038
|
+
elif event == "CLOSE":
|
1039
|
+
datajs = await self.bitunixApi.GetPositionHistoryData({'positionId': positionId})
|
1040
|
+
if datajs and len(datajs['positionList']) == 1:
|
1041
|
+
position = datajs['positionList'][0]
|
1042
|
+
profit = float(position['realizedPNL'])
|
1043
|
+
price = float(position['closePrice'])
|
1044
|
+
qty = float(position['maxQty'])
|
1045
|
+
self.profit += profit
|
1046
|
+
self.notifications.add_notification(
|
1047
|
+
f'{colors.GREEN if profit>0 else colors.RED} Closed {side} position for {symbol} with {qty} qty @ {price} and {"profit" if profit>0 else "loss"} of {profit}'
|
1048
|
+
)
|
1049
|
+
del datajs
|
1050
|
+
gc.collect()
|
1051
|
+
self.tickerObjects.get(symbol).trades.append({'ctime': ts, 'symbol': symbol, 'qty': qty, 'side': side, 'price': price})
|
1052
|
+
del feed
|
1053
|
+
gc.collect()
|
1054
|
+
except Exception as e:
|
1055
|
+
logger.info(f"Function: ProcessPrivateData, {e}, {e.args}, {type(e).__name__}")
|
1056
|
+
|
1057
|
+
def color_cells(val, color):
|
1058
|
+
return f'background-color: {color}' if val else ''
|
1059
|
+
|
1060
|
+
def add_charts_button(self, row):
|
1061
|
+
return f'<button onclick="handleChartsButton(\'{row["symbol"]}\')">charts</button>'
|
1062
|
+
|
1063
|
+
def add_bitunix_button(self, row):
|
1064
|
+
return f'<button onclick="handleBitunixButton(\'{row["symbol"]}\')">show</button>'
|
1065
|
+
|
1066
|
+
def add_buy_button(self, row):
|
1067
|
+
return f'<button onclick="handleBuyButton(\'{row["symbol"]}\',\'{row["last"]}\')">buy</button>'
|
1068
|
+
|
1069
|
+
def add_add_button(self, row):
|
1070
|
+
return f'<button onclick="handleAddButton(\'{row["symbol"]}\',\'{row["last"]}\')">add</button>'
|
1071
|
+
|
1072
|
+
def add_reduce_button(self, row):
|
1073
|
+
return f'<button onclick="handleReduceButton(\'{row["symbol"]}\',\'{row["positionId"]}\',\'{row["qty"]}\',\'{row["last"]}\')">reduce</button>'
|
1074
|
+
|
1075
|
+
def add_sell_button(self, row):
|
1076
|
+
return f'<button onclick="handleSellButton(\'{row["symbol"]}\',\'{row["last"]}\')">sell</button>'
|
1077
|
+
|
1078
|
+
def add_close_button(self, row):
|
1079
|
+
return f'<button onclick="handleCloseButton(\'{row["symbol"]}\',\'{row["positionId"]}\',\'{row["qty"]}\',\'{row["unrealizedPNL"]}\',\'{row["realizedPNL"]}\')">FlashClose</button>'
|
1080
|
+
|
1081
|
+
def add_order_close_button(self, row):
|
1082
|
+
return f'<button onclick="handleOrderCloseButton(\'{row["symbol"]}\',\'{row["orderId"]}\')">close</button>'
|
1083
|
+
|
1084
|
+
async def send_message_to_websocket(self,message):
|
1085
|
+
async def send_to_all():
|
1086
|
+
for ws in self.websocket_connections:
|
1087
|
+
await ws.send_text(message)
|
1088
|
+
asyncio.run(send_to_all())
|
1089
|
+
|
1090
|
+
async def get_portfolio_tradable_balance(self):
|
1091
|
+
return float(self.portfoliodf["available"])+float(self.portfoliodf["crossUnrealizedPNL"])
|
1092
|
+
|
1093
|
+
async def convert_defaultdict_to_dict(self, d):
|
1094
|
+
if isinstance(d, defaultdict):
|
1095
|
+
d = {k: self.convert_defaultdict_to_dict(v) for k, v in d.items()}
|
1096
|
+
return d
|
1097
|
+
|
1098
|
+
|
1099
|
+
|