-
Notifications
You must be signed in to change notification settings - Fork 0
/
robotrader.py
385 lines (342 loc) · 17.1 KB
/
robotrader.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
#!/usr/bin/python3
######################### IMPORTS ###################################################################
import requests
import dateparser
import pytz
import json
import math
import hmac
import hashlib
import pandas as pd
import numpy
import matplotlib.pyplot as plt
import csv
import os.path
import time
from datetime import datetime, timezone
from colors import * # Prompt colors
# Attempt to import API keys from secrets file, if not available can't run.
try:
from secrets import secret_key, api_key # Secrets file containing api key, etc.
except ImportError:
print(f"{colors.FAIL}[ERROR]: Unable to import secrets file. Please check configuration. {colors.ENDC}")
######################## ROBOTRADER #################################################################
class RoboTrader:
def __init__(self, api_key, symbol, trading_interval=None):
self.headers = {'X-MBX-APIKEY': api_key}
self.exchange = "https://api.binance.com/"
self.symbol = symbol
self.params = {'symbol': symbol}
self.candlesticks = None
self.trading_interval=trading_interval
self.historical_file_name = "historical_data.csv"
self.balances = {}
########################## HELPER FUNCTIONS #####################################################
def stringify_params(self, params):
param_string = ""
for i,x in enumerate(params.keys()):
each_param = x + "=" + params[x]
if i > 0:
each_param = "&" + each_param
param_string += each_param
return param_string
def save_historical_data(self, dataframe, mode=None):
if mode == 'a':
dataframe.to_csv(self.historical_file_name, mode='a', index=False, header=False)
else:
print(f"{colors.OKBLUE}[INFO]: Creating historical_data.csv.{colors.ENDC}")
dataframe.to_csv(self.historical_file_name, sep=",", index=False)
print(f"{colors.OKGREEN}[SUCCESS]: historical_data.csv saved.{colors.ENDC}")
def generate_signature(self, params, key):
encoded_params = str.encode(self.stringify_params(params))
encoded_key = str.encode(secret_key)
sig = hmac.new(encoded_key, encoded_params, hashlib.sha256)
return sig.hexdigest()
def get_ms_timestamp(self):
# Get UNIX/POSIX/epoch time
timestamp = datetime.now(timezone.utc).timestamp() * 1000
return str(int(timestamp))
def date_to_ms(self, date):
# Get UNIX/POSIX/epoch time
epoch = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc)
time = dateparser.parse(date)
if time.tzinfo is None or time.tzinfo.utcoffset(time) is None:
time = time.replace(tzinfo=pytz.utc)
return int((time - epoch).total_seconds() * 1000.0)
def interval_to_ms(self, interval):
unit = interval[-1]
milliseconds = None
seconds_per_unit = {
"m": 60,
"h": 60*60,
"d": 60*60*24,
"w": 7*60*60*24
}
if unit in seconds_per_unit:
try:
milliseconds = int(interval[:-1]) * seconds_per_unit[unit] * 1000
except ValueError:
pass
return milliseconds
def candlestick_parser(self, data):
columns = ["open_time", "open", "high", "low", "close", "volume", "close_time", "quote_asset_volume", "num_of_trades", "taker_buy_base_vol", "taker_buy_quote_vol", "x"]
df = pd.DataFrame(data, columns=columns)
return df
def get_quantity(self, side, quantityPercent, price):
# Get wallet balances
self.get_balances()
# Grab assets from balances
assets = [self.balances[i] for i,x in enumerate(self.balances) if self.balances[i]['asset'] in self.symbol]
ordered_list = []
# Grab assets we are interested in base on pair specified in constructor
for i,x in enumerate(assets):
if self.symbol.startswith(x['asset']):
ordered_list.append(x)
ordered_list.append(assets[not i])
# If we are buying we want to check if there are orders in place that will interfere with the order we want to make
if side == 'BUY':
symbol = ordered_list[1]
# total quantity of assets in symbol wallet, both 'locked' and 'free', 'locked' means designated for a order that's in place.
totalQuantity = ((float(symbol['free']) + float(symbol['locked']))*(quantityPercent/100))
if totalQuantity > float(symbol['free']): # If this is true, we don't have enough 'free' currency to make the order we want to make.
return False
else:
return str(math.floor((totalQuantity/price)*1000000)/1000000.0) # If we can place order, returns amount we can buy rounded down to 6 decimal points
else:
symbol = ordered_list[0]
quantity = (float(symbol['free'])*quantityPercent)
######################### API CALLS #############################################################
def get_exchange_info(self):
url = self.exchange + "api/v3/exchangeInfo"
req = requests.get(url, headers=self.headers)
return req.json()
def get_current_avg(self, symbol=None):
params = {'symbol' : self.symbol}
if symbol:
params['symbol'] = symbol
url = self.exchange + "api/v3/avgPrice"
req = requests.get(url, params=params, headers=self.headers)
return req.json()
def get_ticker(self, symbol=None):
params = {'symbol' : self.symbol}
if symbol:
params['symbol'] = symbol
url = self.exchange + "/api/v3/ticker/price"
req = requests.get(url, params=params, headers=self.headers)
return req.json()
def get_candlestick(self, interval, symbol=None, limit=None, start_time=None, end_time=None):
url = self.exchange + "/api/v3/klines"
params = {}
params['interval'] = interval
params['symbol'] = symbol if symbol else self.symbol
params['limit'] = limit if limit else None
params['startTime'] = start_time if start_time else None
params['endTime'] = end_time if end_time else None
params = {k:v for k,v in params.items() if v is not None}
req = requests.get(url, params=params, headers=self.headers)
x = self.candlestick_parser(req.json())
return x
def get_historical_data(self, symbol, interval, limit=None, start=None, end=None):
# Define the size of the chunks of data we pull from the API
limit = limit if limit else 1000
# Define the interval of time represented by a chunk
interval_ms = self.interval_to_ms(interval) * limit
# Define the start of where we want to start gathering data, if given
start_epoch = self.date_to_ms(start) if start else None
# Define end of first chunk, if given
end_iter_epoch = start_epoch + interval_ms if start_epoch else None
# Define where we want to finally stop gathering data, if given
end_epoch = self.date_to_ms(end) if end else None
# If only end is defined
if not start_epoch and end_epoch:
start_epoch = end_epoch - interval_ms
end_iter_epoch = end_epoch
# If only start is defined
if start_epoch and not end_epoch:
# Calculate closest candlestick based on interval
timestamp = int(self.get_ms_timestamp())
end_epoch = timestamp - (timestamp % (interval_ms/limit))
end_iter_epoch = start_epoch + interval_ms
# Dataframe that holds data as we iterate through the time series
historical_data = None
i = 0 # Counter
while True:
# Pause every 3rd loop to be easy on the API
if i % 3 == 0:
time.sleep(1)
if start_epoch: # If we are iterating through a time series starting at some point in the past
start_time = datetime.fromtimestamp(start_epoch/1000).strftime('%Y-%m-%d %H:%M:%S') # datetime required timestamps to be in s, not ms.
end_time = datetime.fromtimestamp(end_iter_epoch/1000).strftime('%Y-%m-%d %H:%M:%S')
# Print information about what chunk we are gathering
print(f"{colors.OKBLUE}[INFO]: Gathering historical data from API, getting data for {start_time} -> {end_time} || Chunk #{i+1} {colors.ENDC}")
# Grab current chunk
temp_data = self.get_candlestick(interval, symbol, limit, start_epoch, end_iter_epoch)
# If first run, save to historical_data, otherwise append
print(temp_data)
if i == 0:
historical_data = temp_data
else:
historical_data = historical_data.append(temp_data)
# Check to see if we have reached the end of our time series
if end_iter_epoch >= end_epoch:
break
# Increment our steps through the time series
end_iter_epoch += interval_ms
start_epoch = temp_data['close_time'].values[-1] + 1
# Increment our counter
i += 1
else: # We want most recent dataset.
timestamp = int(self.get_ms_timestamp())
start_time = datetime.fromtimestamp((timestamp - interval_ms)/1000).strftime('%Y-%m-%d %H:%M:%S')
end_time = datetime.fromtimestamp(timestamp/1000).strftime('%Y-%m-%d %H:%M:%S')
# Print information about what chunk we are gathering
print(f"{colors.OKBLUE}[INFO]: Gathering most recent data from API, getting data for {start_time} -> {end_time} {colors.ENDC}")
# Grab current chunk
temp_data = self.get_candlestick(interval, symbol, limit)
print(temp_data)
# Save to historical_data
historical_data = temp_data
break
historical_data.drop_duplicates(inplace=True)
historical_data = historical_data[historical_data.open_time <= end_epoch]
return historical_data
def get_all_orders(self):
url = self.exchange + "api/v3/allOrders"
params = {'symbol' : self.symbol}
params['timestamp'] = self.get_ms_timestamp()
params['recvWindow'] = '5000'
params['signature'] = self.generate_signature(params, secret_key)
req = requests.get(url, params=params, headers=self.headers)
return req.json()
def get_open_orders(self):
url = self.exchange + "api/v3/openOrders"
params = {'symbol' : self.symbol}
params['timestamp'] = self.get_ms_timestamp()
params['recvWindow'] = '5000'
params['signature'] = self.generate_signature(params, secret_key)
req = requests.get(url, params=params, headers=self.headers)
return req.json()
def query_order(self, orderId):
url = self.exchange + "/api/v3/order"
params = {'symbol' : self.symbol}
params['orderId'] = orderId
params['timestamp'] = self.get_ms_timestamp()
params['recvWindow'] = '5000'
params['signature'] = self.generate_signature(params, secret_key)
req = requests.get(url, params=params, headers=self.headers)
return req.json()
def get_balances(self, symbol=None):
url = self.exchange + "/api/v3/account"
params = {}
params['recvWindow'] = '5000'
params['timestamp'] = self.get_ms_timestamp()
params['signature'] = self.generate_signature(params, secret_key)
req = requests.get(url, params=params, headers=self.headers)
self.balances = req.json()['balances']
for x in self.balances:
if symbol and x['asset'] == symbol:
return x
return None
def cancel_order(self, clientOrderId):
url = self.exchange + "/api/v3/order"
params = {'symbol' : self.symbol}
params['timestamp'] = self.get_ms_timestamp()
params['recvWindow'] = '5000'
params['origClientOrderId'] = clientOrderId
params['signature'] = self.generate_signature(params, secret_key)
req = requests.delete(url, params=params, headers=self.headers)
return(req.json())
def cancel_all_orders(self):
open_orders = self.get_open_orders()
for x in open_orders:
if x['symbol'] == self.params['symbol']:
print(self.cancel_order(x['clientOrderId']))
def cancel_all_open_orders(self):
url = self.exchange + "/api/v3/openOrders"
params = {'symbol' : self.symbol}
params['timestamp'] = self.get_ms_timestamp()
params['recvWindow'] = '5000'
params['signature'] = self.generate_signature(params, secret_key)
req = requests.delete(url, params=params, headers=self.headers)
return(req.json())
def place_order(self, side, orderType, quantityPercent=None, price=None, timeInForce=None, stopPrice=None):
quantity = self.get_quantity('BUY', 100, 8321.28)
if not quantity:
print("Couldn't place order, canceling existing orders.")
self.cancel_all_orders()
quantity = self.get_quantity('BUY', 100, 8321.28)
url = self.exchange + "api/v3/order"
params = {'symbol' : self.symbol}
if timeInForce:
params['timeInForce'] = timeInForce
else:
params['timeInForce'] = "GTC"
params['timestamp'] = self.get_ms_timestamp()
params['recvWindow'] = "5000"
params['side'] = side
params['type'] = orderType
params['quantity'] = quantity
params['price'] = '8321.28'
params['signature'] = self.generate_signature(params, secret_key)
print(params)
req = requests.post(url, params=params, headers=self.headers)
return req.json()
################################# STRATEGY DEFINITIONS ##########################################
def fomo_strategy(self, data, volume_threshold=None, price_threshold=None, ):
### PARAMETER NOTES #########################################################################
# volume_threshold given as a percent change off the previous num_historical unit average #
# price_threshold given as a percent change off the previous num_historical unit average #
# num_historical number of candlesticks to use for moving average #
#############################################################################################
if not volume_threshold:
volume_threshold = 10
if not price_threshold:
price_threshold = 10
# Get last row of dataframe, most up-to-date candlestick
current_data = data[-1]
# Get all but last row of dataframe, data upon which to build our strategy
historical_data = data[:-1]
historical_vol_mean = historical_data["volume"].mean()
historical_price_mean = data["close"].mean() # We've chosen to use the closing price, but this could change to be open, low, or a range between high/low
percent_price_change = (historical_price_mean - current_data["close"])/historical_price_mean*100
percent_volume_change = (historical_vol_mean - current_data["volume"])/historical_vol_mean*100
if abs(percent_price_change) > price_threshold and abs(percent_volume_change) > volume_threshold:
# Now we know the volume AND price are over our threshold - we want to FOMO in and ride the wave, up or down
# Now that we know the amplitude of the move, we need to determine its direction
if percent_price_change > 0:
# Movement is in the positive direction - we need to buy in.
# TODO: Buy at current price.
current_price = self.get_ticker()['price']
self.place_order('BUY', )
else:
# Movement is in the negative direction - we need to sell.
# TODO: Sell at current price.
current_price = self.get_ticker()['price']
self.place_order('SELL', )
'''
def std_dev_reversal_strategy(self, data) :
def arbitrage_strategy(self, data):
def mean_reversal_stratgey(self, data):
def trend_follower_strategy(self, data):
def fft_strategy(self, data):
def sma_ema_strategy(self, data):
def buy_and_hold_strategy(self, data):
### BACKTESTING ###
def backtest(self, strategy, params, historical_data):
for x in historical_data:
results = strategy(params)
#Plot results
'''
if __name__ == "__main__":
robot = RoboTrader(api_key, 'BTCUSDT')
#print(robot.get_balance('USDT'))
#print(robot.get_balance('BTC'))
#print(robot.get_open_orders())
#print(robot.cancel_all_orders())
#print(robot.get_balance('USDT'))
print(robot.place_order(side='BUY', orderType='LIMIT'))
#print(robot.place_order('BUY', 'LIMIT', timeInForce='GTC', ))
#print(robot.get_open_orders())
#historical_data = robot.get_historical_data('BTCUSDT', '1h', limit=1000)
#print(robot.get_ticker()['price'])
#robot.save_historical_data(historical_data)