├── .gitattributes ├── .gitignore ├── Balance.py ├── CurrencyWrapper.py ├── Exchange.py ├── Key_Example.py ├── MarketRecord.py ├── OrderWrapper.py ├── Pair.py ├── Transaction.py ├── TransactionChain.py ├── botconfig.py ├── botutils.py ├── ccxt-trader └── .gitattributes ├── main.py └── tests.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail cache files 2 | Thumbs.db 3 | ehthumbs.db 4 | ehthumbs_vista.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | venv* 9 | .idea* 10 | 11 | # Keys 12 | *.csv 13 | *.txt 14 | *keys.py 15 | 16 | # library copies 17 | ccxt-master* 18 | pybitcointools-master* 19 | 20 | # cache files 21 | *.pyc 22 | 23 | # Recycle Bin used on file shares 24 | $RECYCLE.BIN/ 25 | 26 | # Windows Installer files 27 | *.cab 28 | *.msi 29 | *.msm 30 | *.msp 31 | 32 | # Windows shortcuts 33 | *.lnk 34 | 35 | # ========================= 36 | # Operating System Files 37 | # ========================= 38 | 39 | .DS_Store 40 | -------------------------------------------------------------------------------- /Balance.py: -------------------------------------------------------------------------------- 1 | import botutils 2 | import botconfig 3 | 4 | class Balance: 5 | 6 | def __init__(self, amount, currency, exchange=None): 7 | 8 | self.Amount = amount 9 | self.Exchange = exchange 10 | self.Currency = currency 11 | self.TxChain = None 12 | self.InWallet = exchange == None 13 | 14 | def amountUSD(self): 15 | return botutils.convert_to_USD(self.Amount, self.Currency) 16 | 17 | def assign_transactions(self, txchain): 18 | 19 | assert self.TxChain == None 20 | 21 | self.TxChain = txchain 22 | 23 | 24 | def is_allocated(self): 25 | 26 | return self.TxChain != None 27 | 28 | 29 | def split(self, n): 30 | 31 | newbal = Balance(self.Amount - n, self.Exchange, self.Currency) 32 | self.Amount = n 33 | return newbal 34 | 35 | def balance_key(self): 36 | 37 | return self.Currency.id + "@w" if self.InWallet else self.Exchange.ID -------------------------------------------------------------------------------- /CurrencyWrapper.py: -------------------------------------------------------------------------------- 1 | import ccxt 2 | import botutils 3 | import botconfig 4 | 5 | class Currency: 6 | 7 | def __init__(self, name, key=None, secret=None): 8 | self.Name = name 9 | self.id = name 10 | self.Key = key 11 | self.Secret = secret 12 | self.Exchanges = {} 13 | self.Precisions = {} 14 | self.Balance = 0 15 | self.Available = 0 16 | if key != None: 17 | self.Balance = botutils.balance(name, key) 18 | self.Available = self.Balance 19 | 20 | def add_exchange(self, exchange, balancesheet): 21 | 22 | if id in exchange.currencies: 23 | self.Exchanges[exchange.id] = {'total':balancesheet[self.id]['total'],'available':balancesheet[self.id]['free']} 24 | self.Precisions[exchange.id] = exchange.currencies[id]['precision'] 25 | self.Balance += balancesheet[self.id]['total'] 26 | self.Available += balancesheet[self.id]['free'] 27 | 28 | def update_exchange(self, exchange, balancesheet): 29 | 30 | if exchange.id in self.Exchanges: 31 | self.Balance -= self.Exchanges[exchange.id]['total'] 32 | self.Available -= self.Exchanges[exchange.id]['available'] 33 | self.Exchanges[exchange.id] = {'total': balancesheet[self.id]['total'], 34 | 'available': balancesheet[self.id]['free']} 35 | self.Balance += balancesheet[self.id]['total'] 36 | self.Available += balancesheet[self.id]['free'] 37 | 38 | def check_balance(self): 39 | return {"id":self.id, "total":self.Balance, "available":self.Available, "used":self.Balance-self.Available} 40 | 41 | def __str__(self): 42 | return self.Name 43 | -------------------------------------------------------------------------------- /Exchange.py: -------------------------------------------------------------------------------- 1 | import botutils 2 | import botconfig 3 | import copy 4 | 5 | bookbuffer = botconfig.order_book_trade_buffer 6 | 7 | class Exchange: 8 | 9 | 10 | def __init__(self, ex): 11 | self.Ex = ex 12 | self.Markets = ex.load_markets() 13 | self.ID = ex.id 14 | self.Pairs = set() 15 | self.Currencies = {} 16 | self.OrderBooks = {} 17 | self.WithdrawLimits = copy.deepcopy(botconfig.withdraw_limits[self.ID]) 18 | 19 | def add_pair(self, pair): 20 | 21 | self.Pairs.add(pair) 22 | 23 | def add_currency(self, currency): 24 | 25 | self.Currencies[currency.id] = currency 26 | 27 | def add_book(self, book, symbol): 28 | 29 | self.OrderBooks[symbol] = book 30 | 31 | def max_withdraw(self, currency, converted=False): 32 | 33 | #returns the maximum amount of a currency that can be withdrawn at the time of calling 34 | #TODO: get file records to find withdraw records from last 24 hours 35 | 36 | if type(self.WithdrawLimits) is dict: 37 | if currency in [*self.WithdrawLimits]: 38 | return self.WithdrawLimits[currency] 39 | else: 40 | if 'BTC' in [*self.WithdrawLimits]: 41 | return self.convert_simple(self.WithdrawLimits['BTC'], 'BTC', currency) 42 | else: 43 | if 'USD' in [*self.WithdrawLimits]: 44 | return botutils.convert_from_USD(self.WithdrawLimits['USD'], currency, sigma=-0.05) 45 | else: 46 | return self.convert_simple(self.WithdrawLimits[[*self.WithdrawLimits][0]], [*self.WithdrawLimits][0], currency) 47 | 48 | return self.convert(self.WithdrawLimits, 'BTC', currency) 49 | 50 | def max_order_size(self, symbol, sellbuy, converted=True, sigma=bookbuffer): 51 | 52 | #returns the sum amount of base currency in all orders listed on the l2 order book 53 | 54 | book = self.OrderBooks[symbol] 55 | 56 | if sellbuy == 'sell': 57 | sum = 0.0 58 | for i in range(sigma, len(book['bids'])): 59 | sum += book['bids'][i][1] 60 | 61 | #print(sum) 62 | 63 | else: 64 | sum = 0.0 65 | for i in range(sigma, len(book['asks'])): 66 | if converted: 67 | sum += book['asks'][i][0]*book['asks'][i][1] 68 | else: 69 | sum += book['asks'][i][1] 70 | 71 | 72 | return sum 73 | 74 | def convert(self, amount, cfrom, cto, includefees=False, sigma=bookbuffer): 75 | 76 | #returns the converted value of 'amount' units of currency 'cfrom' in currency 'cto' 77 | #if there isn't a direct conversion, it tries to find intermediate conversions trough BTC, ETH, and USDT. 78 | #if no conversion is found, returns infinity. 79 | 80 | if cfrom == cto: 81 | return amount 82 | 83 | if cfrom+'/'+cto in [*self.Markets]: 84 | symbol = cfrom+'/'+cto 85 | fees = self.Markets[symbol]['taker'] if includefees else 0 86 | return self.market_sell(symbol, amount, sigma=sigma) * (1-fees) 87 | 88 | if cto+'/'+cfrom in [*self.Markets]: 89 | symbol = cto + '/' + cfrom 90 | fees = self.Markets[symbol]['taker'] if includefees else 0 91 | return self.market_buy(symbol, amount, sigma=sigma) * (1 - fees) 92 | 93 | if cfrom != 'BTC': 94 | 95 | if cfrom+'/BTC' in [*self.Markets]: 96 | 97 | symbol = cfrom+'/BTC' 98 | fees = self.Markets[symbol]['taker'] if includefees else 0 99 | return self.convert(self.market_sell(symbol, amount, sigma=sigma)*(1-fees), 'BTC', cto, includefees, sigma=sigma) 100 | 101 | else: 102 | if ('BTC/'+cfrom) in [*self.Markets]: 103 | symbol = 'BTC/'+cfrom 104 | fees = self.Markets[symbol]['taker'] if includefees else 0 105 | return self.convert(self.market_buy(symbol, amount, sigma=sigma) * (1 - fees), 'BTC', cto, includefees, sigma=sigma) 106 | 107 | if cfrom != 'ETH': 108 | 109 | if cfrom + '/ETH' in [*self.Markets]: 110 | symbol = cfrom + '/ETH' 111 | fees = self.Markets[symbol]['taker'] if includefees else 0 112 | return self.convert(self.market_sell(symbol, amount, sigma=sigma) * (1 - fees), 'ETH', cto, includefees, sigma=sigma) 113 | 114 | else: 115 | if ('ETH/' + cfrom) in [*self.Markets]: 116 | symbol = 'ETH/' + cfrom 117 | fees = self.Markets[symbol]['taker'] if includefees else 0 118 | return self.convert(self.market_buy(symbol, amount, sigma=sigma) * (1 - fees), 'ETH', cto, includefees, sigma=sigma) 119 | 120 | if cfrom != 'USDT': 121 | 122 | if cfrom + '/USDT' in [*self.Markets]: 123 | symbol = cfrom + '/USDT' 124 | fees = self.Markets[symbol]['taker'] if includefees else 0 125 | return self.convert(self.market_sell(symbol, amount, sigma=sigma) * (1 - fees), 'USDT', cto, includefees, sigma=sigma) 126 | 127 | else: 128 | 129 | if ('USDT/' + cfrom) in [*self.Markets]: 130 | symbol = 'USDT/' + cfrom 131 | fees = self.Markets[symbol]['taker'] if includefees else 0 132 | return self.convert(self.market_buy(symbol, amount, sigma=sigma) * (1 - fees), 'USDT', cto, includefees, sigma=sigma) 133 | 134 | return float('inf') 135 | 136 | def convert_simple(self, amount, cfrom, cto, includefees=False, sigma=bookbuffer): 137 | #returns the converted value of 'amount' units of currency 'cfrom' in currency 'cto' 138 | #if there isn't a direct conversion, it tries to find intermediate conversions trough BTC, ETH, and USDT. 139 | #if no conversion is found, returns infinity. 140 | 141 | if cfrom == cto: 142 | return amount 143 | 144 | if cfrom+'/'+cto in [*self.Markets]: 145 | symbol = cfrom+'/'+cto 146 | fees = self.Markets[symbol]['taker'] if includefees else 0 147 | return self.ask(symbol)*amount*(1 - fees) 148 | 149 | if cto+'/'+cfrom in [*self.Markets]: 150 | symbol = cto + '/' + cfrom 151 | fees = self.Markets[symbol]['taker'] if includefees else 0 152 | return amount/self.bid(symbol)*(1 - fees) 153 | 154 | if cfrom != 'BTC': 155 | 156 | if cfrom+'/BTC' in [*self.Markets]: 157 | 158 | symbol = cfrom+'/BTC' 159 | fees = self.Markets[symbol]['taker'] if includefees else 0 160 | return self.convert_simple(self.ask(symbol)*amount*(1 - fees), 'BTC', cto, includefees, sigma=sigma) 161 | 162 | else: 163 | if ('BTC/'+cfrom) in [*self.Markets]: 164 | symbol = 'BTC/'+cfrom 165 | fees = self.Markets[symbol]['taker'] if includefees else 0 166 | return self.convert_simple(amount/self.bid(symbol)*(1 - fees), 'BTC', cto, includefees, sigma=sigma) 167 | 168 | if cfrom != 'ETH': 169 | 170 | if cfrom + '/ETH' in [*self.Markets]: 171 | symbol = cfrom + '/ETH' 172 | fees = self.Markets[symbol]['taker'] if includefees else 0 173 | return self.convert_simple(self.ask(symbol)*amount*(1 - fees), 'ETH', cto, includefees, sigma=sigma) 174 | 175 | else: 176 | if ('ETH/' + cfrom) in [*self.Markets]: 177 | symbol = 'ETH/' + cfrom 178 | fees = self.Markets[symbol]['taker'] if includefees else 0 179 | return self.convert_simple(amount/self.bid(symbol)*(1 - fees), 'ETH', cto, includefees, sigma=sigma) 180 | 181 | if cfrom != 'USDT': 182 | 183 | if cfrom + '/USDT' in [*self.Markets]: 184 | symbol = cfrom + '/USDT' 185 | fees = self.Markets[symbol]['taker'] if includefees else 0 186 | return self.convert_simple(self.ask(symbol)*amount*(1 - fees), 'USDT', cto, includefees, sigma=sigma) 187 | 188 | else: 189 | 190 | if ('USDT/' + cfrom) in [*self.Markets]: 191 | symbol = 'USDT/' + cfrom 192 | fees = self.Markets[symbol]['taker'] if includefees else 0 193 | return self.convert_simple(amount/self.bid(symbol)*(1 - fees), 'USDT', cto, includefees, sigma=sigma) 194 | 195 | return float('inf') 196 | 197 | def bid(self, symbol): 198 | if symbol in [*self.OrderBooks] and len(self.OrderBooks[symbol]['bids']) > 0: 199 | return self.OrderBooks[symbol]['bids'][0][0] 200 | return -float('inf') 201 | 202 | def ask(self, symbol): 203 | if symbol in [*self.OrderBooks] and len(self.OrderBooks[symbol]['asks']) > 0: 204 | return self.OrderBooks[symbol]['asks'][0][0] 205 | return float('inf') 206 | 207 | def estimate_bid_price(self, symbol, amount, sigma=bookbuffer): 208 | 209 | #estimate the price of selling the listed amount of currency based on the orders in the market's order book 210 | #symbol and amount are as expected, sigma is the skips past the current bid price to account for orders filled 211 | #since the last time market data was queried. 212 | 213 | assert symbol in [*self.Markets] #convert to get_market call 214 | 215 | book = self.OrderBooks[symbol] 216 | 217 | if sigma > len(book['bids']): 218 | return -float('inf') 219 | 220 | if amount <= 0: 221 | return book['bids'][sigma][0] 222 | 223 | i = sigma 224 | count = 0 225 | price = 0 226 | while count < amount: 227 | #min value in quote currency 228 | minval = min(amount - count, book['bids'][i][1]*book['bids'][i][0]) 229 | #count in quote 230 | count += minval 231 | #price in base 232 | price += minval/book['bids'][i][0] 233 | i +=1 234 | if i == len(book['bids']) and count != amount: 235 | #print(count, '..... ', price) 236 | return float('inf') 237 | return price/amount 238 | 239 | def estimate_buy_price(self, symbol, amount, sigma=bookbuffer): 240 | 241 | # estimate the price of selling the listed amount of currency based on the orders in the market's order book 242 | # symbol and amount are as expected, sigma is the skips past the current bid price to account for orders filled 243 | # since the last time market data was queried. 244 | 245 | assert symbol in [*self.Markets] # convert to get_market call 246 | 247 | book = self.OrderBooks[symbol] 248 | 249 | if sigma > len(book['bids']): 250 | return -float('inf') 251 | 252 | if amount <= 0: 253 | return book['bids'][sigma][0] 254 | 255 | i = sigma 256 | count = 0 257 | price = 0 258 | while count < amount: 259 | # min value in quote currency 260 | minval = min(amount - count, book['asks'][i][1] * book['asks'][i][0]) 261 | # count in quote 262 | count += minval 263 | # price in base 264 | price += minval / book['asks'][i][0] 265 | i += 1 266 | if i == len(book['asks']) and count != amount: 267 | print(count, '.... ', price) 268 | return float('inf') 269 | return price / amount 270 | 271 | def estimate_buy_price_at(self, symbol, amount, sigma=bookbuffer): 272 | 273 | # estimate the price of selling the listed amount of currency based on the orders in the market's order book 274 | # symbol and amount are as expected, sigma is the skips past the current bid price to account for orders filled 275 | # since the last time market data was queried. 276 | 277 | assert symbol in [*self.Markets] # convert to get_market call 278 | 279 | book = self.OrderBooks[symbol] 280 | 281 | if sigma > len(book['bids']): 282 | return -float('inf') 283 | 284 | if amount <= 0: 285 | return book['bids'][sigma][0] 286 | 287 | i = sigma 288 | count = 0 289 | price = 0 290 | while count < amount: 291 | # min value in quote currency 292 | minval = min(amount - count, book['asks'][i][1] * book['asks'][i][0]) 293 | # count in quote 294 | count += minval 295 | # price in base 296 | price = book['asks'][i][0] 297 | i += 1 298 | if i == len(book['asks']) and count != amount: 299 | 300 | #print(count, '.. .. ', price) 301 | return float('inf') 302 | return price 303 | 304 | def estimate_sell_price(self, symbol, amount, sigma=bookbuffer): 305 | 306 | assert symbol in [*self.Markets] # convert to get_market call 307 | 308 | book = self.OrderBooks[symbol] 309 | 310 | if sigma > len(book['bids']): 311 | return -float('inf') 312 | 313 | if amount <= 0: 314 | return book['bids'][sigma][0] 315 | 316 | i = sigma 317 | count = 0 318 | price = 0 319 | while count < amount: 320 | minval = min(amount - count, book['bids'][i][1]) 321 | count += minval 322 | price += minval * book['bids'][i][0] 323 | i += 1 324 | if i == len(book['bids']) and count != amount: 325 | print(count, '... ', price) 326 | return -float('inf') 327 | 328 | return price/amount 329 | 330 | def estimate_sell_price_at(self, symbol, amount, sigma=bookbuffer): 331 | 332 | assert symbol in [*self.Markets] # convert to get_market call 333 | 334 | book = self.OrderBooks[symbol] 335 | 336 | if sigma > len(book['bids']): 337 | return -float('inf') 338 | 339 | if amount <= 0: 340 | return book['bids'][sigma][0] 341 | 342 | i = sigma 343 | count = 0 344 | price = 0 345 | while count < amount: 346 | minval = min(amount - count, book['bids'][i][1]) 347 | count += minval 348 | price = book['bids'][i][0] 349 | i += 1 350 | if i == len(book['bids']) and count != amount: 351 | #print(count, '. . . ', price) 352 | return -float('inf') 353 | 354 | return price 355 | 356 | def estimate_ask_price(self, symbol, amount, sigma=bookbuffer): 357 | 358 | assert symbol in [*self.Markets] # convert to get_market call 359 | 360 | book = self.OrderBooks[symbol] 361 | 362 | if sigma > len(book['bids']): 363 | return -float('inf') 364 | 365 | if amount <= 0: 366 | return book['asks'][sigma][0] 367 | 368 | i = sigma 369 | count = 0 370 | price = 0 371 | while count < amount: 372 | minval = min(amount - count, book['asks'][i][1]) 373 | count += minval 374 | price += minval * book['asks'][i][0] 375 | i += 1 376 | if i == len(book['asks']) and count != amount: 377 | #print('estimate-ask-price amount out of range') 378 | return -float('inf') 379 | return price/amount 380 | 381 | def estimate_bid_order(self, symbol, amount, amtqcurrency=False, sigma=bookbuffer): 382 | 383 | #estimate the price of selling the listed amount of currency based on the orders in the market's order book 384 | #symbol and amount are as expected, sigma is the skips past the current bid price to account for orders filled 385 | #since the last time market data was queried. Amtqcurrency is a flag for whether the amount provided is given in 386 | #the quote currency (True) or the base currency (False). 387 | 388 | assert symbol in [*self.Markets] #convert to get_market call 389 | 390 | if amount <= 0: 391 | return amount 392 | 393 | book = self.OrderBooks[symbol] 394 | 395 | if sigma > len(book['bids']): 396 | return -float('inf') 397 | 398 | i = sigma 399 | count = 0 400 | price = 0 401 | while count < amount: 402 | #min value in quote currency 403 | minval = min(amount - count, book['bids'][i][1]*book['bids'][i][0]) 404 | #count in quote 405 | count += minval 406 | #price in base 407 | price += minval/book['bids'][i][0] 408 | i +=1 409 | if i == len(book['bids']) and count != amount: 410 | #print(count, '. ', price) 411 | return -float('inf') 412 | return price 413 | 414 | def market_buy(self, symbol, amount, amtqcurrency=False, sigma=bookbuffer): 415 | 416 | #estimate the price of selling the listed amount of currency based on the orders in the market's order book 417 | #symbol and amount are as expected, sigma is the skips past the current bid price to account for orders filled 418 | #since the last time market data was queried. Amtqcurrency is a flag for whether the amount provided is given in 419 | #the quote currency (True) or the base currency (False). 420 | 421 | #note, the terminology is backwards on these, since the amount is the amount worth of X being sold. 422 | 423 | assert symbol in [*self.Markets] #convert to get_market call 424 | 425 | if amount <= 0: 426 | return amount 427 | 428 | book = self.OrderBooks[symbol] 429 | 430 | if sigma > len(book['asks']): 431 | return -float('inf') 432 | 433 | i = sigma 434 | count = 0 435 | price = 0 436 | k = 0 437 | 438 | while count < amount: 439 | #min value in quote currency 440 | minval = min(amount - count, book['asks'][i][1]*book['asks'][i][0]) 441 | #count in quote 442 | count += minval 443 | #price in base 444 | price += minval/book['asks'][i][0] 445 | i +=1 446 | if i == len(book['asks']) and count != amount: 447 | print(book['asks'][i-1][0],'/',count, '/ ',amount,'/',price) 448 | #raise Exception 449 | return -float('inf') 450 | #print(count,',, ',price) 451 | return price 452 | 453 | def market_sell(self, symbol, amount, converted=True, sigma=bookbuffer): 454 | 455 | assert symbol in [*self.Markets] 456 | 457 | if amount <= 0: 458 | return amount 459 | 460 | book = self.OrderBooks[symbol] 461 | 462 | if sigma > len(book['bids']): 463 | return -float('inf') 464 | 465 | i = sigma 466 | count = 0 467 | price = 0 468 | while count < amount: 469 | minval = min(amount - count, book['bids'][i][1]) 470 | count += minval 471 | 472 | price += minval * book['bids'][i][0] 473 | i += 1 474 | if i == len(book['bids']) and count != amount: 475 | print(count, ' ', price) 476 | return -float('inf') 477 | return price 478 | 479 | 480 | def estimate_ask_order(self, symbol, amount, sigma=bookbuffer): 481 | 482 | 483 | assert symbol in [*self.Markets] 484 | 485 | if amount <= 0: 486 | return amount 487 | 488 | book = self.OrderBooks[symbol] 489 | 490 | if sigma > len(book['bids']): 491 | return -float('inf') 492 | 493 | i = sigma 494 | count = 0 495 | price = 0 496 | while count < amount: 497 | minval = min(amount - count, book['asks'][i][1]) 498 | count += minval 499 | price += minval * book['asks'][i][0] 500 | i += 1 501 | if i == len(book['asks']) and count != amount: 502 | #print(count, ', ', price) 503 | return float('inf') 504 | return price 505 | 506 | def estimate_buy_cost(self, symbol, amount, sigma=bookbuffer): 507 | 508 | #how much you'd need to buy to get X amount of the base currency 509 | 510 | assert symbol in [*self.Markets] 511 | 512 | if amount <= 0: 513 | return amount 514 | 515 | book = self.OrderBooks[symbol] 516 | 517 | if sigma > len(book['bids']): 518 | return float('inf') 519 | 520 | i = sigma 521 | count = 0 522 | price = 0 523 | while count < amount: 524 | # min value in quote currency 525 | minval = min(amount - count, book['asks'][i][1]) 526 | # count in quote 527 | count += minval 528 | # price in base 529 | price += minval * book['asks'][i][0] 530 | i += 1 531 | if i == len(book['asks']) and count != amount: 532 | #print(count, ',,, ', price) 533 | return float('inf') 534 | return price 535 | 536 | def estimate_sell_cost(self, symbol, amount, sigma=bookbuffer): 537 | 538 | #how much selling X amount of the quote currency costs 539 | 540 | assert symbol in [*self.Markets] 541 | 542 | if amount <= 0: 543 | return amount 544 | 545 | book = self.OrderBooks[symbol] 546 | 547 | if sigma > len(book['bids']): 548 | return float('inf') 549 | 550 | i = sigma 551 | count = 0 552 | price = 0 553 | while count < amount: 554 | minval = min(amount - count, book['bids'][i][0]*book['bids'][i][1]) 555 | count += minval 556 | price += book['bids'][i][1] 557 | i += 1 558 | if i == len(book['bids']) and count != amount: 559 | #print(count, ',,,, ', price) 560 | return float('inf') 561 | 562 | return price 563 | -------------------------------------------------------------------------------- /Key_Example.py: -------------------------------------------------------------------------------- 1 | import botconfig 2 | 3 | #dict of dicts with ccxt-standard 3-letter currency id as the key, and dict with name of coin, public address, and private address 4 | _all_coins = { 5 | 6 | 'BTC':{'name':'bitcoin','public':'pubkey','private':'privkey'}} 7 | 8 | 9 | _all_exchange_keys = {'exchangename':{'key':'apikeypublic','secret':'apikeyprivate'}} 10 | 11 | _active_exchanges = ['exchangename'] 12 | 13 | _active_coins = ['BTC'] 14 | 15 | _infura_url = "used for infura api calls" 16 | 17 | _infura_api_keys = {'public':'pubkey', 'private':'privkey'} 18 | 19 | _onchain_key = "onchain_api_key" 20 | 21 | _email = "your_email for site logins" 22 | 23 | 24 | def get_keys(coins=botconfig.active_coins): 25 | 26 | return {coin: _all_coins[coin] for coin in coins} 27 | 28 | 29 | def get_exchanges(exchanges=botconfig.active_exchanges): 30 | 31 | return {exchange: _all_exchange_keys[exchange] for exchange in exchanges} 32 | 33 | 34 | def get_email(): 35 | 36 | return _email 37 | 38 | 39 | def get_exchange_password(exchange): 40 | #fill this out with your passwords 41 | return 42 | 43 | 44 | def get_infura_keys(): 45 | 46 | return _infura_api_keys 47 | 48 | 49 | def get_infura_url(): 50 | 51 | return _infura_url 52 | 53 | 54 | def get_onchain_key(): 55 | 56 | return _onchain_key 57 | -------------------------------------------------------------------------------- /MarketRecord.py: -------------------------------------------------------------------------------- 1 | class Market: 2 | 3 | def __init__(self, symbol): 4 | self.Symbol = symbol 5 | self.Base = symbol.split('/')[0] 6 | self.Quote = symbol.split('/')[1] 7 | self.Orders = [] 8 | 9 | self.BestBid = -1 10 | self.BestAsk = -1 11 | 12 | def add_order(self, order, exchange): 13 | self.Orders.append(order) 14 | if order['bids'][0][0] > self.BestBid: 15 | self.BestBidOrder = order 16 | self.BestBid = order['bids'][0][0] 17 | self.BestBidEx = exchange 18 | if order['asks'][0][0] < self.BestAsk or self.BestAsk == -1: 19 | self.BestAskOrder = order 20 | self.BestAsk = order['asks'][0][0] 21 | self.BestAskEx = exchange 22 | 23 | def bid_price(self): 24 | return self.BestBid 25 | 26 | def ask_price(self): 27 | return self.BestAsk 28 | 29 | def bid(self): 30 | if len(self.Orders) > 0: 31 | return self.BestBidOrder 32 | 33 | def ask(self): 34 | if len(self.Orders) > 0: 35 | return self.BestAskOrder 36 | 37 | def bid_exchange(self): 38 | return self.BestBidEx 39 | 40 | def ask_exchange(self): 41 | return self.BestAskEx 42 | 43 | 44 | 45 | 46 | class PriceEntry: 47 | 48 | def __init__(self, exchange, book, symbol, isbid): 49 | self.Price = book['bids'][0][0] if isbid else book['asks'][0][0] 50 | self.Exchange = exchange 51 | self.OrderBook = book 52 | self.Symbol = symbol 53 | self.Base = symbol.split("/")[0] 54 | self.Quote = symbol.split("/")[1] 55 | self.Bid = isbid 56 | self.Fees = exchange.fees['trading']['taker'] 57 | 58 | def estimate_fee_percent(self, price, volume): 59 | feepercent = self.estimate_fees(price, volume) 60 | feepercent /= (price*volume) 61 | return feepercent 62 | 63 | def estimate_fees(self, volume): 64 | fee = self.Fees * self.Price * volume 65 | depositFees = self.Exchange.fees['funding']['deposit'] 66 | if self.Base in [*depositFees]: 67 | fee += depositFees[self.Base] * self.Price if not self.Bid else 1.0 68 | withdrawFees = self.Exchange.fees['funding']['withdraw'] 69 | if self.quote in [*withdrawFees]: 70 | fee += withdrawFees[self.Quote] * self.Price if self.Bid else 1.0 71 | return fee 72 | 73 | def get_price(self): 74 | return self.Price * (1.0 - self.Fees if self.Bid else 1.0 + self.Fees) 75 | 76 | 77 | class ArbitrageList: 78 | 79 | def __init__(self, symbol): 80 | self.BidList = [] 81 | self.AskList = [] 82 | self.Symbol = symbol 83 | self.BidPrice = 0 84 | self.AskPrice = 0 85 | 86 | def add_order(self, book, exchange, symbol): 87 | entry = PriceEntry(exchange, book, symbol, True) 88 | for i in range(0, len(self.BidList)): 89 | if self.BidList[i].get_price() < entry.get_price(): 90 | self.BidList.insert(i, entry) 91 | if i == 0: 92 | self.BidPrice = entry.get_price() 93 | askentry = PriceEntry(exchange, book, symbol, False) 94 | for i in range(0, len(self.AskList)): 95 | if self.AskList[i].get_price() > askentry.get_price(): 96 | self.AskList.insert(i, askentry) 97 | if i == 0: 98 | self.AskPrice = askentry.get_price() 99 | if len(self.BidList) == 0: 100 | self.BidList.append(entry) 101 | if len(self.AskList) == 0: 102 | self.AskList.append(askentry) 103 | 104 | def sort(self, n=10000): 105 | 106 | return sorted(self.orders, key=lambda x: n * x.Price - x.estimate_fees(n)) 107 | 108 | def clean_lists(self): 109 | 110 | if len(self.AskList) == 0 or len(self.BidList) == 0: 111 | self.AskList = [] 112 | self.BidList = [] 113 | return 114 | for i in range(0, len(self.BidList)): 115 | if self.BidList[i].get_price() < self.AskPrice: 116 | self.BidList = self.BidList[:i] 117 | break 118 | for i in range(0, len(self.AskList)): 119 | if self.AskList[i].get_price() > self.BidPrice: 120 | self.AskList = self.AskList[:i] 121 | break 122 | if len(self.BidList) == 0: 123 | self.BidPrice = 0 124 | if len(self.AskList) == 0: 125 | self.AskPrice = 0 126 | 127 | def print_bids(self): 128 | if len(self.BidList) == 0 or len(self.AskList) == 0: 129 | return 130 | for bid in self.BidList: 131 | bidval = bid.get_price() 132 | bestaskval = self.AskList[0].get_price() 133 | worstaskval = self.AskList[-1].get_price() 134 | percent = 100 * bidval / bestaskval 135 | lowpercent = 100 * bidval / worstaskval 136 | print("Bid ",bid.Symbol," on ",bid.Exchange.id," is ","%.3f" % percent, 137 | " of lowest ask on ",self.AskList[0].Exchange.id," and ", 138 | "%.3f" % lowpercent," of highest ask on ", self.AskList[-1].Exchange.id) 139 | 140 | def print_test(self): 141 | print(self.Symbol+" - Bid[0]: "+str(self.BidList[0].Price)+", Bid[n]: "+str(self.BidList[-1].Price)+ 142 | ", Ask[0]: "+str(self.AskList[0].Price)+", Ask[n]: "+str(self.AskList[-1].Price)) -------------------------------------------------------------------------------- /OrderWrapper.py: -------------------------------------------------------------------------------- 1 | import CurrencyWrapper 2 | 3 | class Order: 4 | 5 | def __init__(self, symbol, price, amount, exchange, id): 6 | self.Symbol = symbol 7 | self.Base = symbol.split("/")[0] 8 | self.Quote = symbol.split("/")[0] 9 | self.Price = price 10 | self.Amount = amount 11 | self.Exchange = exchange 12 | self.ID = id 13 | 14 | def check_status(self): 15 | try: 16 | if self.Exchange.has['fetch_order']: 17 | order = self.Exchange.fetch_order(self.ID) 18 | return order['status'] 19 | else: 20 | return "unknown" 21 | except Exception as e: 22 | print(e) 23 | return "unknown" 24 | 25 | -------------------------------------------------------------------------------- /Pair.py: -------------------------------------------------------------------------------- 1 | import botutils 2 | import botconfig 3 | import time 4 | 5 | bookbuffer = botconfig.order_book_trade_buffer 6 | 7 | 8 | class Pair: 9 | 10 | def __init__(self, ex1, ex2, symbol): 11 | 12 | self.Symbol = symbol 13 | self.Base = botutils.base(symbol) 14 | self.Quote = botutils.quote(symbol) 15 | self.Margin = 0 16 | 17 | if not (symbol in [*ex1.OrderBooks] and symbol in [*ex2.OrderBooks]): 18 | self.ExBuy = None 19 | self.ExSell = None 20 | return 21 | 22 | if ex1.bid(symbol) > ex2.ask(symbol): 23 | self.ExSell = ex1 24 | self.ExBuy = ex2 25 | else: 26 | if ex2.bid(symbol) > ex1.ask(symbol): 27 | self.ExSell = ex2 28 | self.ExBuy = ex1 29 | else: 30 | self.ExBuy = None 31 | self.ExSell = None 32 | return 33 | 34 | if not (self.Base in [*self.ExBuy.Ex.fees['funding']['withdraw']] and self.Quote in [*self.ExSell.Ex.fees['funding']['withdraw']]): 35 | if botconfig.require_fees: 36 | self.ExBuy = None 37 | self.ExSell = None 38 | return 39 | else: 40 | self.PercentFees = {'buy': botconfig.default_fee_percent, 'sell': botconfig.default_fee_percent} 41 | self.FlatFees = {'buy': 0, 'sell': 0} 42 | else: 43 | self.PercentFees = {'buy': self.ExBuy.Markets[symbol]['taker'], 'sell': self.ExSell.Markets[symbol]['taker']} 44 | self.FlatFees = {} 45 | 46 | if type(self.ExBuy.Ex.fees['funding']['withdraw'][self.Base]) == str and self.ExBuy.Ex.fees['funding']['withdraw'][self.Base][-1] == '%': 47 | self.PercentFees['buy'] += float(self.ExBuy.Ex.fees['funding']['withdraw'][self.Base][0:-1])/100.0 48 | self.FlatFees['buy'] = 0 49 | else: 50 | self.FlatFees['buy'] = self.ExBuy.Ex.fees['funding']['withdraw'][self.Base] 51 | 52 | if type(self.ExSell.Ex.fees['funding']['withdraw'][self.Quote]) == str and self.ExSell.Ex.fees['funding']['withdraw'][self.Quote][-1] == '%': 53 | self.PercentFees['sell'] += float(self.ExSell.Ex.fees['funding']['withdraw'][self.Quote][0:-1]) / 100.0 54 | self.FlatFees['sell'] = 0 55 | else: 56 | self.FlatFees['sell'] = self.ExSell.Ex.fees['funding']['withdraw'][self.Quote] 57 | 58 | self.Margin = self.ExSell.bid(symbol)/self.ExBuy.ask(symbol) 59 | 60 | if self.min_trade() < self.max_trade(): 61 | 62 | try: 63 | delay = max(int(self.ExBuy.Ex.rateLimit / 1000), int(self.ExSell.Ex.rateLimit / 1000)) 64 | self.BuyAddress = self.ExBuy.Ex.fetch_deposit_address(botutils.quote(self.Symbol)) 65 | self.SellAddress = self.ExSell.Ex.fetch_deposit_address(botutils.base(self.Symbol)) 66 | time.sleep(delay) 67 | ex1.add_pair(self) 68 | ex2.add_pair(self) 69 | except: 70 | self.ExBuy = None 71 | self.ExSell = None 72 | 73 | def min_trade(self, flatfee=0, percentfee=0, sigma=bookbuffer, error=0.0001000000001): 74 | 75 | sellmax = self.ExSell.max_order_size(self.Symbol, 'sell') 76 | buymax = self.ExBuy.max_order_size(self.Symbol, 'buy', converted=True) 77 | sellmaxadj = min(sellmax, self.ExSell.estimate_sell_cost(self.Symbol, buymax)) 78 | buymaxadj = self.ExBuy.market_buy(self.Symbol, min(buymax, self.ExBuy.estimate_buy_cost(self.Symbol, sellmax))) 79 | mcount = min(sellmaxadj, buymaxadj) 80 | sbook = self.ExSell.OrderBooks[self.Symbol] 81 | bbook = self.ExBuy.OrderBooks[self.Symbol] 82 | scount = 0 83 | bcount = 0 84 | ai = sigma 85 | bi = sigma 86 | if sigma >= len(sbook['bids']) or sigma >= len(bbook['asks']): 87 | return float('inf') 88 | 89 | sprice = sbook['bids'][ai][0] 90 | bprice = bbook['asks'][bi][0] 91 | 92 | if self.FlatFees['sell'] == 0 and self.FlatFees['buy'] == 0 and sprice > bprice: 93 | return 0 94 | if self.Margin <= 1: 95 | return float('inf') 96 | 97 | while min(scount, bcount) < mcount and ai < len(sbook['bids']) and bi < len(bbook['asks']): 98 | 99 | lcount = min(scount, bcount) 100 | if scount+self.FlatFees['sell']/self.ExSell.estimate_sell_price(self.Symbol, scount, sigma=sigma) < bcount+self.FlatFees['sell']: 101 | scount += sbook['bids'][ai][1] 102 | sprice = sbook['bids'][ai][0] * (1 - self.PercentFees['sell']) 103 | ai += 1 104 | else: 105 | bcount += bbook['asks'][bi][1] 106 | bprice = bbook['asks'][bi][0] * (1 + self.PercentFees['buy']) 107 | bi += 1 108 | if min(scount, bcount) > mcount: 109 | return float('inf') 110 | 111 | rmin = self.roi(min(scount, bcount), sellbuy='sell', flatfee=flatfee, percentfee=percentfee) 112 | rlast = self.roi(lcount, sellbuy='sell', flatfee=flatfee, percentfee=percentfee) 113 | 114 | if rmin >= 0: 115 | if rmin == 0: 116 | return min(scount, bcount) 117 | #todo: figure out why linear interpolation isn't working; maybe splines? 118 | return lcount - rlast/(rmin-rlast)*(min(scount, bcount)-lcount) 119 | 120 | return float('inf') 121 | 122 | 123 | def max_trade(self, percentfee=0, sigma=bookbuffer): 124 | 125 | sellmax = self.ExSell.max_order_size(self.Symbol, 'sell') 126 | buymax = self.ExBuy.max_order_size(self.Symbol, 'buy', converted=True) 127 | sellmaxadj = min(sellmax, self.ExSell.estimate_sell_cost(self.Symbol, buymax)) 128 | buymaxadj = self.ExBuy.market_buy(self.Symbol, min(buymax, self.ExBuy.estimate_buy_cost(self.Symbol, sellmax))) 129 | mcount = min(sellmaxadj, buymaxadj) 130 | sbook = self.ExSell.OrderBooks[self.Symbol] 131 | bbook = self.ExBuy.OrderBooks[self.Symbol] 132 | scount = 0 133 | bcount = 0 134 | lcount = 0 135 | smax = float('inf') 136 | bmax = -float('inf') 137 | ai = sigma 138 | bi = sigma 139 | 140 | if sigma >= len(sbook['bids']) or sigma >= len(bbook['asks']): 141 | return -1 142 | 143 | while min(scount, bcount) < mcount and smax > bmax and ai < len(sbook['bids']) and bi < len(bbook['asks']): 144 | 145 | lcount = min(scount, bcount) 146 | if scount < bcount: 147 | scount += sbook['bids'][ai][1] 148 | ai+=1 149 | else: 150 | bcount += bbook['asks'][bi][1] 151 | bi+=1 152 | 153 | smax = self.ExSell.estimate_sell_price_at(self.Symbol, min(scount, bcount)) * (1 - self.PercentFees['sell'] - percentfee) 154 | bmax = self.ExBuy.estimate_buy_price_at(self.Symbol, self.ExSell.market_sell(self.Symbol, min(scount, bcount))) * (1 + self.PercentFees['buy']) 155 | 156 | return lcount 157 | 158 | def margin(self, amount, sellbuy='buy', flatfee=0, percentfee=0, sigma=bookbuffer): 159 | 160 | return (self.roi(amount, sellbuy, flatfee, percentfee, sigma)+amount)/amount if amount != 0 else 0 161 | 162 | def roi(self, amount, sellbuy='buy', flatfee=0, percentfee=0, sigma=bookbuffer): 163 | 164 | if sellbuy == 'sell': 165 | 166 | amt = amount * (1-percentfee) - flatfee 167 | 168 | if amt > self.ExSell.max_order_size(self.Symbol, 'sell'): 169 | print('above max') 170 | return -float('inf') 171 | 172 | sold = (self.ExSell.market_sell(self.Symbol, amt - self.PercentFees['sell']*abs(amt)) if amt > 0 else amt) - self.FlatFees['sell'] 173 | bought = (self.ExBuy.market_buy(self.Symbol, sold - self.PercentFees['buy'] * abs(sold)) if amt > 0 else sold) - self.FlatFees['buy'] 174 | return bought - amount 175 | 176 | amt = amount * (1 - percentfee) - flatfee 177 | 178 | if amt > self.ExBuy.max_order_size(self.Symbol, 'buy'): 179 | print('above max') 180 | return -float('inf') 181 | 182 | first = ( self.ExBuy.market_buy(self.Symbol, amt) if amt > 0 else amt ) - self.PercentFees['buy']*abs(amt) - self.FlatFees['buy'] 183 | second = ( self.ExSell.market_sell(self.Symbol, first) if amt > 0 else first ) - self.PercentFees['sell']*abs(first) - self.FlatFees['sell'] 184 | 185 | return second - amount 186 | 187 | def generate_tx_chain(self, exfrom, cfrom, txlast=None): 188 | 189 | if txlast is not None: 190 | cf = txlast.SummaryInfo['cto'] 191 | ef = txlast.SummaryInfo['eto'] 192 | 193 | else: 194 | cf = cfrom 195 | ef = exfrom 196 | 197 | if cf == self.Base: 198 | 199 | if ef == self.ExSell: 200 | # trade/transfer/trade 201 | pass 202 | else: 203 | # transfer to ex-sell, trade/transfer/trade 204 | pass 205 | 206 | elif cf == self.Quote: 207 | 208 | if ef == self.ExBuy: 209 | # trade/transfer/trade 210 | pass 211 | elif ef == self.ExSell: 212 | # transfer to ex-buy, trade/transfer/trade 213 | pass 214 | else: 215 | # todo: pathfinding algorithm for to or , find cheapest way to move funds 216 | pass 217 | 218 | def __str__(self): 219 | return 'sell: '+self.ExSell.Ex.name+', buy:'+self.ExBuy.Ex.name+', via '+self.Symbol+' | '+'%.3f' % (100*self.Margin-100)+'% | min: '+"%.5f" % self.min_trade()+' | max: '+'%.5f'%self.max_trade()+' | $'+"%.2f" % botutils.convert_to_USD(self.max_trade(), self.Base)+' -> $'+"%.2f" % botutils.convert_to_USD(self.max_trade()+self.roi(self.max_trade(), sellbuy='sell'), self.Base) -------------------------------------------------------------------------------- /Transaction.py: -------------------------------------------------------------------------------- 1 | import ccxt 2 | import main 3 | import botutils 4 | import botconfig 5 | class Transaction: 6 | 7 | def __init__(self): 8 | 9 | self.Data = {} 10 | self.Started = False 11 | self.Finished = False 12 | self.Next = None 13 | self.SummaryInfo = {} 14 | 15 | def push_tx(self): 16 | pass 17 | 18 | def cancel_tx(self): 19 | pass 20 | 21 | def check_status(self): 22 | pass 23 | 24 | def add_next(self, nxt): 25 | self.Next = nxt 26 | 27 | 28 | 29 | 30 | class TransferTX(Transaction): 31 | 32 | def __init__(self, exchangefrom, exchangeto, currency, amount): 33 | 34 | super() 35 | self.Currency = currency 36 | self.From = exchangefrom 37 | self.To = exchangeto 38 | self.Amount = amount 39 | self.SummaryInfo['cfrom'] = self.Currency 40 | self.SummaryInfo['cto'] = self.Currency 41 | self.SummaryInfo['efrom'] = self.From 42 | self.SummaryInfo['eto'] = self.To 43 | self.SummaryInfo['amount'] = self.Amount 44 | 45 | def push_tx(self): 46 | 47 | super.push_tx() 48 | #TODO: 49 | # get deposit address from To 50 | # call From.withdraw to deposit address To 51 | # get transaction info from exchange from and store to data 52 | # set Finished to deposit flag for transfer 53 | 54 | 55 | class BuyTX(Transaction): 56 | 57 | def __init__(self, exchange, symbol, amount): 58 | 59 | super() 60 | self.Exchange = exchange 61 | self.Symbol = symbol 62 | self.Amount = amount 63 | self.SummaryInfo['cfrom'] = botutils.quote(symbol) 64 | self.SummaryInfo['cto'] = botutils.base(symbol) 65 | self.SummaryInfo['efrom'] = self.Exchange 66 | self.SummaryInfo['eto'] = self.Exchange 67 | self.SummaryInfo['amount'] = self.Amount 68 | self.OrderID = None 69 | self.Info = None 70 | self.Order = None 71 | 72 | def push_tx(self): 73 | 74 | #TODO: 75 | #create trade 76 | #get trade info and store to data 77 | #set Finished to flag from transaction data 78 | ex = self.Exchange.Ex 79 | if ex.fetch_balance()[self.SummaryInfo['cfrom']]['free'] < self.Amount: 80 | raise Exception('error: not enough funds') 81 | results = ex.create_limit_buy_order(self.Symbol, self.Amount, self.Exchange.estimate_sell_price_at(self.Symbol, self.Amount)) 82 | if 'id' in [*results]: 83 | self.OrderID = results['id'] 84 | self.Info = results['info'] 85 | self.Started = True 86 | self.Order = ex.fetch_order(self.OrderID) 87 | 88 | def cancel_tx(self): 89 | 90 | if self.Started: 91 | ex = self.Exchange.Ex 92 | try: 93 | ex.cancel_order(self.OrderID) 94 | self.Started = False 95 | except: 96 | if ex.fetch_order(self.OrderID)['status'] == 'closed': 97 | self.Finished = True 98 | 99 | def check_status(self): 100 | 101 | if self.OrderID is not None: 102 | ex = self.Exchange.Ex 103 | self.Order = ex.fetch_ordere(self.OrderID) 104 | if self.Order['status'] == 'closed': 105 | self.Finished = True 106 | if self.Order['status'] == 'cancelled': 107 | self.Started = False 108 | 109 | 110 | class SellTX(Transaction): 111 | 112 | def __init__(self, exchange, symbol, amount): 113 | 114 | super() 115 | self.Exchange = exchange 116 | self.Symbol = symbol 117 | self.Amount = amount 118 | self.SummaryInfo['cfrom'] = botutils.base(symbol) 119 | self.SummaryInfo['cto'] = botutils.quote(symbol) 120 | self.SummaryInfo['efrom'] = self.Exchange 121 | self.SummaryInfo['eto'] = self.Exchange 122 | self.SummaryInfo['amount'] = self.Amount 123 | 124 | def push_tx(self): 125 | 126 | #TODO: 127 | #create trade 128 | #get trade info and store to data 129 | #set Finished to flag from transaction data 130 | ex = self.Exchange.Ex 131 | if ex.fetch_balance()[self.SummaryInfo['cfrom']]['free'] < self.Amount: 132 | raise Exception('error: not enough funds') 133 | results = ex.create_limit_sell_order(self.Symbol, self.Amount, 134 | self.Exchange.estimate_sell_price_at(self.Symbol, self.Amount)) 135 | if 'id' in [*results]: 136 | self.OrderID = results['id'] 137 | self.Info = results['info'] 138 | self.Started = True 139 | self.Order = ex.fetch_order(self.OrderID) 140 | 141 | 142 | -------------------------------------------------------------------------------- /TransactionChain.py: -------------------------------------------------------------------------------- 1 | import Transaction 2 | import ccxt 3 | import queue 4 | import copy 5 | 6 | class TransactionChain: 7 | 8 | def __init__(self, balance): 9 | 10 | self.Transactions = queue.Queue() 11 | self.Balance = balance 12 | self.Pending = None 13 | self.Started = False 14 | self.Finished = False 15 | 16 | def size(self): 17 | 18 | return self.Transactions.qsize() 19 | 20 | def set(self, txchain): 21 | 22 | self.Transactions = copy.deepcopy(txchain.Transactions) 23 | 24 | def start(self): 25 | 26 | if self.validate_chain() == False: 27 | raise Exception("Invalid Transaction Chain") 28 | 29 | if self.Transactions.qsize() == 0: 30 | self.Finished = True 31 | return 32 | if self.Started: 33 | self.update() 34 | return 35 | self.Started = True 36 | self.Pending = self.Transactions.get() 37 | self.Pending.push_tx() 38 | 39 | def update(self): 40 | 41 | if not self.Started: 42 | self.start() 43 | return 44 | 45 | if self.Finished or not self.Pending.Finished: 46 | return 47 | 48 | if self.Pending.Finished: 49 | if self.Transactions.qsize() == 0: 50 | self.Finished = True 51 | return 52 | self.Pending = self.Transactions.get() 53 | self.Pending.push_tx() 54 | 55 | def validate_chain(self): 56 | 57 | #TODO: 58 | #for tx in transactions 59 | #if cfrom != last.cto or efrom != last.eto or amount > last.amount return false 60 | # else return true 61 | -------------------------------------------------------------------------------- /botconfig.py: -------------------------------------------------------------------------------- 1 | 2 | active_exchanges = ['binance','hitbtc','coinbasepro','ethfinex','bitbay','bittrex','exmo','fcoin','bigone','kucoin','coinex','bitz','zb','cryptopia','tidex','liqui','livecoin','bithumb','bibox'] 3 | #['binance','hitbtc','kraken','coinbasepro','ethfinex','bitbay','bittrex','exmo','fcoin','bigone','kucoin','coinex','bitz','yobit'] 4 | #['binance','hitbtc','coinbasepro','ethfinex','bitbay','bittrex','exmo','fcoin','bigone','kucoin','coinex', 'bitz'] 5 | #['coinbasepro','ethfinex','bitbay','exmo','fcoin','bigone','coinex'] 6 | #['coinbasepro','bitbay','exmo','bigone'] 7 | 8 | active_coins = ['BTC','BCH','ETH','LTC','DASH'] 9 | 10 | blacklisted_coins = ['BOX'] 11 | 12 | withdraw_limits = {'binance':{'BTC':100}, 13 | 'hitbtc':float('inf'), 14 | 'kraken':{'USD':200000}, 15 | 'kucoin':float('inf'), 16 | 'bittrex':{'BTC':100}, 17 | 'yobit':float('inf'), 18 | 'bigone':float('inf'), 19 | 'exmo':float('inf'), 20 | 'ethfinex':float('inf'), 21 | 'bitbay':float('inf'), 22 | 'bitz':{'BTC':20}, 23 | 'fcoin':{'BTC':150,'ETH':2500,'BCH':1200,'LTC':8000,'USDT':500000,'FT':1500000,'ZIP':200000000,'ETC':120000,'BTM':800000,'ZIL':3000000,'OMG':200000,'ICX':8000000,'ZRX':500000,'BNB':120000,'GTC':150000000,'AE':1000000}, 24 | 'coinbasepro':{'USD':10000}, 25 | 'coinex':float('inf'), 26 | 'bibox':{'BTC':20}, 27 | 'zb':float('inf'), 28 | 'bithumb':{'BTC':0}, 29 | 'cryptopia':{'USD':20000}, 30 | 'liqui':{'USDT':50000,'USD':48000}, 31 | 'tidex':float('inf'), 32 | 'livecoin':float('inf') 33 | } 34 | 35 | max_single_withdraw = {'kucoin':{'BTC':50}} 36 | 37 | #skips this many orders in the book to offset trades made after orders have been fetched and before calculations can be made 38 | #TODO: replace this with estimated currency amounts based off of individual exchange/currency trade volume data 39 | #TODO: improve the above todo with a cyclical curve to represent peak trading hours each day 40 | order_book_trade_buffer = 0 41 | 42 | require_fees = False 43 | 44 | default_fee_percent = 0.00 45 | 46 | max_margin = 100 47 | min_margin = 1 -------------------------------------------------------------------------------- /botutils.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import ccxt 3 | import web3 4 | import cryptos 5 | import coinmarketcap 6 | import sys 7 | from keys import * 8 | 9 | 10 | #TESTNET = True 11 | 12 | cointable = { 13 | 'ETH':'ethereum', 14 | 'BTC':'bitcoin', 15 | 'BCH':'bitcoincash', 16 | 'LTC':'litecoin', 17 | 'DASH':'dash', 18 | } 19 | 20 | cap = coinmarketcap.Market() 21 | tickerdata = cap.ticker()['data'] 22 | 23 | iso_currencies = ['KHR', 'MRU', 'SOS', 'TND', 'MVR', 'ARS', 'XCD', 'FKP', 'MAD', 'TZS', 'CLP', 'MMK', 'HKD', 'RON', 'PAB', 'THB', 'HNL', 'BTN', 'EGP', 'SRD', 'SVC', 'SGD', 'NGN', 'XSU', 'KPW', 'KYD', 'COU', 'SHP', 'UYU', 'GIP', 'SLL', 'CZK', 'BOV', 'KZT', 'LBP', 'MDL', 'KES', 'DJF', 'KRW', 'DOP', 'MXV', 'HRK', 'PYG', 'KGS', 'NPR', 'PGK', 'HTG', 'SEK', 'GNF', 'UAH', 'IQD', 'PLN', 'ETB', 'SBD', 'INR', 'CHW', 'TRY', 'BGN', 'GMD', 'BYN', 'CHE', 'GHS', 'TTD', 'AFN', 'MOP', 'MKD', 'NIO', 'XPF', 'AOA', 'MZN', 'TWD', 'LRD', 'UZS', 'BWP', 'BZD', 'MXN', 'VND', 'SCR', 'AWG', 'PKR', 'TMT', 'XAF', 'STN', 'BRL', 'CRC', 'RUB', 'SSP', 'BDT', 'SDG', 'CUP', 'LYD', 'AUD', 'YER', 'IDR', 'LAK', 'USD', 'MGA', 'LSL', 'DZD', 'NZD', 'EUR', 'AMD', 'JPY', 'COP', 'GYD', 'OMR', 'JMD', 'BIF', 'CHF', 'JOD', 'BHD', 'KMF', 'BND', 'FJD', 'LKR', 'BSD', 'SYP', 'VEF', 'TOP', 'SAR', 'WST', 'HUF', 'UYI', 'GTQ', 'ILS', 'ZWL', 'BAM', 'ALL', 'GBP', 'UGX', 'TJS', 'MYR', 'MWK', 'ERN', 'VUV', 'BOB', 'SZL', 'BMD', 'XDR', 'RWF', 'KWD', 'XUA', 'CDF', 'NOK', 'MUR', 'NAD', 'CAD', 'CUC', 'ISK', 'ZAR', 'QAR', 'RSD', 'XOF', 'PEN', 'USN', 'ZMW', 'GEL', 'CNY', 'BBD', 'PHP', 'MNT', 'AED', 'ANG', 'CLF', 'CVE', 'DKK', 'IRR', 'AZN'] 24 | 25 | #withdraw limits for exchanges. 0 means that withdraw is disabled 26 | withdraw_limits = {'hitbtc':float('inf')} 27 | 28 | def style(s, style): 29 | return style + s + '\033[0m' 30 | 31 | 32 | def green(s): 33 | return style(s, '\033[92m') 34 | 35 | 36 | def blue(s): 37 | return style(s, '\033[94m') 38 | 39 | 40 | def yellow(s): 41 | return style(s, '\033[93m') 42 | 43 | 44 | def red(s): 45 | return style(s, '\033[91m') 46 | 47 | 48 | def pink(s): 49 | return style(s, '\033[95m') 50 | 51 | 52 | def bold(s): 53 | return style(s, '\033[1m') 54 | 55 | 56 | def underline(s): 57 | return style(s, '\033[4m') 58 | 59 | 60 | def dump(*args): 61 | print(' '.join([str(arg) for arg in args])) 62 | 63 | 64 | def print_exchanges(): 65 | dump('Supported exchanges:', ', '.join(ccxt.exchanges)) 66 | 67 | 68 | def print_usage(): 69 | dump("Usage: python " + sys.argv[0], green('id1'), yellow('id2'), blue('id3'), '...') 70 | 71 | 72 | def convert_from_USD(amount, currency, sigma=0): 73 | for i in [*tickerdata]: 74 | if tickerdata[i]['symbol'] == currency: 75 | return (1+sigma)*amount*(1/tickerdata[i]['quotes']['USD']['price']) 76 | return 0 77 | 78 | def convert_to_USD(amount, currency, sigma=0): 79 | for i in [*tickerdata]: 80 | if tickerdata[i]['symbol'] == currency: 81 | return (1-sigma)*amount*(tickerdata[i]['quotes']['USD']['price']) 82 | return 0 83 | 84 | 85 | def print_ticker(exchange, symbol): 86 | ticker = exchange.fetch_ticker(symbol.upper()) 87 | print(ticker) 88 | dump( 89 | green(exchange.id), 90 | yellow(symbol), 91 | 'ticker', 92 | ticker['datetime'], 93 | 'high: ' + str(ticker['high']), 94 | 'low: ' + str(ticker['low']), 95 | 'bid: ' + str(ticker['bid']), 96 | 'ask: ' + str(ticker['ask']), 97 | 'volume: ' + str(ticker['quoteVolume'])) 98 | 99 | 100 | w3 = web3.Web3(web3.HTTPProvider(get_infura_url())) 101 | 102 | # def balance(currency, address): 103 | # 104 | # if currency in cointable: 105 | # header = {'Accept': 'application/json', 'X-API-KEY': get_onchain_key()} 106 | # 107 | # try: 108 | # r = requests.get('https://onchain.io/api/address/balance/'+cointable[currency]+'/'+address, 109 | # params={}, 110 | # headers=header) 111 | # json = r.json() 112 | # # print(json) 113 | # 114 | # if len(json) > 0: 115 | # return json 116 | # return {} 117 | # 118 | # except Exception as e: 119 | # print(e) 120 | # return {} 121 | # 122 | # return {} 123 | 124 | def balance(currency, address): 125 | 126 | 127 | try: 128 | assert currency in [*cointable] 129 | if currency == 'ETH': 130 | balance = w3.eth.getBalance(address) 131 | return web3.utils.fromWei(balance) 132 | else: 133 | if currency == 'BTC': 134 | coin = cryptos.Bitcoin() 135 | if currency == 'LTC': 136 | coin = cryptos.Litecoin() 137 | if currency == 'DASH': 138 | coin = cryptos.Dash() 139 | if currency == 'BCH': 140 | coin = cryptos.BitcoinCash() 141 | balance = coin.history(address)['final_balance'] 142 | return balance 143 | 144 | except Exception as e: 145 | print(e) 146 | return 0 147 | 148 | 149 | 150 | def transfer(currency, amount, addressto, addressfrom, secret): 151 | 152 | if currency == 'ETH': 153 | receipt = send_eth(amount, addressto, addressfrom, secret) 154 | if currency == 'BTC': 155 | coin = cryptos.Bitcoin() 156 | if currency == 'LTC': 157 | coin = cryptos.Litecoin() 158 | if currency == 'DASH': 159 | coin = cryptos.Dash() 160 | if currency == 'BCH': 161 | coin = cryptos.BitcoinCash() 162 | tx = coin.preparesignedtx(secret,addressto,amount,change_addr=addressfrom) 163 | receipt = cryptos.pushtx(tx) 164 | 165 | 166 | def send_eth(amount, addressto, addressfrom, secret): 167 | 168 | amt = web3.toWei(amount, 'ether') 169 | to = w3.toChecksumAddress(addressto) 170 | frm = w3.toChecksumAddress(addressfrom) 171 | gasprice = w3.eth.gasPrice 172 | gas = w3.eth.estimateGas({'to':to, 'from':frm}) 173 | transaction = { 174 | 'to': addressto, 175 | 'value': amt, 176 | 'gas': gas, 177 | 'gasPrice': gasprice, 178 | 'nonce': w3.eth.getTransactionCount(frm), 179 | 'chainId': 1 180 | } 181 | signedtxn = w3.eth.account.signTransaction(transaction, private_key=secret) 182 | w3.eth.sendRawTransaction(signedtxn.rawTtansaction) 183 | return w3.eth.waitForTransactionReceipt(signedtxn.hash, timeout=1200) 184 | 185 | def base(symbol): 186 | return symbol.split('/')[0] 187 | 188 | def quote(symbol): 189 | return symbol.split('/')[1] 190 | 191 | def check_symbol(symbol): 192 | 193 | try: 194 | b = base(symbol) 195 | q = quote(symbol) 196 | return (not b in iso_currencies) and (not q in iso_currencies) and (not b in botconfig.blacklisted_coins) and (not q in botconfig.blacklisted_coins) 197 | 198 | except: 199 | return False 200 | 201 | 202 | if __name__ == "__main__": 203 | 204 | addr = "0x1cC3e192d51DeFF9796590c4C33D715f9253d249" 205 | curr = "ETH" 206 | print (balance(curr, addr)) 207 | 208 | 209 | -------------------------------------------------------------------------------- /ccxt-trader/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import ccxt 2 | import csv 3 | import json 4 | import time 5 | import Exchange 6 | import CurrencyWrapper 7 | import os 8 | import sys 9 | import MarketRecord 10 | import random 11 | from botutils import * 12 | import keys 13 | import Balance 14 | import botconfig 15 | import copy 16 | import Pair 17 | 18 | Exchanges = {} 19 | Currencies = {} 20 | Symbols = [] 21 | pairedSymbols = [] 22 | marketSymbols = {} 23 | pairTable = {} 24 | ExchangeFees = {} 25 | ArbList = {} 26 | QueuedOrders = [] 27 | PendingOrders = [] 28 | TotalFunds = 0 29 | TotalFundsUSD = 0 30 | TotalAvailable = 0 31 | TotalAvailableUSD = 0 32 | Balances = [] 33 | exchanges = {} 34 | pairs = [] 35 | symbex = {} 36 | pairtuples = [] 37 | pairs = [] 38 | 39 | def load_exchanges(): 40 | 41 | proxies = [ 42 | '', # no proxy by default 43 | 'https://crossorigin.me/', 44 | 'https://cors-anywhere.herokuapp.com/', 45 | ] 46 | 47 | nexs = 0 48 | with open('apikeys.csv', 'r') as exfile: 49 | exreader = csv.DictReader(exfile) 50 | for row in exreader: 51 | 52 | nexs += 1 53 | exchange = getattr(ccxt, row['Exchange'])() 54 | 55 | try: 56 | # load all markets from the exchange 57 | markets = exchange.load_markets() 58 | exchange.currencies = exchange.fetch_currencies() 59 | print(green) 60 | 61 | # save it in a dictionary under its id for future use 62 | Exchanges[row['Exchange']] = exchange 63 | exchange.apiKey = row['Key'] 64 | exchange.secret = row['Secret'] 65 | if exchange.id == 'coinbasepro': 66 | exchange.password = keys.get_exchange_password(exchange.id) 67 | # instantiate the exchange by id 68 | dump(green(row['Exchange']), 'loaded', green(str(len(exchange.symbols))), 'markets') 69 | 70 | except Exception as e: 71 | print(e) 72 | pass 73 | 74 | 75 | 76 | 77 | dump(green('Loaded ' + str(len(Exchanges)) + " of " + str(nexs) + ' exchanges.')) 78 | 79 | 80 | def load_currencies(): 81 | with open('cryptokeys.csv', 'r') as cfile: 82 | coinreader = csv.DictReader(cfile) 83 | for row in coinreader: 84 | Currencies[row['COIN']] = CurrencyWrapper.Currency(row['COIN'], row['ADDRESS'], row['PRIVKEY']) 85 | for c1 in [*Currencies]: 86 | # print(c1) 87 | for c2 in [*Currencies]: 88 | if Currencies[c1].Name != Currencies[c2].Name: 89 | Symbols.append(Currencies[c1].Name+"/"+Currencies[c2].Name) 90 | # print("Available Markets:") 91 | #for s in Symbols: 92 | # print(s) 93 | for id in [*Exchanges]: 94 | try: 95 | balancesheet = Exchanges[id].fetch_balance() 96 | except: 97 | pass 98 | for currency in [*Currencies]: 99 | Currencies[currency].add_exchange(Exchanges[id], balancesheet) 100 | 101 | for c in [*Currencies]: 102 | Currencies[c].check_balance() 103 | 104 | 105 | def load_exes(): 106 | 107 | exkeys = keys.get_exchanges(botconfig.active_exchanges) 108 | nexs = 0 109 | for ex in [*exkeys]: 110 | 111 | 112 | exchange = getattr(ccxt, ex)() 113 | 114 | try: 115 | # load all markets from the exchange 116 | 117 | 118 | # save it in a dictionary under its id for future use 119 | 120 | exchange.apiKey = exkeys[ex]['key'] 121 | exchange.secret = exkeys[ex]['secret'] 122 | exchange.password = keys.get_exchange_password(ex) 123 | Exchanges[ex] = exchange 124 | exchanges[ex] = (Exchange.Exchange(exchange)) 125 | markets = exchange.load_markets() 126 | exchange.currencies = exchange.fetch_currencies() 127 | # instantiate the exchange by id 128 | dump(green(ex), 'loaded', green(str(len(exchange.symbols))), 'markets') 129 | nexs += 1 130 | 131 | except Exception as e: 132 | print(e) 133 | pass 134 | 135 | dump(green('loaded '),(green(str(nexs)) if nexs == len(exkeys) else blue(str(nexs))), green(' of '), green(str(len(exkeys))), green(' markets.')) 136 | 137 | 138 | def load_coins(): 139 | 140 | coins = keys.get_keys(botconfig.active_coins) 141 | 142 | 143 | for coin in [*coins]: 144 | Currencies[coin] = CurrencyWrapper.Currency(coin, coins[coin]['public'], coins[coin]['private']) 145 | for c1 in [*Currencies]: 146 | # print(c1) 147 | for c2 in [*Currencies]: 148 | if Currencies[c1].Name != Currencies[c2].Name: 149 | Symbols.append(Currencies[c1].Name+"/"+Currencies[c2].Name) 150 | # print("Available Markets:") 151 | #for s in Symbols: 152 | # print(s) 153 | for id in [*Exchanges]: 154 | balancesheet = Exchanges[id].fetch_balance() 155 | #print(balancesheet) 156 | for currency in [*Currencies]: 157 | Currencies[currency].add_exchange(Exchanges[id], balancesheet) 158 | 159 | for c in [*Currencies]: 160 | Currencies[c].check_balance() 161 | 162 | 163 | def getArbitragePairs(): 164 | 165 | ids = [*Exchanges] 166 | for id in ids: 167 | marketSymbols[id] = [] 168 | allSymbols = [symbol for id in ids for symbol in Exchanges[id].symbols] 169 | 170 | # get all unique symbols 171 | uniqueSymbols = list(set(allSymbols)) 172 | 173 | # filter out symbols that are not present on at least two exchanges 174 | arbitrableSymbols = sorted([symbol for symbol in uniqueSymbols if allSymbols.count(symbol) > 1 and symbol in Symbols]) 175 | 176 | for symbol in arbitrableSymbols: 177 | pairedSymbols.append(symbol) 178 | pair = Pair.ArbPair() 179 | for id in ids: 180 | if symbol in Exchanges[id].symbols: 181 | exchanges[id].addPair() 182 | marketSymbols[id].append(symbol) 183 | 184 | # print a table of arbitrable symbols 185 | #table = [] 186 | #dump(green(' symbol | ' + ''.join([' {:<15} | '.format(id) for id in ids]))) 187 | #dump(green(''.join(['-----------------+-' for x in range(0, len(ids) + 1)]))) 188 | 189 | #for symbol in arbitrableSymbols: 190 | # string = ' {:<15} | '.format(symbol) 191 | # row = {} 192 | # for id in ids: 193 | # # if a symbol is present on a exchange print that exchange's id in the row 194 | # string += ' {:<15} | '.format(id if symbol in Exchanges[id].symbols else '') 195 | # dump(string) 196 | 197 | def is_active(symbol, exchange): 198 | b = base(symbol) 199 | q = quote(symbol) 200 | if b in exchange.currencies and q in exchange.currencies and symbol in exchange.markets and 'active' in exchange.currencies[b] and 'active' in exchange.currencies[q] and 'active' in exchange.markets[symbol]: 201 | return exchange.currencies[b]['active'] and exchange.currencies[q]['active'] and exchange.markets[symbol]['active'] 202 | else: 203 | return True 204 | 205 | def getAllArbitragePairs(): 206 | 207 | ids = [*Exchanges] 208 | for id in ids: 209 | marketSymbols[id] = [] 210 | allSymbols = [symbol for id in ids for symbol in Exchanges[id].symbols if check_symbol(symbol) and is_active(symbol, Exchanges[id])] 211 | 212 | # get all unique symbols 213 | uniqueSymbols = list(set(allSymbols)) 214 | 215 | # filter out symbols that are not present on at least two exchanges 216 | arbitrableSymbols = sorted([symbol for symbol in uniqueSymbols if allSymbols.count(symbol) > 1]) 217 | 218 | for symbol in arbitrableSymbols: 219 | pairedSymbols.append(symbol) 220 | b = base(symbol) 221 | q = quote(symbol) 222 | if not b in [*Currencies]: 223 | Currencies[b] = CurrencyWrapper.Currency(b) 224 | if not q in [*Currencies]: 225 | Currencies[q] = CurrencyWrapper.Currency(q) 226 | 227 | symbex[symbol] = [] 228 | 229 | for id in ids: 230 | if symbol in Exchanges[id].symbols and is_active(symbol, Exchanges[id]): 231 | marketSymbols[id].append(symbol) 232 | symbex[symbol].append(exchanges[id]) 233 | 234 | for i in range(0, len(symbex[symbol])): 235 | for j in range(i+1, len(symbex[symbol])): 236 | pairtuples.append((symbex[symbol][i], symbex[symbol][j], symbol)) 237 | 238 | 239 | 240 | def get_prices(): 241 | 242 | delay = 0 243 | maxLength = 0 244 | nmarkets = 0 245 | for id in [*marketSymbols]: 246 | rlm = int(Exchanges[id].rateLimit/1000) 247 | ls = len(marketSymbols[id]) 248 | nmarkets += ls 249 | if rlm > delay: 250 | delay = rlm 251 | if ls > maxLength: 252 | maxLength = ls 253 | 254 | dump(green("Fetching price data from "+str(nmarkets)+" markets...")) 255 | 256 | try: 257 | for i in range(0,maxLength): 258 | dump(yellow("loading ("+"%.1f" % (100*(i+0.0)/maxLength)+"%)...")) 259 | time.sleep(delay) 260 | for id in [*marketSymbols]: 261 | if i >= len(marketSymbols[id]): 262 | continue 263 | else: 264 | exchange = Exchanges[id] 265 | symbol = marketSymbols[id][i] 266 | try: 267 | orderbook = exchange.fetch_order_book(symbol) 268 | exchanges[id].add_book(orderbook, symbol) 269 | 270 | except ccxt.DDoSProtection as e: 271 | print(type(e).__name__, e.args, 'DDoS Protection (ignoring)') 272 | pass 273 | except ccxt.RequestTimeout as e: 274 | print(type(e).__name__, e.args, 'Request Timeout (ignoring)') 275 | pass 276 | except ccxt.ExchangeNotAvailable as e: 277 | print(type(e).__name__, e.args, 278 | 'Exchange Not Available due to downtime or maintenance (ignoring)') 279 | pass 280 | except ccxt.AuthenticationError as e: 281 | print(type(e).__name__, e.args, 'Authentication Error (missing API keys, ignoring)') 282 | pass 283 | except Exception as e: 284 | print("Exception:", e.args, exchange, symbol, str(symbol in exchange.symbols)) 285 | 286 | pass 287 | 288 | except Exception as e: 289 | 290 | print(type(e).__name__, e.args, str(e)) 291 | print_usage() 292 | 293 | 294 | def update_pairs(): 295 | 296 | dump('Trying '+yellow(str(len(pairtuples)))+' arbitrable trade pairs.') 297 | 298 | for p in pairtuples: 299 | #print(p[0], p[1], p[2]) 300 | pair = Pair.Pair(p[0], p[1], p[2]) 301 | # if pair.Margin > 1.0: 302 | # print(pair) 303 | # print(pair.Exsell is not None) 304 | # print(pair.min_trade()) 305 | # print(pair.max_trade()) 306 | if pair.ExSell is not None and pair.min_trade() < pair.max_trade(): 307 | pairs.append(pair) 308 | #print('pair: ', pair.Symbol) 309 | 310 | pairs.sort(key=lambda x: x.Margin, reverse=True) 311 | 312 | 313 | def get_prices_better(): 314 | 315 | #create exchange objects 316 | #create pair objects 317 | #add pair ovjects to exchanges 318 | 319 | return 320 | 321 | def get_balances(): 322 | 323 | 324 | #### Fetch free balances from exchanges and enter them into Balance objects. Should only run once during setup to prevent duplicate entries. Does not include balances currently allocated by exchanges for pending transactions. 325 | 326 | balancelist = [] 327 | 328 | for id in [*Exchanges]: 329 | 330 | balances = Exchanges[id].fetch_balance() 331 | for b in [*balances['free']]: 332 | amount = balances['free'][b] 333 | exchange = Exchanges[id] 334 | currency = Currencies[b] 335 | balancelist.append(Balance.Balance(amount, exchange, currency)) 336 | 337 | return balancelist 338 | 339 | 340 | 341 | def get_available_balances(): 342 | 343 | available_balances = [] 344 | for b in Balances: 345 | if not b.is_allocated(): 346 | available_balances.append(b) 347 | 348 | return merge_balances(available_balances) 349 | 350 | 351 | def merge_balances(balances): 352 | 353 | #### Merges balances held in the same coin on the same exchange. Takes a list of balances as an argument and returns a list of the merged entries. 354 | 355 | if len(balances) == 0: 356 | return [] 357 | 358 | mergedbalances = {balances[0].balance_key() : copy.copy(balances[0])} 359 | 360 | for b in balances[1:]: 361 | key = b.balance_key() 362 | if key in [*mergedbalances]: 363 | mergedbalances[key].Amount += b.Amount 364 | else: 365 | mergedbalances[key] = copy.copy(b) 366 | 367 | return mergedbalances.values() 368 | 369 | 370 | 371 | 372 | 373 | def check_balances(): 374 | pass 375 | 376 | 377 | def select_orders(): 378 | 379 | #get balance of all accounts for max 380 | #get max withdraw of different markets 381 | #get sorted list of profitable pairs 382 | #while ask and bid values are less than 80% max balance: 383 | # add most profitable bids and asks to queues 384 | # make sure queues are balanced 50/50 385 | # add order values to counters 386 | #combine all orders into 1 limit order per exchange 387 | 388 | pass 389 | 390 | def create_tx_paths(pairs, exchanges): 391 | 392 | pass 393 | 394 | 395 | 396 | def allocate_funds(): 397 | 398 | #use weight arrays to transfer funds from wallets to exchanges 399 | #remember that withdraw prices are already included into prices for pairsgg 400 | #prioritize moving funds on exchanges first, then funds in wallets 401 | #check for completed orders on exchanges and move funds from ask exchangtes to bid exchanges 402 | #withdraw all remaining funds on markets 403 | 404 | pass 405 | 406 | 407 | def place_orders(): 408 | 409 | #use trade atomic to place limit orders for queued trades 410 | #pop filled queued trades and add to pending list 411 | #figure out a way to block ask/transfer/bid transactions? 412 | #write placed orders to file 413 | pass 414 | 415 | 416 | def trade_atomic(exchange, symbol, price, volume): 417 | markets = exchange.load_markets(reload=True) 418 | ordeerbook = exchange.fetch_order_book() 419 | 420 | #todo: add interactive front end 421 | #todo: add visualization functions for market depth, arb pair prices, available funds vs total funds, profit 422 | #todo: add functionality to toggle trading 423 | 424 | 425 | def setup(): 426 | dump("ccxt v.",ccxt.__version__) 427 | load_exes() 428 | load_coins() 429 | 430 | def main(): 431 | 432 | setup() 433 | getAllArbitragePairs() 434 | get_prices() 435 | update_pairs() 436 | print(' ') 437 | print(green("Found "+str(len(pairs))+" arbitrage pairs:")) 438 | print(' ') 439 | sumprofit = 0 440 | maxamount = 0 441 | totalamount = 0 442 | for p in pairs: 443 | 444 | if p.Margin > botconfig.max_margin or p.Margin < botconfig.min_margin: 445 | pairs.remove(p) 446 | p.ExBuy.Pairs.remove(p) 447 | p.ExSell.Pairs.remove(p) 448 | else: 449 | print(str(p)) 450 | sumprofit += convert_to_USD(p.roi(p.max_trade(), sellbuy='sell'), p.Base) 451 | maxamount = max(convert_to_USD(p.max_trade(), p.Base), maxamount) 452 | totalamount += convert_to_USD(p.max_trade(), p.Base) 453 | 454 | print('Max Profit: $', "%.2f" % sumprofit) 455 | print('Max Trade Price: $',"%.2f" % maxamount) 456 | print('Total Transaction Costs: $',"%.2f" % totalamount) 457 | 458 | 459 | 460 | # for symbol in [*pairTable]: 461 | # bid = pairTable[symbol].bid_price() 462 | # ask = pairTable[symbol].ask_price() 463 | # 464 | # if(bid > ask): 465 | # bidVolume = pairTable[symbol].bid()['bids'][0][1] 466 | # askVolume = pairTable[symbol].ask()['asks'][0][1] 467 | # bidex = pairTable[symbol].bid_exchang"%.3f" % lowpercente().id 468 | # askex = pairTable[symbol].ask_exchange().id 469 | # print(symbol + " " + askex + "->" + bidex + " - " + "bid: " + str(bidVolume) + " @" + str(bid) + ", ask: " + str(askVolume) + " @" + str(ask) + ", margin: " + str(100*(bid-ask)/ask) + "%") 470 | 471 | 472 | if __name__ == "__main__": 473 | main() 474 | 475 | 476 | # def arbitrage(cycle_num=5, cycle_time=240): 477 | # #Create Triangular Arbitrage Function 478 | # print("Arbitrage Function Running") 479 | # fee_percentage = 0.001 #divided by 100 480 | # coins = ['BTC', 'LTC', 'ETH'] #Coins to Arbitrage 481 | # #Create Functionality for Binance 482 | # for exch in ccxt.exchanges: #initialize Exchange 483 | # exchange1 = getattr (ccxt, exch) () 484 | # symbols = exchange1.symbols 485 | # if symbols is None: 486 | # print("Skipping Exchange ", exch) 487 | # print("\n-----------------\nNext Exchange\n-----------------") 488 | # elif len(symbols)<15: 489 | # print("\n-----------------\nNeed more Pairs (Next Exchange)\n-----------------") 490 | # else: 491 | # print(exchange1) 492 | # 493 | # exchange1_info = dir(exchange1) 494 | # print("------------Exchange: ", exchange1.id) 495 | # #pprint(exchange1_info) 496 | # print(exchange1.symbols) #List all currencies 497 | # time.sleep(5) 498 | # #Find Currencies Trading Pairs to Trade 499 | # pairs = [] 500 | # for sym in symbols: 501 | # for symbol in coins: 502 | # if symbol in sym: 503 | # pairs.append(sym) 504 | # print(pairs) 505 | # #From Coin 1 to Coin 2 - ETH/BTC - Bid 506 | # #From Coin 2 to Coin 3 - ETH/LTC - Ask 507 | # #From Coin 3 to Coin 1 - BTC/LTC - Bid 508 | # arb_list = ['ETH/BTC'] #, 'ETH/LTC', 'BTC/LTC'] 509 | # #Find 'closed loop' of currency rate pairs 510 | # j=0 511 | # while 1: 512 | # if j == 1: 513 | # final = arb_list[0][-3:] + '/' + str(arb_list[1][-3:]) 514 | # print(final) 515 | # #if final in symbols: 516 | # arb_list.append(final) 517 | # break 518 | # for sym in symbols: 519 | # if sym in arb_list: 520 | # pass 521 | # else: 522 | # if j % 2 == 0: 523 | # if arb_list[j][0:3] == sym[0:3]: 524 | # if arb_list[j] == sym: 525 | # pass 526 | # else: 527 | # arb_list.append(sym) 528 | # print(arb_list) 529 | # j+=1 530 | # break 531 | # if j % 2 == 1: 532 | # if arb_list[j][-3:] == sym[-3:]: 533 | # if arb_list[j] == sym: 534 | # pass 535 | # else: 536 | # arb_list.append(sym) 537 | # print(arb_list) 538 | # j+=1 539 | # break 540 | # 541 | # #time.sleep(.5) 542 | # print("List of Arbitrage Symbols:", arb_list) 543 | # #time.sleep(3) 544 | # #Determine Rates for our 3 currency pairs - order book 545 | # list_exch_rate_list = [] 546 | # #Create Visualization of Currency Exchange Rate Value - Over Time 547 | # #Determine Cycle number (when data is taken) and time when taken 548 | # for k in range(0,cycle_num): 549 | # i=0 550 | # exch_rate_list = [] 551 | # print("Cycle Number: ", k) 552 | # for sym in arb_list: 553 | # print(sym) 554 | # if sym in symbols: 555 | # depth = exchange1.fetch_order_book(symbol=sym) 556 | # #pprint(depth) 557 | # if i % 2 == 0: 558 | # exch_rate_list.append(depth['bids'][0][0]) 559 | # else: 560 | # exch_rate_list.append(depth['asks'][0][0]) 561 | # i+=1 562 | # else: 563 | # exch_rate_list.append(0) 564 | # #exch_rate_list.append(((rateB[-1]-rateA[-1])/rateA[-1])*100) #Expected Profit 565 | # exch_rate_list.append(time.time()) #change to Human Readable time 566 | # print(exch_rate_list) 567 | # #Compare to determine if Arbitrage opp exists 568 | # if exch_rate_list[0] $',"%.2f" % botutils.convert_to_USD(pair.max_trade()+pair.roi(pair.max_trade()), 'ETH')) 135 | print('roilim:', (pair.roi(pair.max_trade())-pair.roi(pair.max_trade()*0.999))*1000) 136 | 137 | 138 | def test6(): 139 | symbol = 'ETH/BTC' 140 | t1 = Exchange.Exchange(ccxt.binance()) 141 | t1.add_book(abook, symbol) 142 | t2 = Exchange.Exchange(ccxt.hitbtc()) 143 | t2.add_book(bbook, 'ETH/BTC') 144 | pair = Pair.Pair(t1, t2, 'ETH/BTC') 145 | for i in range(int(10*pair.min_trade()), int(10*pair.max_trade())): 146 | print(pair.roi(i/10.0)) 147 | 148 | 149 | def test7(): 150 | symbol = 'ETH/BTC' 151 | t1 = Exchange.Exchange(ccxt.binance()) 152 | t1.add_book(abook, symbol) 153 | t2 = Exchange.Exchange(ccxt.hitbtc()) 154 | t2.add_book(abook, 'ETH/BTC') 155 | pair = Pair.Pair(t1, t2, 'ETH/BTC') 156 | print(pair.max_trade()) 157 | print(pair.min_trade()) 158 | print(pair.Margin) 159 | print(pair.margin(pair.max_trade())) 160 | print(pair.margin(pair.min_trade())) 161 | print('$', "%.2f" % botutils.convert_to_USD(pair.max_trade(), 'ETH'), ' -> $', 162 | "%.2f" % botutils.convert_to_USD(pair.max_trade() + pair.roi(pair.max_trade()), 'ETH')) 163 | 164 | def test8(): 165 | symbol = 'ETH/USDT' 166 | t1 = Exchange.Exchange(ccxt.exmo()) 167 | t1.add_book(t1.Ex.fetch_order_book(symbol), symbol) 168 | t2 = Exchange.Exchange(ccxt.ethfinex()) 169 | t2.add_book(t2.Ex.fetch_order_book(symbol), symbol) 170 | pair = Pair.Pair(t1, t2, 'ETH/USDT') 171 | 172 | 173 | def main(): 174 | test8() 175 | 176 | if __name__ == "__main__": 177 | main() 178 | --------------------------------------------------------------------------------