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.
- bitunix_automated_crypto_trading/AsyncThreadRunner.py +81 -0
- bitunix_automated_crypto_trading/BitunixApi.py +278 -0
- bitunix_automated_crypto_trading/BitunixSignal.py +1099 -0
- bitunix_automated_crypto_trading/BitunixWebSocket.py +254 -0
- bitunix_automated_crypto_trading/DataFrameHtmlRenderer.py +74 -0
- bitunix_automated_crypto_trading/NotificationManager.py +23 -0
- bitunix_automated_crypto_trading/ResourceManager.py +35 -0
- bitunix_automated_crypto_trading/ThreadManager.py +69 -0
- bitunix_automated_crypto_trading/TickerManager.py +636 -0
- bitunix_automated_crypto_trading/__init__.py +1 -0
- bitunix_automated_crypto_trading/bitunix.py +593 -0
- bitunix_automated_crypto_trading/clearenv.py +8 -0
- bitunix_automated_crypto_trading/config.py +90 -0
- bitunix_automated_crypto_trading/config.txt +60 -0
- bitunix_automated_crypto_trading/logger.py +85 -0
- bitunix_automated_crypto_trading/sampleenv.txt +5 -0
- bitunix_automated_crypto_trading/static/chart.css +28 -0
- bitunix_automated_crypto_trading/static/chart.js +362 -0
- bitunix_automated_crypto_trading/static/modal.css +68 -0
- bitunix_automated_crypto_trading/static/modal.js +147 -0
- bitunix_automated_crypto_trading/static/script.js +166 -0
- bitunix_automated_crypto_trading/static/styles.css +118 -0
- bitunix_automated_crypto_trading/templates/charts.html +98 -0
- bitunix_automated_crypto_trading/templates/login.html +19 -0
- bitunix_automated_crypto_trading/templates/main.html +551 -0
- bitunix_automated_crypto_trading/templates/modal-chart.html +26 -0
- bitunix_automated_crypto_trading/templates/modal-config.html +34 -0
- bitunix_automated_crypto_trading/templates/modal-logs.html +15 -0
- {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/METADATA +1 -1
- bitunix_automated_crypto_trading-2.6.1.dist-info/RECORD +33 -0
- bitunix_automated_crypto_trading-2.6.1.dist-info/top_level.txt +1 -0
- bitunix_automated_crypto_trading-2.6.0.dist-info/RECORD +0 -5
- bitunix_automated_crypto_trading-2.6.0.dist-info/top_level.txt +0 -1
- {bitunix_automated_crypto_trading-2.6.0.dist-info → bitunix_automated_crypto_trading-2.6.1.dist-info}/WHEEL +0 -0
- {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
|