-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsignal_spot_bot.py
395 lines (323 loc) · 15.6 KB
/
signal_spot_bot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
import argparse
import json
import time
import datetime
import os.path
import trader.binance.account
import trader.binance.helper
import trader.binance.indicators
import trader.binance.trade
import trader.constants
import trader.helper
import trader.ssb.constants
import trader.ssb.helper
from tui.ssb_interface import TUI, LiveDataInfo
# Keep track of the individual config files
master_config_files = []
# Telegram user to be notified on buy or sell
telegram_chat_id = ''
telegram_api_token = ''
# Discord channel to be notified on buy or sell
discord_channel_id = ''
discord_api_token = ''
# If prevent_loss is enabled,
# make sure the profit is at least <MIN_PROFIT_PERCENT>
MIN_PROFIT_PERCENT = 6
BUY_SIGNAL_PERCENT = 100
SELL_SIGNAL_PERCENT = 80
BUY_SIGNAL_EMOJI = '💸'
SELL_SIGNAL_EMOJI = '🔔'
# For syncing with the TUI
live_data_points = {}
def update_and_save_config_file(config_instance):
instance_symbol = config_instance['base_currency'] + config_instance['target_currency']
for current_config_index in range(len(master_config_files)):
current_config = master_config_files[current_config_index]
current_symbol = current_config['base_currency'] + current_config['target_currency']
if instance_symbol == current_symbol:
master_config_files[current_config_index] = config_instance
trader.ssb.helper.write_config_file(master_config_files)
return
raise Exception(f'The symbol {instance_symbol} was not found in the {master_config_files}')
def is_telegram_enabled():
return telegram_chat_id != '' and telegram_api_token != ''
def is_discord_enabled():
return discord_channel_id != '' and discord_api_token != ''
def update_live_data_points(buy_on_next_trade, base_currency, target_currency, is_in_favor, current_price, last_operation_price, difference_in_percent, buy_signal, sell_signal, tui):
symbol = base_currency + target_currency
last_updated_time = datetime.datetime.now().strftime('%H:%M:%S')
live_data_points[f'{symbol}'] = LiveDataInfo(not buy_on_next_trade, base_currency, target_currency, is_in_favor, current_price, last_operation_price, difference_in_percent, f'{buy_signal} Buy - {sell_signal} Sell {BUY_SIGNAL_EMOJI * buy_signal}{SELL_SIGNAL_EMOJI * sell_signal}', f"{last_updated_time}")
tui.live_data.update_data_points(live_data_points.copy())
def perform_bot_operations(config, api_key, secret_key, tui):
base_currency = config['base_currency']
target_currency = config['target_currency']
buy_on_next_trade = config['buy_on_next_trade']
trade_amount_buy = config['trade_amount_buy']
trade_wealth_percent_sell = config['trade_wealth_percent_sell']
last_operation_price = config['last_operation_price']
prevent_loss = config['prevent_loss']
symbol = base_currency + target_currency
current_price = trader.binance.trade.get_current_trade_ratio(symbol)
total_indicator_count = 5
# Check the indicator signals
buy_signal = 0
sell_signal = 0
# RSI indicator
rsi_margin = 4.0
rsi = trader.binance.indicators.get_rsi(symbol, '4h', moving_average=0, data_count=14)
if rsi >= 70 - rsi_margin:
sell_signal += 1
elif rsi <= 30 + rsi_margin:
buy_signal += 1
# Bollinger bands indicator
(upper, _, lower) = trader.binance.indicators.get_bollinger_bands(symbol, '4h', 20)
if current_price > upper:
sell_signal += 1
elif current_price < lower:
buy_signal += 1
# Simple moving average
sma = trader.binance.indicators.get_sma(symbol, '4h', 9)
if current_price > sma:
sell_signal += 1
elif current_price < sma:
buy_signal += 1
# Exponential moving average (4h)
ema = trader.binance.indicators.get_ema(symbol, '4h', 9)
if current_price > ema:
sell_signal += 1
elif current_price < ema:
buy_signal += 1
# Exponential moving average (1d)
ema = trader.binance.indicators.get_ema(symbol, '1d', 9)
if current_price > ema:
sell_signal += 1
elif current_price < ema:
buy_signal += 1
if buy_on_next_trade:
if buy_signal > sell_signal:
is_in_favor = True
else:
is_in_favor = False
else:
if sell_signal > buy_signal:
is_in_favor = True
else:
is_in_favor = False
difference_in_percent = 100 * (current_price - last_operation_price) / last_operation_price
if buy_on_next_trade:
current_buy_signal_percent = 100 * buy_signal / total_indicator_count
if current_buy_signal_percent >= BUY_SIGNAL_PERCENT:
target_amount = trade_amount_buy
quantity = target_amount / current_price
result = trader.binance.trade.create_market_order(
api_key,
secret_key,
symbol,
'BUY',
quantity
)
if result['status'] != 'FILLED':
err_log = f"Response status was't FILLED, could't create BUY order for {symbol}, result was: {result}"
trader.ssb.helper.error_log(err_log, False)
tui.program_log.add_log(err_log)
update_live_data_points(buy_on_next_trade, base_currency, target_currency, is_in_favor, current_price, last_operation_price, difference_in_percent, buy_signal, sell_signal, tui)
return
buy_on_next_trade = False
last_operation_price = current_price
config['buy_on_next_trade'] = False
config['last_operation_price'] = current_price
update_and_save_config_file(config)
if 'executedQty' in result.keys():
quantity = result['executedQty']
if 'cummulativeQuoteQty' in result.keys():
target_amount = result['cummulativeQuoteQty']
log_str = f'Bought {quantity} {base_currency} for {target_amount} '\
f'{target_currency} ( {symbol} -> {current_price} )'
trader.ssb.helper.log(log_str, False)
tui.transaction_log.add_log(log_str)
if is_telegram_enabled():
trader.helper.notify_on_telegram(
telegram_api_token,
telegram_chat_id,
log_str
)
if is_discord_enabled():
trader.helper.notify_on_discord(
discord_api_token,
discord_channel_id,
log_str
)
else:
current_sell_signal_percent = 100 * sell_signal / total_indicator_count
if current_sell_signal_percent >= SELL_SIGNAL_PERCENT:
# If prevent_loss is enabled,
# make sure the profit is at least <MIN_PROFIT_PERCENT>
if (not prevent_loss) or (prevent_loss and (current_price >= last_operation_price + (last_operation_price * MIN_PROFIT_PERCENT / 100))):
# Create sell order
base_amount = trader.binance.account.get_free_balance_amount(
api_key,
secret_key,
base_currency
)
# Calculate total amount that we can trade
base_amount = base_amount * trade_wealth_percent_sell / 100
result = trader.binance.trade.create_market_order(
api_key,
secret_key,
symbol,
'SELL',
base_amount
)
if result['status'] != 'FILLED':
err_log = f"Response status was't FILLED, could't create SELL order for {symbol}, result was: {result}"
trader.ssb.helper.error_log(err_log, False)
tui.program_log.add_log(err_log)
update_live_data_points(buy_on_next_trade, base_currency, target_currency, is_in_favor, current_price, last_operation_price, difference_in_percent, buy_signal, sell_signal, tui)
return
if 'executedQty' in result.keys():
quantity = result['executedQty']
else:
quantity = base_amount
if 'cummulativeQuoteQty' in result.keys():
target_amount = result['cummulativeQuoteQty']
else:
target_amount = quantity * current_price
buy_on_next_trade = True
last_operation_price = current_price
config['buy_on_next_trade'] = True
config['last_operation_price'] = current_price
update_and_save_config_file(config)
log_str = f'Sold {quantity} {base_currency} '\
f'for {target_amount} {target_currency} '\
f'( {symbol} -> {current_price} )'
trader.ssb.helper.log(log_str, False)
tui.transaction_log.add_log(log_str)
if is_telegram_enabled() or is_discord_enabled():
try:
profit_text = get_sell_profit_text(
base_currency,
target_currency,
target_amount
)
except Exception as ex:
err_log = f'Error at get_sell_profit_text, Exception message: {ex}'
trader.ssb.helper.error_log(err_log, False)
tui.program_log.add_log(err_log)
profit_text = ''
notification_str = f'{log_str} {profit_text}'
if is_telegram_enabled():
trader.helper.notify_on_telegram(
telegram_api_token,
telegram_chat_id,
notification_str
)
if is_discord_enabled():
trader.helper.notify_on_discord(
discord_api_token,
discord_channel_id,
notification_str
)
update_live_data_points(buy_on_next_trade, base_currency, target_currency, is_in_favor, current_price, last_operation_price, difference_in_percent, buy_signal, sell_signal, tui)
def get_sell_profit_text(base_currency, target_currency, target_amount) -> str:
symbol = base_currency + target_currency
difference = calculate_sell_profit(symbol, target_amount)
if difference == 0:
return ""
if difference > 0:
return f'(Profit: {difference} {target_currency})'
else:
return f'(Loss: {difference} {target_currency})'
def calculate_sell_profit(symbol, target_amount) -> float:
# Get the latest bought log for <symbol>
with open(trader.ssb.constants.LOG_FILE, 'r') as log_file:
logs = [log for log in log_file.readlines() if symbol in log and 'Bought' in log]
if len(logs) == 0:
return 0.0
last_bought_log = logs[-1]
last_bought_target_quantity = float(last_bought_log.split(' ')[4])
target_amount = float(target_amount)
return target_amount - last_bought_target_quantity
if __name__ == '__main__':
parser = argparse.ArgumentParser(description = 'A bot that does cryptocurrency trading based on several indicator signals.')
parser.add_argument('-t',
'--telegram',
help = 'The telegram chat_id that will be notified on buy or sell operations.',
type = str,
default = '',
)
parser.add_argument('-d',
'--discord',
help = 'The discord channel_id that will be notified on buy or sell operations.',
type = str,
default = '',
)
args = parser.parse_args()
telegram_chat_id = args.telegram
discord_channel_id = args.discord
tui = TUI()
tui.nonblocking_draw()
# Load the previous transaction logs if it exists
try:
if os.path.exists(trader.ssb.constants.LOG_FILE):
with open(trader.ssb.constants.LOG_FILE, 'r') as log_file:
lines = log_file.readlines()
for line in lines:
chunks = line.split("---")
date = str(chunks[0]).strip()
log = str(chunks[1]).strip()
tui.transaction_log.add_log(log, date)
except Exception as ex:
tui.program_log.add_log(f"Failed to collect previous transaction log: {ex}")
tui.program_log.add_log("Started the trader bot!")
# Read the binance api key and api secret key
with open(trader.constants.BINANCE_API_KEYS_FILE, 'r') as credentials_file:
keys = json.loads(credentials_file.read())
api_key = keys['api_key']
secret_key = keys['secret_key']
if telegram_chat_id != '':
# Read the telegram api token
with open(trader.constants.TELEGRAM_BOT_API_KEYS_FILE, 'r') as credentials_file:
keys = json.loads(credentials_file.read())
telegram_api_token = keys['api_token']
if discord_channel_id != '':
# Read the discord api token
with open(trader.constants.DISCORD_BOT_API_KEYS_FILE, 'r') as credentials_file:
keys = json.loads(credentials_file.read())
discord_api_token = keys['api_token']
# Default config values
default_config = {
'enabled': True, # if true actively trade with this config, else dismiss
'base_currency': 'BTC', # First currency asset in the symbol
'target_currency': 'USDT', # Second currency asset in the symbol, want to maximize this asset
'buy_on_next_trade': True, # Buy `base_currency` at the next trade
'trade_amount_buy': 15.0, # Constant amount of `target_currency` to use while buying `base_currency`
'trade_wealth_percent_sell': 100.0, # The percent of the account balance to be traded while selling `base_currency`
'last_operation_price': -1.0, # Previous trade price, if there is no trade (-1), set to current price
'prevent_loss': True, # If True never sell cheaper
}
# Fetch config files from fs
final_config_files = trader.ssb.helper.load_config_file(default_config)
# Append the individual config files to global var `master_config_files`
for current_config in final_config_files:
master_config_files.append(current_config)
for current_config in master_config_files:
symbol = current_config['base_currency'] + current_config['target_currency']
if current_config['last_operation_price'] == -1:
current_config['last_operation_price'] = trader.binance.trade.get_current_trade_ratio(symbol)
# Validate the config file
trader.ssb.helper.validate_config_file(master_config_files)
# Update config on the file system
trader.ssb.helper.write_config_file(master_config_files)
enabled_symbols = [f"{cc['base_currency']}/{cc['target_currency']}" for cc in master_config_files if cc["enabled"]]
tui.program_log.add_log(f"Enabled symbols are: {', '.join(enabled_symbols)}")
while True:
for current_config in master_config_files:
if current_config['enabled']:
try:
perform_bot_operations(current_config, api_key, secret_key, tui)
except Exception as ex:
symbol = current_config['base_currency'] + current_config['target_currency']
err_log = f'Error at perform_bot_operations for: {symbol}, Exception message: {ex}'
trader.ssb.helper.error_log(err_log, False)
tui.program_log.add_log(err_log)
time.sleep(5)