bitunix-automated-crypto-trading 2.6.0__py3-none-any.whl → 2.6.1__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 (35) hide show
  1. bitunix_automated_crypto_trading/AsyncThreadRunner.py +81 -0
  2. bitunix_automated_crypto_trading/BitunixApi.py +278 -0
  3. bitunix_automated_crypto_trading/BitunixSignal.py +1099 -0
  4. bitunix_automated_crypto_trading/BitunixWebSocket.py +254 -0
  5. bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +74 -0
  6. bitunix_automated_crypto_trading/NotificationManager.py +23 -0
  7. bitunix_automated_crypto_trading/ResourceManager.py +35 -0
  8. bitunix_automated_crypto_trading/ThreadManager.py +69 -0
  9. bitunix_automated_crypto_trading/TickerManager.py +636 -0
  10. bitunix_automated_crypto_trading/__init__.py +1 -0
  11. bitunix_automated_crypto_trading/bitunix.py +593 -0
  12. bitunix_automated_crypto_trading/clearenv.py +8 -0
  13. bitunix_automated_crypto_trading/config.py +90 -0
  14. bitunix_automated_crypto_trading/config.txt +60 -0
  15. bitunix_automated_crypto_trading/logger.py +85 -0
  16. bitunix_automated_crypto_trading/sampleenv.txt +5 -0
  17. bitunix_automated_crypto_trading/static/chart.css +28 -0
  18. bitunix_automated_crypto_trading/static/chart.js +362 -0
  19. bitunix_automated_crypto_trading/static/modal.css +68 -0
  20. bitunix_automated_crypto_trading/static/modal.js +147 -0
  21. bitunix_automated_crypto_trading/static/script.js +166 -0
  22. bitunix_automated_crypto_trading/static/styles.css +118 -0
  23. bitunix_automated_crypto_trading/templates/charts.html +98 -0
  24. bitunix_automated_crypto_trading/templates/login.html +19 -0
  25. bitunix_automated_crypto_trading/templates/main.html +551 -0
  26. bitunix_automated_crypto_trading/templates/modal-chart.html +26 -0
  27. bitunix_automated_crypto_trading/templates/modal-config.html +34 -0
  28. bitunix_automated_crypto_trading/templates/modal-logs.html +15 -0
  29. {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/METADATA +1 -1
  30. bitunix_automated_crypto_trading-2.6.1.dist-info/RECORD +33 -0
  31. bitunix_automated_crypto_trading-2.6.1.dist-info/top_level.txt +1 -0
  32. bitunix_automated_crypto_trading-2.6.0.dist-info/RECORD +0 -5
  33. bitunix_automated_crypto_trading-2.6.0.dist-info/top_level.txt +0 -1
  34. {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/WHEEL +0 -0
  35. {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,636 @@
1
+ import pandas as pd
2
+ #pd.set_option('future.no_silent_downcasting', True)
3
+ import numpy as np
4
+ import asyncio
5
+ import talib
6
+ import traceback
7
+ from logger import Logger
8
+ logger = Logger(__name__).get_logger()
9
+ import gc
10
+ from concurrent.futures import ProcessPoolExecutor
11
+
12
+ class Interval:
13
+ def __init__(self, symbol, intervalId, delta, data, settings):
14
+ self.symbol = symbol
15
+ self.intervalId = intervalId
16
+ self.delta = delta
17
+ self.settings=settings
18
+ self._data = data
19
+
20
+ #these signals are used to list stocks in the signals sections on the main page
21
+ self.current_signal="HOLD"
22
+ self.ema_signal="HOLD"
23
+ self.macd_signal="HOLD"
24
+ self.bbm_signal="HOLD"
25
+ self.rsi_signal="HOLD"
26
+ self.candle_trend="HOLD"
27
+ self.adx_signal="WEAK"
28
+
29
+ self.signal_strength=0
30
+
31
+ def update_settings(self, settings):
32
+ self.settings=settings
33
+
34
+ def get_data(self):
35
+ return self._data
36
+
37
+ def set_data(self, new_value):
38
+ self._data = self.calculate_study(new_value)
39
+
40
+ def calculate_study(self, new_value):
41
+ df = pd.DataFrame(new_value)
42
+ if not df.empty and df.shape[0] >= int(self.settings.BARS):
43
+
44
+ try:
45
+ #consecutive same color candle
46
+ df['Group'] = (df['barcolor'] != df['barcolor'].shift()).cumsum()
47
+ df['Consecutive'] = df.groupby('Group').cumcount() + 1
48
+
49
+ # Get the last color and its consecutive count
50
+ self.signal_strength = df['Consecutive'].iloc[-1]
51
+
52
+ # Calculate the Moving Averages
53
+ if self.settings.EMA_STUDY:
54
+ df['ma_fast'] = talib.EMA(df['close'], timeperiod=self.settings.MA_FAST)
55
+ df['ma_fast'] = df['ma_fast'].bfill()
56
+ df.fillna({'ma_fast':0}, inplace=True)
57
+
58
+ df['ma_slow'] = talib.EMA(df['close'], timeperiod=self.settings.MA_SLOW)
59
+ df['ma_slow'] = df['ma_slow'].bfill()
60
+ df.fillna({'ma_slow':0}, inplace=True)
61
+
62
+ df['ma_medium'] = talib.EMA(df['close'], timeperiod=self.settings.MA_MEDIUM)
63
+ df['ma_medium'] = df['ma_medium'].bfill()
64
+ df.fillna({'ma_medium':0}, inplace=True)
65
+
66
+ df['ma_slope'] = df['ma_medium'].diff()
67
+ df['ma_angle'] = np.degrees(np.arctan(df['ma_slope']))
68
+ df.fillna({'ma_slope':0}, inplace=True)
69
+ df.fillna({'ma_angle':0}, inplace=True)
70
+
71
+ if self.settings.EMA_CROSSING:
72
+ if df['ma_fast'].iloc[-2] <= df['ma_medium'].iloc[-2] and df['ma_fast'].iloc[-1] > df['ma_medium'].iloc[-1]:
73
+ self.ema_signal = "BUY"
74
+ elif df['ma_fast'].iloc[-2] >= df['ma_medium'].iloc[-2] and df['ma_fast'].iloc[-1] <= df['ma_medium'].iloc[-1]:
75
+ self.ema_signal = "SELL"
76
+ else:
77
+ self.ema_signal = "HOLD"
78
+ else:
79
+ if df['close'].iloc[-1] > df['ma_fast'].iloc[-1] and df['ma_fast'].iloc[-1] > df['ma_medium'].iloc[-1]:
80
+ self.ema_signal = "BUY"
81
+ elif df['close'].iloc[-1] < df['ma_fast'].iloc[-1] and df['ma_fast'].iloc[-1] < df['ma_medium'].iloc[-1]:
82
+ self.ema_signal = "SELL"
83
+ else:
84
+ self.ema_signal = "HOLD"
85
+ else:
86
+ # Drop EMA columns if not used
87
+ df.drop(['ma_fast', 'ma_medium', 'ma_slow', 'ma_slope', 'ma_angle'], axis=1, inplace=True, errors='ignore')
88
+
89
+ # Calculate the MACD Line
90
+ if self.settings.MACD_STUDY:
91
+ df['MACD_Line'] = 0.0
92
+ df['MACD_Signal'] = 0.0
93
+ df['MACD_Histogram'] = 0.0
94
+ df['MACD_Line'], df['MACD_Signal'], df['MACD_Histogram'] = talib.MACD(df['close'], fastperiod=self.settings.MACD_SHORT, slowperiod=self.settings.MACD_LONG, signalperiod=self.settings.MACD_PERIOD)
95
+ df.fillna({'MACD_Line':0}, inplace=True)
96
+ df.fillna({'MACD_Signal':0}, inplace=True)
97
+ df.fillna({'MACD_Histogram':0}, inplace=True)
98
+
99
+ df['MACD_slope'] = df['MACD_Signal'].diff()
100
+ df['MACD_angle'] = np.degrees(np.arctan(df['MACD_slope']))
101
+ df.fillna({'MACD_slope':0}, inplace=True)
102
+ df.fillna({'MACD_angle':0}, inplace=True)
103
+
104
+ if self.settings.MACD_CROSSING:
105
+ if df['MACD_Line'].iloc[-2] <= df['MACD_Signal'].iloc[-2] and df['MACD_Line'].iloc[-1] > df['MACD_Signal'].iloc[-1]:
106
+ self.macd_signal = "BUY"
107
+ elif df['MACD_Line'].iloc[-2] >= df['MACD_Signal'].iloc[-2] and df['MACD_Line'].iloc[-1] < df['MACD_Signal'].iloc[-1]:
108
+ self.macd_signal = "SELL"
109
+ else:
110
+ self.macd_signal = "HOLD"
111
+ else:
112
+ if df['MACD_Line'].iloc[-1] > df['MACD_Signal'].iloc[-1]:
113
+ self.macd_signal = "BUY"
114
+ elif df['MACD_Line'].iloc[-1] < df['MACD_Signal'].iloc[-1]:
115
+ self.macd_signal = "SELL"
116
+ else:
117
+ self.macd_signal = "HOLD"
118
+ else:
119
+ # Drop MACD columns if not used
120
+ df.drop(['MACD_Line', 'MACD_Signal', 'MACD_Histogram', 'MACD_slope', 'MACD_angle'], axis=1, inplace=True, errors='ignore')
121
+
122
+ # Calculate Bollinger Bands
123
+ if self.settings.BBM_STUDY:
124
+ df['BBL'] = 0.0
125
+ df['BBM'] = 0.0
126
+ df['BBU'] = 0.0
127
+ df['BBU'], df['BBM'], df['BBL'] = talib.BBANDS(df['close'], timeperiod=self.settings.BBM_PERIOD, nbdevup=self.settings.BBM_STD, nbdevdn=self.settings.BBM_STD, )
128
+ df.fillna({'BBL':0}, inplace=True)
129
+ df.fillna({'BBM':0}, inplace=True)
130
+ df.fillna({'BBU':0}, inplace=True)
131
+
132
+ df['BBM_slope'] = df['BBM'].diff()
133
+ df['BBM_angle'] = np.degrees(np.arctan(df['BBM_slope']))
134
+ df.fillna({'BBM_slope':0}, inplace=True)
135
+ df.fillna({'BBM_angle':0}, inplace=True)
136
+
137
+ if self.settings.BBM_CROSSING:
138
+ if df['close'].iloc[-2] <= df['BBM'].iloc[-2] and df['close'].iloc[-1] > df['BBM'].iloc[-1]:
139
+ self.bbm_signal = "BUY"
140
+ elif df['close'].iloc[-2] >= df['BBM'].iloc[-2] and df['close'].iloc[-1] < df['BBM'].iloc[-1]:
141
+ self.bbm_signal = "SELL"
142
+ else:
143
+ self.bbm_signal = "HOLD"
144
+ else:
145
+ if df['close'].iloc[-1] > df['BBM'].iloc[-1] and df['close'].iloc[-2] > df['BBM'].iloc[-2]:
146
+ self.bbm_signal = "BUY"
147
+ elif df['close'].iloc[-1] < df['BBM'].iloc[-1] and df['close'].iloc[-2] < df['BBM'].iloc[-2]:
148
+ self.bbm_signal = "SELL"
149
+ else:
150
+ self.bbm_signal = "HOLD"
151
+ else:
152
+ # Drop BBM columns if not used
153
+ df.drop(['BBL', 'BBM', 'BBU', 'BBM_slope', 'BBM_angle'], axis=1, inplace=True, errors='ignore')
154
+
155
+ # Calculate the RSI
156
+ if self.settings.RSI_STUDY:
157
+ df['rsi_fast'] = talib.RSI(df['close'],timeperiod=self.settings.RSI_FAST)
158
+ df.fillna({'rsi_fast':0}, inplace=True)
159
+
160
+ df['rsi_slow'] = talib.RSI(df['close'],timeperiod=self.settings.RSI_SLOW)
161
+ df.fillna({'rsi_slow':0}, inplace=True)
162
+
163
+ df['rsi_slope'] = df['rsi_fast'].diff()
164
+ df['rsi_angle'] = np.degrees(np.arctan(df['rsi_slope']))
165
+ df.fillna({'rsi_slope':0}, inplace=True)
166
+ df.fillna({'rsi_angle':0}, inplace=True)
167
+
168
+ if self.settings.RSI_CROSSING:
169
+ if df['rsi_fast'].iloc[-2] <= df['rsi_slow'].iloc[-2] and df['rsi_fast'].iloc[-1] > df['rsi_slow'].iloc[-1]:
170
+ self.rsi_signal = "BUY"
171
+ elif df['rsi_fast'].iloc[-2] >= df['rsi_slow'].iloc[-2] and df['rsi_fast'].iloc[-1] < df['rsi_slow'].iloc[-1]:
172
+ self.rsi_signal = "SELL"
173
+ else:
174
+ self.rsi_signal = "HOLD"
175
+ else:
176
+ if df['rsi_fast'].iloc[-1] > df['rsi_slow'].iloc[-1]:
177
+ self.rsi_signal = "BUY"
178
+ elif df['rsi_fast'].iloc[-1] < df['rsi_slow'].iloc[-1]:
179
+ self.rsi_signal = "SELL"
180
+ else:
181
+ self.rsi_signal = "HOLD"
182
+ else:
183
+ # Drop RSI columns if not used
184
+ df.drop(['rsi_fast', 'rsi_slow', 'rsi_slope', 'rsi_angle'], axis=1, inplace=True, errors='ignore')
185
+
186
+ # Calculate the ADX
187
+ if self.settings.ADX_STUDY:
188
+ df['ADX'] = talib.ADX(df['high'], df['low'], df['close'], timeperiod=self.settings.ADX_PERIOD)
189
+ df.fillna({'ADX':0}, inplace=True)
190
+ if df['ADX'].iloc[-1] > 25:
191
+ self.adx_signal = "STRONG"
192
+ else:
193
+ self.adx_signal = "WEAK"
194
+ else:
195
+ # Drop ADX columns if not used
196
+ df.drop(['ADX'], axis=1, inplace=True, errors='ignore')
197
+
198
+ # Calculate the close proximity
199
+ if self.settings.CANDLE_TREND_STUDY:
200
+ df['range'] = df['high'] - df['low']
201
+ df['candle_trend'] = ((df['close'] - df['low'])/df['range'])*100
202
+ df.fillna({'candle_trend':0}, inplace=True)
203
+ df.fillna({'range':0}, inplace=True)
204
+
205
+ #open and close criteria
206
+ if df['candle_trend'].iloc[-1] > 70:
207
+ self.candle_trend = 'BULLISH'
208
+ elif df['candle_trend'].iloc[-1] < 30:
209
+ self.candle_trend = 'BEARISH'
210
+ else:
211
+ self.candle_trend = 'HOLD'
212
+ else:
213
+ # Drop candle trend columns if not used
214
+ df.drop(['candle_trend', 'range'], axis=1, inplace=True, errors='ignore')
215
+
216
+
217
+ #replace infinity
218
+ df.replace([np.inf, -np.inf], 0, inplace=True)
219
+
220
+ if self.settings.OPEN_ON_ANY_SIGNAL:
221
+ # If EMA is enabled and crossing or MACD is enabled and crossing or BBM is enabled and crossing or RSI is enbabled and crossing
222
+ # and
223
+ # ADX is enabled and strong and candle trend is enabled and bullish
224
+ # then BUY or SELL
225
+
226
+ # Check for BUY signal
227
+ buy_conditions = (
228
+ (not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_signal == "BUY") or
229
+ (not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "BUY") or
230
+ (not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "BUY") or
231
+ (not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "BUY")
232
+ )
233
+ additional_buy_conditions = (
234
+ (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
235
+ (not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BULLISH")
236
+ )
237
+
238
+ # Check for SELL signal
239
+ sell_conditions = (
240
+ (not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_signal == "SELL") or
241
+ (not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "SELL") or
242
+ (not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "SELL") or
243
+ (not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "SELL")
244
+ )
245
+ additional_sell_conditions = (
246
+ (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
247
+ (not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BEARISH")
248
+ )
249
+
250
+ # Determine current signal
251
+ if buy_conditions and additional_buy_conditions:
252
+ self.current_signal = "BUY"
253
+ elif sell_conditions and additional_sell_conditions:
254
+ self.current_signal = "SELL"
255
+ else:
256
+ self.current_signal = "HOLD"
257
+ else:
258
+ # If EMA is enabled and crossing and MACD is enabled and crossing and BBM is enabled and crossing and RSI is enbabled and crossing
259
+ # and
260
+ # ADX is enabled and strong and candle trend is enabled and bullish
261
+ # then BUY or SELL
262
+ buy_conditions = (
263
+ (not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_signal == "BUY") and
264
+ (not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "BUY") and
265
+ (not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "BUY") and
266
+ (not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "BUY")
267
+ )
268
+ additional_buy_conditions = (
269
+ (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
270
+ (not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BULLISH")
271
+ )
272
+
273
+ # Check for SELL signal
274
+ sell_conditions = (
275
+ (not self.settings.EMA_STUDY or not self.settings.EMA_CHECK_ON_OPEN or self.ema_signal == "SELL") and
276
+ (not self.settings.MACD_STUDY or not self.settings.MACD_CHECK_ON_OPEN or self.macd_signal == "SELL") and
277
+ (not self.settings.BBM_STUDY or not self.settings.BBM_CHECK_ON_OPEN or self.bbm_signal == "SELL") and
278
+ (not self.settings.RSI_STUDY or not self.settings.RSI_CHECK_ON_OPEN or self.rsi_signal == "SELL")
279
+ )
280
+ additional_sell_conditions = (
281
+ (not self.settings.ADX_STUDY or not self.settings.ADX_CHECK_ON_OPEN or self.adx_signal == "STRONG") and
282
+ (not self.settings.CANDLE_TREND_STUDY or not self.settings.CANDLE_TREND_CHECK_ON_OPEN or self.candle_trend == "BEARISH")
283
+ )
284
+
285
+ # Determine current signal
286
+ if buy_conditions and additional_buy_conditions:
287
+ self.current_signal = "BUY"
288
+ elif sell_conditions and additional_sell_conditions:
289
+ self.current_signal = "SELL"
290
+ else:
291
+ self.current_signal = "HOLD"
292
+
293
+
294
+ except Exception as e:
295
+ logger.info(f"Function: calculate_study, {e}, {e.args}, {type(e).__name__}")
296
+ logger.info(traceback.logger.info_exc())
297
+ retval = df.to_dict('records')
298
+ del df
299
+ gc.collect()
300
+ return retval
301
+
302
+ class Ticker:
303
+
304
+ def __init__(self, symbol, intervalIds, settings):
305
+ self.symbol=symbol
306
+ self.settings=settings
307
+
308
+ self._ts=0
309
+ self._bid = 0.0
310
+ self.bidcolor = ""
311
+ self._last = 0.0
312
+ self.lastcolor = ""
313
+ self._ask = 0.0
314
+ self.askcolor = ""
315
+ self._mtv=0.0
316
+ self.intervalIds=intervalIds
317
+ self._intervals = {
318
+ '1m' : Interval(self.symbol, '1m', 60000, [], self.settings),
319
+ '5m' : Interval(self.symbol, '5m', 300000, [], self.settings),
320
+ '15m': Interval(self.symbol, '15m', 900000, [], self.settings),
321
+ '1h' : Interval(self.symbol, '1h', 3600000, [], self.settings),
322
+ '1d' : Interval(self.symbol, '1d', 86400000, [], self.settings),
323
+ }
324
+ self.current_data={}
325
+ self.trades = []
326
+
327
+ self.green="#A5DFDF"
328
+ self.red="#FFB1C1"
329
+
330
+ def update_settings(self, settings):
331
+ self.settings=settings
332
+ for intervalId in self.intervalIds:
333
+ self._intervals[intervalId].update_settings(settings)
334
+
335
+ def get_bid(self):
336
+ return self._bid
337
+
338
+ def set_bid(self, value):
339
+ self.bidcolor = self.green if value >= self._bid else self.red
340
+ self._bid = value
341
+
342
+ def get_ask(self):
343
+ return self._ask
344
+
345
+ def set_ask(self, value):
346
+ self.askcolor = self.green if value >= self._ask else self.red
347
+ self._ask = value
348
+
349
+ def get_last(self):
350
+ return self._last
351
+
352
+ def set_last(self, last):
353
+ self.lastcolor = self.green if last >= self._last else self.red
354
+ self._last = last
355
+
356
+ def set_mtv(self, value):
357
+ self._mtv = value
358
+
359
+ def set_24hrData(self, highest,lowest,volume,volumeInCurrency):
360
+ self.highest = highest
361
+ self.lowest = lowest
362
+ self.volume = volume
363
+ self.volumeInCurrency = volumeInCurrency
364
+
365
+ def form_candle(self, last, ts):
366
+ self.lastcolor = self.green if last >= self._last else self.red
367
+ self._last = last
368
+ self._ts = ts
369
+ for intervalId in self.intervalIds:
370
+ self.create_bar_with_last_and_ts( intervalId)
371
+ return (self._last, self.lastcolor, self._ts, self.get_intervals())
372
+
373
+ async def set_bidlastask(self, bid, last, ask):
374
+ self.bidcolor = self.green if bid >= self._bid else self.red
375
+ self._bid = bid
376
+ self.lastcolor = self.green if last >= self._last else self.red
377
+ self._last = last
378
+ self.askcolor = self.green if ask >= self._ask else self.red
379
+ self._ask = ask
380
+
381
+ def get_intervals(self):
382
+ return self._intervals
383
+
384
+ def set_intervals(self, intervals):
385
+ self._intervals = intervals
386
+
387
+ def get_interval_ticks(self, intervalId):
388
+ return self._intervals.get(intervalId, None)
389
+
390
+ def set_interval_ticks(self, intervalId, new_value):
391
+ self._intervals[intervalId] = new_value
392
+
393
+ def create_bar_with_last_and_ts(self, intervalId):
394
+ if self._last==0:
395
+ return
396
+ try:
397
+ current_bar={}
398
+ new_item = {
399
+ 'open': self._last,
400
+ 'high': self._last,
401
+ 'low': self._last,
402
+ 'close': self._last,
403
+ 'quoteVol': 0.0,
404
+ 'baseVol': 0.0,
405
+ 'time': self._ts,
406
+ 'barcolor': "",
407
+ 'last':0.0,
408
+ 'ma_fast':0.0,
409
+ 'ma_slow':0.0,
410
+ 'ma_medium':0.0,
411
+ 'rsi_fast':0.0,
412
+ 'rsi_slow':0.0,
413
+ 'MACD_Line':0.0,
414
+ 'MACD_Signal':0.0,
415
+ 'MACD_Histogram':0.0,
416
+ 'BBU':0.0,
417
+ 'BBM':0.0,
418
+ 'BBL':0.0
419
+ }
420
+
421
+ intervalObj = self.get_interval_ticks(intervalId)
422
+ if intervalObj is None:
423
+ return
424
+ ticks_interval=intervalObj.get_data()
425
+ if ticks_interval is None:
426
+ return
427
+ if len(ticks_interval)==0 or self._ts - ticks_interval[-1]['time'] >= intervalObj.delta:
428
+ ticks_interval.append(new_item)
429
+ current_bar = ticks_interval[-1]
430
+ if len(ticks_interval) > int(self.settings.BARS):
431
+ ticks_interval.pop(0)
432
+ #print(f'{self.symbol} {self._last}, {self._ts} {intervalId} {ticks_interval[-1]['time']} n' )
433
+ else:
434
+ current_bar = ticks_interval[-1]
435
+ current_bar['high'] = max(current_bar['high'], self._last)
436
+ current_bar['low'] = min(current_bar['low'], self._last)
437
+ current_bar['close'] = self._last
438
+ current_bar['barcolor'] = self.red if current_bar['close'] <= current_bar['open'] else self.green
439
+ #print(f'{self.symbol} {self._last}, {self._ts} {intervalId} {ticks_interval[-1]['time']} c' )
440
+
441
+ intervalObj.set_data(ticks_interval)
442
+
443
+ except Exception as e:
444
+ logger.info(f"Function: create_bar_with_last_and_ts, {e}, {e.args}, {type(e).__name__}")
445
+ logger.info(traceback.logger.info_exc())
446
+
447
+
448
+ class Tickers:
449
+ def __init__(self, settings):
450
+ self.settings=settings
451
+
452
+ self._tickerObjects={}
453
+
454
+ self.green="#A5DFDF" #light green
455
+ self.red="#FFB1C1" #light red
456
+
457
+ self.signaldf_full = pd.DataFrame()
458
+ self.signaldf_filtered = pd.DataFrame()
459
+
460
+ self.intervalIds=['1m','5m','15m','1h','1d']
461
+
462
+ def update_settings(self, settings):
463
+ self.settings=settings
464
+ for symbol in self._tickerObjects:
465
+ self._tickerObjects[symbol].update_settings(settings)
466
+
467
+ def add(self, symbol):
468
+ ticker = Ticker(symbol, self.intervalIds, self.settings)
469
+ self._tickerObjects[symbol]=ticker
470
+
471
+ def get_tickerDict(self):
472
+ return self._tickerObjects
473
+
474
+ def get(self, symbol):
475
+ return self._tickerObjects.get(symbol, None)
476
+
477
+ def get_intervalIds(self):
478
+ return self.intervalIds
479
+
480
+ def get_interval(self, symbol, intervalId):
481
+ tickerObj = self.get(symbol)
482
+ if tickerObj:
483
+ return tickerObj.get_interval_ticks(intervalId)
484
+ else:
485
+ return None
486
+
487
+ def setTrades(self, symbol, trades):
488
+ tickerObj = self.get(symbol)
489
+ if tickerObj:
490
+ tickerObj.trades = trades
491
+
492
+ def symbols(self):
493
+ return list(self._tickerObjects.keys())
494
+
495
+ def load_kline_history(self, symbol, intervalId, bars, data=None):
496
+ tickerObj=self.get(symbol)
497
+ if tickerObj:
498
+ intervalObj = tickerObj.get_interval_ticks(intervalId)
499
+ ticks_interval=[]
500
+
501
+ for item in data:
502
+ item["barcolor"] = self.green if item['open'] <= item['close'] else self.red
503
+ item.update({
504
+ 'open': float(item['open']),
505
+ 'high': float(item['high']),
506
+ 'low': float(item['low']),
507
+ 'close': float(item['close']),
508
+ 'last': 0,
509
+ 'quoteVol': float(item['quoteVol']),
510
+ 'baseVol': float(item['baseVol']),
511
+ 'time': int(item["time"])
512
+ })
513
+ ticks_interval.append(item)
514
+
515
+ empty_item = {
516
+ "barcolor": "",
517
+ 'open': 0.0,
518
+ 'high': 0.0,
519
+ 'low': 0.0,
520
+ 'close': 0.0,
521
+ 'last': 0,
522
+ 'quoteVol': 0.0,
523
+ 'baseVol': 0.0,
524
+ 'time': 0
525
+ }
526
+ ticks_interval.extend([empty_item] * (bars - len(ticks_interval)))
527
+
528
+ # Reverse the list of dictionaries in ascending
529
+ ticks_interval = ticks_interval[::-1]
530
+
531
+ loop = asyncio.get_event_loop()
532
+ loop.run_in_executor(None, intervalObj.set_data, ticks_interval)
533
+ del ticks_interval, empty_item, intervalObj, tickerObj, loop
534
+ gc.collect()
535
+
536
+ def process_ticker_candle(self,args):
537
+ ticker_obj, last, ts = args # Unpack the tuple
538
+ return ticker_obj.form_candle(last, ts)
539
+
540
+ # this uses cpu to process all ticker study when new lastprice arrives,
541
+ # since the calulation is done at a different thread and uses a different memory space,
542
+ # the caluated value has to be reassigned to the original ticker class instance from the caluated ticker instance
543
+ def form_candle(self, tuples_list):
544
+ with ProcessPoolExecutor() as executor:
545
+ results = executor.map(self.process_ticker_candle, tuples_list, chunksize=10)
546
+ for args, result in zip(tuples_list, results):
547
+ for intervalId, interval in result[3].items():
548
+ if not interval._data is None:
549
+ args[0].get_interval_ticks(intervalId)._data = interval._data
550
+ args[0].get_interval_ticks(intervalId).current_signal = interval.current_signal
551
+ args[0].get_interval_ticks(intervalId).ema_signal = interval.ema_signal
552
+ args[0].get_interval_ticks(intervalId).macd_signal = interval.macd_signal
553
+ args[0].get_interval_ticks(intervalId).bbm_signal = interval.bbm_signal
554
+ args[0].get_interval_ticks(intervalId).rsi_signal = interval.rsi_signal
555
+ args[0].get_interval_ticks(intervalId).candle_trend = interval.candle_trend
556
+ args[0].get_interval_ticks(intervalId).adx_signal = interval.adx_signal
557
+ args[0].get_interval_ticks(intervalId).signal_strength = interval.signal_strength
558
+ args[0]._last = result[0]
559
+ args[0].lastcolor = result[1]
560
+ args[0]._ts = result[2]
561
+
562
+ def getCurrentData(self, period):
563
+ current_data = []
564
+ df=pd.DataFrame()
565
+ try:
566
+ for symbol, tickerObj in self._tickerObjects.items():
567
+ bid = tickerObj.get_bid()
568
+ last = tickerObj.get_last()
569
+ ask = tickerObj.get_ask()
570
+ lastcolor = tickerObj.lastcolor
571
+ bidcolor = tickerObj.bidcolor
572
+ askcolor = tickerObj.askcolor
573
+
574
+ if period in tickerObj.intervalIds:
575
+ intervalObj = tickerObj.get_interval_ticks(period)
576
+ ticks = intervalObj.get_data()
577
+ if not ticks:
578
+ continue
579
+ if len(ticks)>=int(self.settings.BARS):
580
+ lastcandle = intervalObj.get_data()[-1]
581
+ if len(lastcandle) >= 20:
582
+ new_row = {
583
+ 'symbol' : symbol,
584
+ f"{period}_trend": intervalObj.current_signal,
585
+ f"{period}_cb": intervalObj.signal_strength,
586
+ f"{period}_barcolor": lastcandle['barcolor'],
587
+ f"{period}_ema": intervalObj.ema_signal,
588
+ f"{period}_macd":intervalObj.macd_signal,
589
+ f"{period}_bbm":intervalObj.bbm_signal,
590
+ f"{period}_rsi":intervalObj.rsi_signal,
591
+ f"{period}_adx":intervalObj.adx_signal,
592
+ f"{period}_candle_trend":intervalObj.candle_trend,
593
+ 'bid' : bid,
594
+ 'bidcolor' : bidcolor,
595
+ 'last' : last,
596
+ 'lastcolor' : lastcolor,
597
+ 'ask' : ask,
598
+ 'askcolor' : askcolor,
599
+ f"{period}_open": lastcandle['open'],
600
+ f"{period}_close": lastcandle['close'],
601
+ f"{period}_high": lastcandle['high'],
602
+ f"{period}_low": lastcandle['low'],
603
+ }
604
+ current_data.append(new_row)
605
+
606
+ df = pd.DataFrame(current_data)
607
+ if not df.empty:
608
+ fill_values = {
609
+ 'lastcolor': "", 'bidcolor': "", 'askcolor': "", 'bid': 0.0, 'ask': 0.0, 'last': 0.0,
610
+ f"{period}_cb":0, f"{period}_barcolor": "", f"{period}_trend": "",
611
+ f"{period}_open": 0.0, f"{period}_close": 0.0, f"{period}_high": 0.0, f"{period}_low": 0.0,
612
+ f"{period}_ema": "", f"{period}_macd": "", f"{period}_bbm": "", f"{period}_rsi": "", f"{period}_candle_trend": "", f"{period}_adx": ""
613
+ }
614
+ df.fillna(fill_values, inplace=True)
615
+ df.set_index("symbol", inplace=True, drop=False)
616
+ self.signaldf_full = df.copy().sort_values(by=[f'{period}_cb'], ascending=[False])
617
+ #signaldf only contain symbol that has cosecutive colored bar > 1 for buy or sell
618
+ trending_conditions = [
619
+ (
620
+ (df[f'{period}_trend']=='BUY') &
621
+ (df[f'{period}_cb']>1) &
622
+ (df[f'{period}_barcolor']==self.green)
623
+ ),
624
+ (
625
+ (df[f'{period}_trend']=='SELL') &
626
+ (df[f'{period}_cb']>1) &
627
+ (df[f'{period}_barcolor']==self.red)
628
+ )
629
+ ]
630
+ self.signaldf_filtered = df[np.any(trending_conditions, axis=0)].copy()
631
+ except Exception as e:
632
+ logger.info(f"Function: getCurrentData, {e}, {e.args}, {type(e).__name__}")
633
+ logger.info(traceback.logger.info_exc())
634
+ finally:
635
+ del df, current_data
636
+ gc.collect()
@@ -0,0 +1 @@
1
+ # This file is intentionally left empty to make the directory a Python package