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.
Files changed (31) hide show
  1. bitunix_automated_crypto_trading/AsyncThreadRunner.py +81 -81
  2. bitunix_automated_crypto_trading/BitunixApi.py +278 -278
  3. bitunix_automated_crypto_trading/BitunixSignal.py +1099 -1099
  4. bitunix_automated_crypto_trading/BitunixWebSocket.py +254 -254
  5. bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +74 -74
  6. bitunix_automated_crypto_trading/NotificationManager.py +23 -23
  7. bitunix_automated_crypto_trading/ThreadManager.py +68 -68
  8. bitunix_automated_crypto_trading/TickerManager.py +635 -635
  9. bitunix_automated_crypto_trading/bitunix.py +597 -594
  10. bitunix_automated_crypto_trading/config.py +90 -90
  11. bitunix_automated_crypto_trading/logger.py +84 -84
  12. {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/METADATA +36 -36
  13. bitunix_automated_crypto_trading-2.6.8.dist-info/RECORD +17 -0
  14. bitunix_automated_crypto_trading/config.txt +0 -60
  15. bitunix_automated_crypto_trading/sampleenv.txt +0 -5
  16. bitunix_automated_crypto_trading/static/chart.css +0 -28
  17. bitunix_automated_crypto_trading/static/chart.js +0 -362
  18. bitunix_automated_crypto_trading/static/modal.css +0 -68
  19. bitunix_automated_crypto_trading/static/modal.js +0 -147
  20. bitunix_automated_crypto_trading/static/script.js +0 -166
  21. bitunix_automated_crypto_trading/static/styles.css +0 -118
  22. bitunix_automated_crypto_trading/templates/charts.html +0 -98
  23. bitunix_automated_crypto_trading/templates/login.html +0 -19
  24. bitunix_automated_crypto_trading/templates/main.html +0 -551
  25. bitunix_automated_crypto_trading/templates/modal-chart.html +0 -26
  26. bitunix_automated_crypto_trading/templates/modal-config.html +0 -34
  27. bitunix_automated_crypto_trading/templates/modal-logs.html +0 -15
  28. bitunix_automated_crypto_trading-2.6.7.dist-info/RECORD +0 -31
  29. {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/WHEEL +0 -0
  30. {bitunix_automated_crypto_trading-2.6.7.dist-info → bitunix_automated_crypto_trading-2.6.8.dist-info}/entry_points.txt +0 -0
  31. {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 .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
-
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
+