├── .gitignore ├── .ipynb_checkpoints └── ARIMA-checkpoint.ipynb ├── 0001.model ├── ARIMA.ipynb ├── README.md ├── __pycache__ └── helper.cpython-37.pyc ├── b_trader.py ├── blocks.py ├── build_model.py ├── calculate_pred.py ├── calculate_renko.py ├── data.py ├── engines.py ├── fix_data.py ├── helper.py ├── new_data.py ├── out.jpg ├── pyrenko.py ├── requirements.txt └── scaler.save /.gitignore: -------------------------------------------------------------------------------- 1 | *.ini 2 | *.pyc 3 | __pychache* 4 | *.csv 5 | *.model 6 | * 7 | -------------------------------------------------------------------------------- /0001.model: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackWBoynton/python_trader/c38370254a2ec60db4b3bc0660ae1cf6856fcd95/0001.model -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bitmex, robinhood, and binance algoritmic traders 2 | 3 | 1. runs a webhook that waits for POST requests from tradingview alerts, and trades accordingly 4 | 2. implements margin trading and take profit and stop loss placement 5 | 3. posts orders to SQL server 6 | 4. post order updates and balances to slack channels 7 | 8 | to run: 9 | 10 | 1. pip3 install -r requirements.txt 11 | 2. start ./ngrok http 7777 12 | 2. python3 webhook.py 13 | 14 | to calculate renko bricks locally and perform strategy calculations: 15 | 16 | * must have raw bitmex tick data in ../ for backtest 17 | 18 | 1. python3 calculate_renko.py 19 | -------------------------------------------------------------------------------- /__pycache__/helper.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackWBoynton/python_trader/c38370254a2ec60db4b3bc0660ae1cf6856fcd95/__pycache__/helper.cpython-37.pyc -------------------------------------------------------------------------------- /b_trader.py: -------------------------------------------------------------------------------- 1 | # seperate file for running and controlling the backtesting captial balances; 2 | import math 3 | from termcolor import colored 4 | #print colored('RED TEXT', 'red'), colored('GREEN TEXT', 'green') 5 | 6 | 7 | class trader: 8 | 9 | def __init__(self, bal): 10 | self.__bal = bal 11 | self._bal = bal 12 | self.bal_btc = bal 13 | self.open_contracts_usd = 0 14 | self.risk = 0.6 # risk 50% of capital, fee comes out of btc bal after trade comes out, therefore must be < 1.0 15 | self.long = False 16 | self.short = False 17 | self.leverage = 5 18 | self.fee = 0.0075 # 0.075% 19 | self.tot_fees = 0 20 | self.tot_profit = 0 21 | 22 | def buy(self, price): 23 | assert not self.long and self.open_contracts_usd == 0 24 | self.long = True 25 | self.open_price = price 26 | buying_power = math.floor(self.bal_btc * price * self.risk) * self.leverage # num contracts can buy --> USD 27 | fee = round((buying_power * self.fee) / price, 8) 28 | self.bal_btc -= fee 29 | self.tot_fees += fee 30 | self.open_contracts_usd = buying_power 31 | print(f"[+] bought {buying_power} contracts with {self.leverage}x leverage, fee: {fee} BTC, at {price}") 32 | 33 | def sell(self, price): 34 | assert not self.short and self.open_contracts_usd == 0 35 | self.short = True 36 | self.open_price = price 37 | selling_power = math.floor(self.bal_btc * price * self.risk) * self.leverage 38 | fee = round((selling_power * self.fee) / price,8) 39 | self.bal_btc -= fee 40 | self.tot_fees += fee 41 | self.open_contracts_usd = selling_power 42 | print(f"[+] shorted {selling_power} contracts with {self.leverage}x leverage, fee: {fee} BTC, at {price}") 43 | 44 | def close(self, price, time): 45 | assert self.long or self.short 46 | if self.short: 47 | profit = (1/self.open_price)-(1/price) 48 | profit *= -self.open_contracts_usd 49 | self.short = False 50 | elif self.long: 51 | profit = (1 / self.open_price) - (1 / price) 52 | profit *= self.open_contracts_usd 53 | self.long = False 54 | fee = round((self.open_contracts_usd * self.fee) / price, 8) 55 | self.bal_btc -= abs(fee) 56 | self.bal_btc += round(profit,8) 57 | self.tot_fees += abs(fee) 58 | self.tot_profit += round(profit,8) 59 | self.bal_btc = round(self.bal_btc, 8) 60 | #self.bal_btc += round((self.open_contracts_usd/self.leverage) / price,8) 61 | self.open_contracts_usd = 0 62 | 63 | print(f"{colored('[-]', 'green' if profit > 0 else 'red')} closed trade at {price}, profit: {profit} BTC, bal after: {self.bal_btc}, fee: {fee} BTC at {time}") 64 | 65 | def end(self,price,time): 66 | if self.long or self.short: 67 | self.close(price,time=time) 68 | if self.bal_btc - self._bal > 0: 69 | col = 'green' 70 | else: 71 | col = 'red' 72 | pct = ((self.bal_btc - self._bal)/self._bal)*100 73 | print(f"{colored('[**]',col)} end, change: {self.bal_btc - self._bal} BTC, ending bal {self.bal_btc} BTC, total profit: {self.tot_profit}, total fees: {self.tot_fees}, net: {self.tot_profit-self.tot_fees}, pct: {pct}") 74 | 75 | -------------------------------------------------------------------------------- /blocks.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from helper import load_dfs_mult 3 | # block data into 5 day segments for effecient memory analysis 4 | 5 | BLOCKSIZE = 5 6 | 7 | def get_data(days): 8 | final = [days[i * BLOCKSIZE:(i + 1) * BLOCKSIZE] for i in range((len(days) + BLOCKSIZE - 1) // BLOCKSIZE )] 9 | for i in final[::-1]: 10 | yield pd.DataFrame(load_dfs_mult("XBTUSD",files=i,location="../")) 11 | if len(days) % 5 != 0: 12 | yield pd.DataFrame(load_dfs_mult("XBTUSD",files=days,location="../")) 13 | 14 | -------------------------------------------------------------------------------- /build_model.py: -------------------------------------------------------------------------------- 1 | import xgboost as xgb 2 | 3 | dtrain = xgb.DMatrix('data-clean.csv?format=csv&label_column=0') 4 | dtest = xgb.DMatrix('data-clean.csv?format=csv&label_column=0') 5 | param = {'max_depth': 2, 'eta': 1, 'objective': 'binary:logistic'} 6 | param['nthread'] = 4 7 | param['eval_metric'] = 'auc' 8 | 9 | evallist = [(dtest, 'eval'), (dtrain, 'train')] 10 | 11 | num_round = 10 12 | bst = xgb.train(param, dtrain, num_round, evallist) 13 | bst.save_model('../0001.model') 14 | -------------------------------------------------------------------------------- /calculate_pred.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import xgboost as xgb 3 | import joblib 4 | scaler_filename = "scaler.save" 5 | scaler = joblib.load(scaler_filename) 6 | 7 | bst = xgb.Booster({'nthread': 4}) # init model 8 | bst.load_model('0001.model') # load data 9 | 10 | 11 | def main(ys, macd, sma, last_price): 12 | string = [] 13 | for i in ys: 14 | string.append(i) 15 | for j in macd: 16 | string.append(j[0]) 17 | for k in sma: 18 | string.append(k[0]) 19 | string.append(0) 20 | string.append(last_price) 21 | 22 | string = pd.DataFrame([string]) 23 | string = scaler.transform(string) 24 | string = pd.DataFrame(string, columns=range(0, 32)) 25 | return bst.predict(xgb.DMatrix(string))[0] 26 | -------------------------------------------------------------------------------- /calculate_renko.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Python3 3 | Main running script -- for pyrenko live brick calculation and indicator calculation 4 | Jack Boynton 2019 5 | ''' 6 | import pandas as pd 7 | import pyrenko 8 | import helper as helper 9 | import sys 10 | import datetime 11 | import argparse 12 | import matplotlib.pyplot as plt 13 | import time 14 | import calendar 15 | from tqdm import tqdm 16 | import matplotlib 17 | from blocks import get_data 18 | 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument("fast", nargs=3, type=int) 21 | parser.add_argument("-t", "--trade", type=int) 22 | parser.add_argument('-r', '--tr', type=str) 23 | parser.add_argument('-b', '--brick_size', type=int) 24 | parser.add_argument('-d', '--days', type=int) 25 | parser.add_argument('-o', '--order_type', type=str) 26 | parser.add_argument("-p", "--plot", type=bool) 27 | args = parser.parse_args() 28 | if args.order_type and args.order_type == 'Market' or args.order_type == 'Limit': 29 | pass 30 | else: 31 | print('must set order_type to market or limit') 32 | sys.exit(0) 33 | args.trade = bool(args.trade) 34 | print(args.trade) 35 | print('fast ma length: {}'.format(args.fast[0]), 'slow ma length: {}'.format( 36 | args.fast[1]), 'signal length: {}'.format(args.fast[2]), 'ord_type: ' + str(args.order_type)) 37 | time = datetime.date.today() - datetime.timedelta(days=1) # cant get todays data until tomorrow 38 | sta = [] 39 | for i in range(args.days): # gets all date csv files in home directory 40 | sta.append('../' + datetime.datetime.strftime(time - 41 | datetime.timedelta(days=i), "%Y%m%d") + '.csv') 42 | 43 | print('starting to load csv backtest data... days: ' + str(args.days)) 44 | 45 | #data = pd.DataFrame(helper.load_dfs_mult('XBTUSD', files=sta, location='../')) # uses multiprocessing to parse huge csv datafiles 46 | data = get_data(sta) # get_data is a generator that returns a pandas dataframe for 5 day chunks of data 47 | 48 | print('finished loading csv backtest data... starting renko brick calculation') 49 | renko_obj = pyrenko.renko(plot=False, j_backtest=True, fast=int(args.fast[0]), slow=int( 50 | args.fast[1]), signal_l=int(args.fast[2]), to_trade=args.trade, strategy=args.tr, ordtype=args.order_type) 51 | renko_obj.set_brick_size(brick_size=args.brick_size, auto=False) # sets brick_size hyperparam in dollars 52 | while True: 53 | try: 54 | renko_obj.build_history(prices=next(data), timestamps=['']) # builds renko backtest 55 | except Exception as e: 56 | print(e) 57 | break 58 | 59 | trades = renko_obj.plot_renko() # starts live renko brick calculation 60 | # 2019-12-17D23:09:17.575367000 # sample timestamp from bitmex 61 | if args.plot: 62 | macd_ = renko_obj.macd() 63 | fast, slow = renko_obj.ma() 64 | times = renko_obj.act_timestamps 65 | wma, times_wma = renko_obj.wma(9) 66 | p = "%Y-%m-%d%H:%M:%S.%f000" 67 | timestampss = [] 68 | """ 69 | plt.figure(figsize=(20,20)) 70 | for n, point in tqdm(enumerate(data[1]),total=len(data[1])): ## somehow improve parsing time??? 71 | point = point.replace("D","") 72 | st = (datetime.datetime.strptime(point,p)) 73 | timestampss.append(calendar.timegm(st.timetuple())) 74 | macd_timestamps = [] 75 | for m, pt in tqdm(enumerate(times[-len(macd_):]),total=len(times[-len(macd_):])): 76 | pt = pt.replace("D","") 77 | ac = (datetime.datetime.strptime(pt,p)) 78 | macd_timestamps.append(calendar.timegm(ac.timetuple())) 79 | timestamps_wma = [] 80 | for a, b in tqdm(enumerate(times_wma),total=len(times_wma)): 81 | b = b.replace("D","") 82 | ad = (datetime.datetime.strptime(b,p)) 83 | timestamps_wma.append(calendar.timegm(ad.timetuple())) 84 | #plt.plot(macd_timestamps, macd_) 85 | plt.plot(timestampss, data[2]) 86 | plt.plot(macd_timestamps, slow) 87 | plt.plot(macd_timestamps, fast) 88 | plt.plot(timestamps_wma, wma, c="#00ff00") 89 | for i in trades: 90 | if i[0] == 1: 91 | point = i[1].replace("D","") 92 | timestamp = (datetime.datetime.strptime(point,p)) 93 | plt.scatter(calendar.timegm(timestamp.timetuple()),[i[2]], c="#00ff00") 94 | else: 95 | point = i[1].replace("D","") 96 | timestamp = (datetime.datetime.strptime(point,p)) 97 | plt.scatter(calendar.timegm(timestamp.timetuple()),[i[2]], c="#ff0000") 98 | 99 | plt.savefig("out.jpg") 100 | plt.close() 101 | """ 102 | rsi = [] 103 | for i in renko_obj.rsi(): 104 | rsi.append(i[0]) 105 | #print(rsi) 106 | plt.plot(list(range(len(rsi))),rsi) 107 | plt.show() 108 | -------------------------------------------------------------------------------- /data.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | def new_trade(past_bricks, price_open, price_close, side, macd_open, macd_close, sma_open, sma_close, time_open, time_close): 4 | string = '' 5 | for i in past_bricks: 6 | string += str(i) + ',' 7 | for j in macd_open: 8 | string += str(j[0]) + ',' 9 | for k in sma_open: 10 | string += str(k[0]) + ',' 11 | 12 | if side == 1 and price_close > price_open: 13 | profit = 1 14 | elif side == 0 and price_open > price_close: 15 | profit = 1 16 | else: 17 | profit = 0 18 | 19 | with open('data-raw.csv', 'a') as f: 20 | f.write(string + str(price_open) + ',' + str(price_close) + ',' + str(side) + ',' + str(macd_open[0]) + ',' + str(macd_close[0]) + 21 | ',' + str(sma_open[0]) + ',' + str(sma_close[0]) + ',' + str(time_open) + ',' + str(time_close) + ',' + str(profit) + '\n') 22 | 23 | with open('data-clean.csv', 'a') as f: 24 | # STRING: past_bricks(10),macd_open(10),sma_open(10),price_open,profitable? 25 | f.write(str(profit) + ',' + string + ',' + str(price_open) + '\n') 26 | -------------------------------------------------------------------------------- /engines.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | 3 | # This file contains all of the trading logic 4 | 5 | import time 6 | from math import floor 7 | import slack 8 | from statistics import mean 9 | import bitmex 10 | import requests 11 | from binance.client import Client as binance_client 12 | import robin_stocks as r 13 | from bravado.exception import HTTPServiceUnavailable, HTTPBadRequest, HTTPTooManyRequests 14 | import alpaca_trade_api as tradeapi 15 | import configparser 16 | import pymysql 17 | Config = configparser.ConfigParser() 18 | Config.read("config.ini") # load api_keys 19 | 20 | API_KEY = "PK9NQMLQ6JQIUVEHNMLR" 21 | API_SECRET = "t9bh74YO5jPhKbo3EA0yQoLYfaedU/2Jg79NTzqS" 22 | APCA_API_BASE_URL = "https://paper-api.alpaca.markets" 23 | 24 | 25 | class AlpacaTrader(): 26 | def __init__(self): 27 | self.auth_client_alpaca = tradeapi.REST(Config.get('Alpaca', 'api_key'), Config.get('Alpaca','api_secret'),APCA_API_BASE_URL, api_version='v2') # or use ENV Vars shown below 28 | self.account = self.auth_client_alpaca.get_account() 29 | self.equity = float(self.account.equity) 30 | 31 | def buy_long(self, asset): 32 | try: 33 | self.auth_client_alpaca.submit_order(symbol=asset,qty=10,side='buy',type='market',time_in_force='gtc') 34 | except Exception as e: 35 | print (str(e)) 36 | finally: 37 | print ('buying 10 ' + str(asset) + ' on ALPACA') 38 | 39 | def sell_short(self, asset): 40 | try: 41 | self.auth_client_alpaca.submit_order(symbol=asset,qty=10,side='sell',type='market',time_in_force='gtc') 42 | except Exception as e: 43 | print (str(e)) 44 | finally: 45 | print ('selling 10 ' + str(asset) + ' on ALPACA') 46 | 47 | 48 | class BitmexTrader(): 49 | 50 | def __init__(self, trade, leverage, tp, test, ord_type): 51 | self.bitmex_api_key = Config.get('Bitmex', 'api_key') 52 | self.bitmex_api_secret = Config.get('Bitmex', 'api_secret') 53 | self.bitmex_api_key_t = Config.get('Bitmex-Testnet', 'api_key') 54 | self.bitmex_api_secret_t = Config.get('Bitmex-Testnet', 'api_secret') 55 | self.slack_api = Config.get("Slack", 'api_key') 56 | self.trade = trade 57 | self.long = False 58 | self.type = 'GoodTillCancel' 59 | self.ord_type = ord_type 60 | self.short = False 61 | print('sending trades? ' + str(self.trade)) 62 | self.leverage = leverage 63 | self.take_profit = tp 64 | self.stop_loss = 0.1 # 10% 65 | self.slips = [] 66 | 67 | if test and 1==2: 68 | self.auth_client_bitmex = bitmex.bitmex( 69 | test=True, api_key=self.bitmex_api_key_t, api_secret=self.bitmex_api_secret_t) 70 | print('testnet') 71 | elif 1==2: 72 | self.auth_client_bitmex = bitmex.bitmex( 73 | test=False, api_key=self.bitmex_api_key, api_secret=self.bitmex_api_secret) 74 | print('LIVE') 75 | try: 76 | self.auth_client_bitmex.Position.Position_updateLeverage( 77 | symbol='XBTUSD', leverage=leverage).result() 78 | self.last_bal = float(self.auth_client_bitmex.User.User_getMargin().result()[0]['marginBalance'] / 100000000) 79 | except: 80 | pass 81 | 82 | self.channel = 'tradeupdates' 83 | self.channel_trades = 'trades' 84 | self.client = slack.WebClient(self.slack_api, timeout=30) 85 | self.trade_template = {'signal_price':0.0, 'fill_price':0.0, 'quantity':0.0, 'leverage':1, 'side':'', 'timestamp':''} 86 | 87 | def db(self): 88 | ## TRADE = {'signal_price':float, 'fill_price':float, 'quantity':float, 'leverage':int, 'side',string, 'timestamp':string} 89 | connection = pymysql.connect(host='localhost', 90 | user='root', 91 | password='Starluna1', 92 | db='trades', 93 | cursorclass=pymysql.cursors.DictCursor) 94 | try: 95 | with connection.cursor() as cursor: 96 | sql = "INSERT INTO `trades_raw` (`signal_price`, `fill_price`, `quantity`, `leverage`, `side`, `timestamp`) VALUES (%s, %s, %s, %s, %s, %s)" 97 | cursor.execute(sql, (str(self.trade_template['signal_price']), str(self.trade_template['fill_price']), str(self.trade_template['quantity']), str(self.trade_template['leverage']), str(self.trade_template['side']), str(self.trade_template['timestamp']))) 98 | connection.commit() 99 | finally: 100 | connection.close() 101 | 102 | self.trade_template = {'signal_price':0.0, 'fill_price':0.0, 'quantity':0.0, 'leverage':1, 'side':'', 'timestamp':''} 103 | 104 | def buy_long(self, ex, pair, ind, pric, risk): 105 | if self.trade: 106 | self.client.chat_postMessage(channel=self.channel, text='BUY:BITMEX:XBTUSD') 107 | self.auth_client_bitmex.Order.Order_cancelAll().result() 108 | if self.short: 109 | if self.ord_type == 'Limit': 110 | print('trying long' + str(pric)) 111 | close = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Limit', execInst='Close', price=pric, timeInForce=self.type).result() 112 | time.sleep(1) 113 | runs = 1 114 | while close[0]['ordStatus'] != 'Filled': 115 | self.auth_client_bitmex.Order.Order_cancelAll().result() 116 | print('trying ' + str(pric-(5-runs*0.5))) 117 | close = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Limit', execInst='Close', price=pric-(5-runs*0.5), timeInForce=self.type).result() 118 | runs += 1 119 | time.sleep(1) 120 | else: 121 | close = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Market', execInst='Close').result() 122 | time.sleep(2) 123 | self.short = False 124 | self.trade_template['signal_price'] = pric 125 | self.trade_template['fill_price'] = float(close[0]['price']) 126 | self.trade_template['quantity'] = float(close[0]['orderQty']) 127 | self.trade_template['leverage'] = self.leverage 128 | self.trade_template['side'] = 'close' 129 | self.trade_template['timestamp'] = str(close[0]['timestamp']) 130 | self.db() 131 | new_bal = float(self.auth_client_bitmex.User.User_getMargin().result()[0]['marginBalance'] / 100000000) 132 | try: 133 | self.client.chat_postMessage(channel=self.channel_trades, text='closed short at ' + str(close[0]['price']) + '. profit: $' + str(round((new_bal - self.last_bal) * self.last_risk * float(requests.get("https://www.bitmex.com/api/v1/orderBook/L2?symbol=xbt&depth=1").json()[1]['price']), 3))) 134 | self.last_bal = float(self.auth_client_bitmex.User.User_getMargin().result()[0]['marginBalance'] / 100000000) 135 | except: 136 | pass 137 | 138 | bal = self.auth_client_bitmex.User.User_getMargin().result()[0]['availableMargin'] / 100000000 139 | price = float(requests.get("https://www.bitmex.com/api/v1/orderBook/L2?symbol=xbt&depth=1").json()[1]['price']) 140 | order_q = floor(bal * risk * self.leverage * price) - 10 141 | 142 | try: 143 | if self.ord_type == 'Limit': 144 | print('trying long: ' + str(pric)) 145 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=order_q, price=pric, timeInForce=self.type).result() 146 | time.sleep(5) 147 | runs = 1 148 | while order[0]['ordStatus'] != 'Filled': 149 | self.auth_client_bitmex.Order.Order_cancelAll().result() 150 | print('trying: ' + str(pric-(runs*0.5))) 151 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=order_q, price=pric+(runs*0.5), timeInForce=self.type).result() 152 | runs += 1 153 | time.sleep(5) 154 | else: 155 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=order_q).result() 156 | except HTTPServiceUnavailable as e: 157 | self.client.chat_postMessage(channel=self.channel_trades, text='error: ' + str(e) + ' retrying...') 158 | ord = '' 159 | while ord != 'Filled': 160 | time.sleep(0.6) 161 | try: 162 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=order_q).result() 163 | except HTTPServiceUnavailable: 164 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=order_q).result() 165 | ord = order[0]['ordStatus'] 166 | except HTTPBadRequest or HTTPTooManyRequests as r: 167 | time.sleep(5) 168 | try: 169 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=order_q-10).result() 170 | except: 171 | self.client.chat_postMessage(channel=self.channel_trades, text='error: ' + str(r) + ' FATAL!!! order not placed') 172 | finally: 173 | self.slips.append(float(abs(ind-float(order[0]['price']))/0.5)) 174 | print('bought long on bitmex: ' + str(order[0]['orderQty']) + ' @ ' + str(order[0]['price']), 'slip: $' + str(abs(ind-float(order[0]['price']))), 'ticks: ' + str((ind-float(order[0]['price']))/0.5), 'average tick slip: ' + str(mean(self.slips))) 175 | 176 | try: 177 | self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='MarketIfTouched', stopPx=floor(price * (1 + self.take_profit / self.leverage) * 0.5) / 0.5, orderQty=-order_q).result() 178 | except HTTPServiceUnavailable: 179 | print('503 retrying...') 180 | time.sleep(0.6) 181 | self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='MarketIfTouched', stopPx=floor(price * (1 + self.take_profit / self.leverage) * 0.5) / 0.5, orderQty=-order_q).result() 182 | finally: 183 | #print('placed tp at: ' + str(floor(price * (1 + self.take_profit / self.leverage) * 0.5) / 0.5)) 184 | pass 185 | 186 | try: 187 | self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Stop', stopPx=floor((price - (price * self.stop_loss / self.leverage)) * 0.5) / 0.5, orderQty=-order_q).result() 188 | except HTTPServiceUnavailable: 189 | print('503 retrying...') 190 | time.sleep(0.6) 191 | self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Stop', stopPx=floor((price - (price * self.stop_loss / self.leverage)) * 0.5) / 0.5, orderQty=-order_q).result() 192 | finally: 193 | #print('placed sl at: ' + str(floor((price - (price * self.stop_loss / self.leverage)) * 0.5) / 0.5)) 194 | pass 195 | 196 | if order[0]['ordStatus'] == 'Filled': 197 | self.last_risk = risk 198 | self.client.chat_postMessage(channel=self.channel_trades, text='bought: ' + str(round(float(order[0]['orderQty']) / self.leverage, 3)) + ' XBT with ' + str(self.leverage) + ' X leverage at $' + str(order[0]['price']) + ' risk: ' + str(round(risk, 6))) 199 | self.long = True 200 | self.trade_template['signal_price'] = pric 201 | self.trade_template['fill_price'] = float(order[0]['price']) 202 | self.trade_template['quantity'] = float(order[0]['orderQty']) 203 | self.trade_template['leverage'] = self.leverage 204 | self.trade_template['side'] = 'BUY' 205 | self.trade_template['timestamp'] = str(order[0]['timestamp']) 206 | self.db() 207 | 208 | def sell_short(self, ex, pair, ind, pric, risk): 209 | print(str(pric) + '\n') 210 | if self.trade: 211 | self.client.chat_postMessage(channel=self.channel, text='SELL:BITMEX:XBTUSD') 212 | self.auth_client_bitmex.Order.Order_cancelAll().result() 213 | if self.long: 214 | if self.ord_type == 'Limit': 215 | print('trying ' + str(pric)) 216 | close = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Limit', execInst='Close', price=pric).result() 217 | time.sleep(5) 218 | runs = 1 219 | while close[0]['ordStatus'] != 'Filled': 220 | self.auth_client_bitmex.Order.Order_cancelAll().result() 221 | print('trying ' + str(pric+(5-runs*0.5))) 222 | close = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Limit', execInst='Close', price=pric+(5-runs*0.5), timeInForce=self.type).result() 223 | runs += 1 224 | time.sleep(5) 225 | else: 226 | close = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Market', execInst='Close').result() 227 | time.sleep(1) 228 | self.long = False 229 | self.trade_template['signal_price'] = pric 230 | self.trade_template['fill_price'] = float(close[0]['price']) 231 | self.trade_template['quantity'] = float(close[0]['orderQty']) 232 | self.trade_template['leverage'] = self.leverage 233 | self.trade_template['side'] = 'close' 234 | self.trade_template['timestamp'] = str(close[0]['timestamp']) 235 | self.db() 236 | new_bal = float(self.auth_client_bitmex.User.User_getMargin().result()[0]['marginBalance'] / 100000000) 237 | try: 238 | self.client.chat_postMessage(channel=self.channel_trades, text='closed long at ' + str(close[0]['price']) + '. profit: $' + str(round((new_bal - self.last_bal) * self.last_risk * float(requests.get("https://www.bitmex.com/api/v1/orderBook/L2?symbol=xbt&depth=1").json()[1]['price']), 3))) 239 | self.last_bal = float(self.auth_client_bitmex.User.User_getMargin().result()[0]['marginBalance'] / 100000000) 240 | except: 241 | pass 242 | 243 | price = float(requests.get("https://www.bitmex.com/api/v1/orderBook/L2?symbol=xbt&depth=1").json()[1]['price']) 244 | bal = self.auth_client_bitmex.User.User_getMargin().result()[0]['availableMargin'] / 100000000 245 | 246 | try: 247 | if self.ord_type == 'Limit': 248 | print('trying... ' + str(pric)) 249 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=-floor(bal * risk * self.leverage * price) + 10, price=pric, timeInForce=self.type).result() 250 | time.sleep(5) 251 | runs = 1 252 | while order[0]['ordStatus'] != 'Filled': 253 | self.auth_client_bitmex.Order.Order_cancelAll().result() 254 | print('trying... ' + str(pric+(runs*0.5))) 255 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=-floor(bal * risk * self.leverage * price) + 10, price=pric-(runs*0.5), timeInForce=self.type).result() 256 | runs += 1 257 | time.sleep(5) 258 | else: 259 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=-floor(bal * risk * self.leverage * price) + 10).result() 260 | except HTTPServiceUnavailable as e: 261 | print(str(e) + ' retrying...') 262 | self.client.chat_postMessage(channel=self.channel_trades, text='error: ' + str(e) + ' retrying...') 263 | ord = '' 264 | while ord != 'Filled': 265 | time.sleep(0.6) 266 | try: 267 | bal = self.auth_client_bitmex.User.User_getMargin().result()[0]['availableMargin'] / 100000000 268 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=-floor(bal * risk * self.leverage * price) + 15).result() 269 | except HTTPServiceUnavailable: 270 | order = self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', orderQty=-floor(bal * risk * self.leverage * price) + 15).result() 271 | ord = order[0]['ordStatus'] 272 | except HTTPBadRequest or HTTPTooManyRequests as r: 273 | time.sleep(5) 274 | print('short: ' + str(-floor(bal * risk * self.leverage * price) + 15)) 275 | self.client.chat_postMessage(channel=self.channel_trades, text='error: ' + str(r) + ' FATAL!!! order not placed') 276 | finally: 277 | self.slips.append(float(abs(ind-float(order[0]['price']))/0.5)) 278 | print('sold short on bitmex: ' + str(order[0]['orderQty']) + ' @ ' + str(order[0]['price']), 'slip: $' + str(abs(ind-float(order[0]['price']))), 'ticks: ' + str((ind-float(order[0]['price']))/0.5), 'average tick slip: ' + str(mean(self.slips))) 279 | 280 | try: 281 | self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='MarketIfTouched', stopPx=floor(price * (1 - self.take_profit / self.leverage) * 0.5) / 0.5, orderQty=floor(bal * self.leverage * price) + 15).result() 282 | except HTTPServiceUnavailable: 283 | print('503 retrying...') 284 | time.sleep(0.6) 285 | self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='MarketIfTouched', stopPx=floor(price * (1 - self.take_profit / self.leverage) * 0.5) / 0.5, orderQty=floor(bal * self.leverage * price) + 15).result() 286 | finally: 287 | #print('placed tp at: ' + str(floor(price *(1 - self.take_profit / self.leverage) * 0.5) / 0.5)) 288 | pass 289 | 290 | try: 291 | self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Stop', stopPx=floor((price + (price * self.stop_loss / self.leverage)) * 0.5) / 0.5, orderQty=floor(bal * self.leverage * price) + 15).result() 292 | except HTTPServiceUnavailable: 293 | print('503 retrying...') 294 | time.sleep(0.6) 295 | self.auth_client_bitmex.Order.Order_new(symbol='XBTUSD', ordType='Stop', stopPx=floor((price + (price * self.stop_loss / self.leverage)) * 0.5) / 0.5, orderQty=floor(bal * self.leverage * price) + 15).result() 296 | finally: 297 | #print('placed sl at: ' + str(floor((price + (price *self.stop_loss / self.leverage)) * 0.5) / 0.5)) 298 | pass 299 | if order[0]['ordStatus'] == 'Filled': 300 | self.last_risk = risk 301 | self.client.chat_postMessage(channel=self.channel_trades, text='shorted: ' + str(round(float(-order[0]['orderQty']), 3) / self.leverage) + ' XBT with ' + str(self.leverage) + ' X leverage at $' + str(order[0]['price']) + ' risk: ' + str(round(risk, 6))) 302 | self.short = True 303 | self.trade_template['signal_price'] = pric 304 | self.trade_template['fill_price'] = float(order[0]['price']) 305 | self.trade_template['quantity'] = float(order[0]['orderQty']) 306 | self.trade_template['leverage'] = self.leverage 307 | self.trade_template['side'] = 'SELL' 308 | self.trade_template['timestamp'] = str(order[0]['timestamp']) 309 | self.db() 310 | class BinanceTrader(): 311 | def __init__(self): 312 | self.binance_api_key = Config.get('Binance', 'api_key') 313 | self.binance_api_secret = Config.get('Binance', 'api_secret') 314 | self.auth_client_binance = binance_client( 315 | self.binance_api_key, self.binance_api_secret) 316 | data = self.auth_client_binance.get_symbol_info('BNBUSDT')['filters'] 317 | data = data[5] 318 | self.min_size = data['minQty'] 319 | self.max_size = data['maxQty'] 320 | self.step = data['stepSize'] 321 | 322 | def buy_long(self): 323 | balance = float( 324 | self.auth_client_binance.get_asset_balance(asset='USDT')['free']) 325 | if floor(balance / float(requests.get("https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT").json()['price']) / 0.01) * 0.01 - 0.01 == 0: 326 | print('unable to but must sell first') 327 | else: 328 | print('qty: ' + str(floor(balance / float(requests.get( 329 | "https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT").json()['price']) / 0.01) * 0.01 - 0.01)) 330 | order = self.auth_client_binance.order_market_buy(symbol='BNBUSDT', quantity=floor( 331 | balance / float(requests.get("https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT").json()['price']) / 0.01) * 0.01 - 0.01) 332 | if order['status'] == 'FILLED': 333 | print('bought ' + str(order['origQty']) + 334 | ' at ' + str(order['fills'][0]['price'])) 335 | else: 336 | print('not confirmed but bought ' + str(floor(balance / float(requests.get( 337 | "https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT").json()['price']) / 0.01) * 0.01 - 0.01) + ' at market price') 338 | print('bought BNB on Binance') 339 | 340 | def sell_short(self): 341 | if float(self.auth_client_binance.get_asset_balance(asset='BNB')['free']) > 1: 342 | selling_power = floor( 343 | float(self.auth_client_binance.get_asset_balance(asset='BNB')['free'])) 344 | elif float(self.auth_client_binance.get_asset_balance(asset='BNB')['free']) > 0.01: 345 | selling_power = floor(float(self.auth_client_binance.get_asset_balance( 346 | asset='BNB')['free']) / 0.01) * 0.01 347 | else: 348 | selling_power = 0 349 | 350 | if selling_power != 0: 351 | order = self.auth_client_binance.order_market_sell( 352 | symbol='BNBUSDT', quantity=selling_power) 353 | if order['status'] == 'FILLED': 354 | print('sold ' + str(order['origQty']) + 355 | ' at ' + str(order['fills'][0]['price'])) 356 | else: 357 | print('not confirmed but sold ' + 358 | str(selling_power) + ' at market price') 359 | else: 360 | print('unable to sell, must buy first') 361 | print('sold BNB on Binance') 362 | 363 | 364 | class RobinhoodTrader(): 365 | def __init__(self): 366 | self.em = Config.get("Robinhood", "email") 367 | self.pas = Config.get("Robinhood", 'password') 368 | r.login(self.em, self.pas) 369 | self.my_stocks = r.build_holdings() 370 | 371 | def buy_long(self, stock): 372 | buying_power = float(r.profiles.load_account_profile()[ 373 | 'margin_balances']['day_trade_buying_power']) 374 | if not self.my_stocks: 375 | try: 376 | qty = floor(buying_power / 377 | float(r.stocks.get_latest_price(stock)[0])) 378 | r.order_buy_market(stock, qty) 379 | print('bought ' + str(qty) + ' : ' + stock) 380 | self.my_stocks = r.build_holdings() 381 | except Exception as e: 382 | print(e) 383 | 384 | def sell_short(self, stock): 385 | if self.my_stocks: 386 | try: 387 | for i in self.my_stocks: 388 | if i == stock: 389 | bal = float(i['quantity']) 390 | r.order_sell_market(stock, bal) 391 | print('sold ' + str(bal) + ' : ' + stock) 392 | self.my_stocks = r.build_holdings() 393 | except Exception as e: 394 | print(e) 395 | -------------------------------------------------------------------------------- /fix_data.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import wget 3 | import gzip 4 | import glob 5 | import argparse 6 | import os 7 | 8 | 9 | def fix_absolute(filename, location='../'): 10 | if filename not in glob.glob(location+'*.csv'): 11 | #print(f'dop... {filename}') 12 | 13 | print('downloading new data for: ' + str(filename)) 14 | wget.download(url="https://s3-eu-west-1.amazonaws.com/public.bitmex.com/data/quote/{}.gz".format(filename), out=location) 15 | input = gzip.GzipFile(location + filename+'.gz', 'rb') 16 | s = input.read() 17 | input.close() 18 | output = open(location + filename, 'wb') 19 | output.write(s) 20 | output.close() 21 | # Parse: 22 | print('\nparsing') 23 | asset = 'XBTUSD' 24 | data = pd.read_csv(location+filename, header=None, low_memory=False, usecols=[0, 1, 3], dtype={0: str, 1: str, 3: float}, skiprows=2) 25 | for n, j in enumerate(data[1]): 26 | if j == asset: 27 | data = pd.DataFrame(data.values[n:]) 28 | break 29 | for n, k in enumerate(data[1]): 30 | if k != asset: 31 | data = pd.DataFrame(data.values[:n]) 32 | break 33 | del data[1] 34 | data.to_csv(location+filename) 35 | print('done') 36 | return 1 37 | else: 38 | os.remove(filename) 39 | fix_absolute(filename) 40 | 41 | 42 | def fix(filename): 43 | # called from other functions 44 | # TODO: 45 | print('fix') 46 | 47 | 48 | if __name__ == '__main__': 49 | # if called from commandline 50 | parser = argparse.ArgumentParser() 51 | parser.add_argument("filename", nargs=1, type=str) 52 | args = parser.parse_args() 53 | 54 | fix_absolute(args.filename) 55 | -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import glob 3 | from tqdm import tqdm 4 | from multiprocessing import Pool 5 | from new_data import download_new 6 | from fix_data import fix_absolute 7 | len_df = 0 8 | files_ = [] 9 | 10 | 11 | def load_df(ind, filename): 12 | global len_df 13 | #print(filename) 14 | if ".csv.csv" in filename: # never hits 15 | filename = filename[0:-3] 16 | #print(filename) 17 | len_df += 1 18 | tqdm.pandas(desc="load csvs #" + str(ind) + ' ' + str(files_[ind])) 19 | try: 20 | data = pd.read_csv(filename, header=None, low_memory=True, dtype={0: str, 1: str, 21 | 3: float}, skiprows=2, na_values=0).progress_apply(lambda x: x) 22 | except Exception as e: 23 | print(e) 24 | return data 25 | 26 | 27 | def load_dfs(asset, files): 28 | frm = files[0].split('/')[1].split('.')[0] 29 | too = files[-1].split('/')[1].split('.')[0] 30 | print('backtest dates: ' + frm + '-' + too) 31 | if 1 == 1 or not glob.glob('../loaded' + frm + too + '.csv'): 32 | a = [] 33 | first = True 34 | for i in tqdm(files): 35 | data = load_df(filename=i) 36 | if not first: 37 | a = pd.concat([a, data], ignore_index=True) 38 | else: 39 | first = False 40 | a = data 41 | a.to_csv(path_or_buf='../loaded' + frm + too + '.csv', header=False) 42 | else: 43 | a = pd.read_csv('../loaded' + frm + too + '.csv', header=None, 44 | low_memory=False, dtype={1: float}, usecols=[0, 1], skiprows=2, na_values=0) 45 | print('loaded ' + str(a.shape[0]) + ' ticks of data') 46 | return a 47 | 48 | 49 | def load_dfs_mult(asset, files, location="../"): 50 | download_new(location) 51 | # multiprocessing version of load_dfs 52 | print(f"loading {len(files)} files") 53 | for n, i in enumerate(files): 54 | if location == '../': 55 | if i.split('/')[1].split('.')[0] == '20190927': 56 | del files[n] # remove wonky day's data 57 | frm = files[0].split('/')[1].split('.')[0] 58 | too = files[-1].split('/')[1].split('.')[0] 59 | else: 60 | if i.split('.')[0] == '20190927': 61 | del files[n] 62 | frm = files[0].split('.')[0] 63 | too = files[-1].split('.')[0] 64 | 65 | files.reverse() 66 | print('backtest dates: ' + frm + '-' + too) 67 | global files_ 68 | files_ = files 69 | for file_ in files: 70 | if file_ not in sorted(glob.glob("../*.csv")): 71 | #print(f"downloading {filename}") 72 | print(file_) 73 | fix_absolute(file_.replace("../","")) 74 | if 1 == 1 or not glob.glob(location+'loaded' + frm + too + '.csv'): 75 | with Pool(processes=8) as pool: 76 | df_list = (pool.starmap(load_df, enumerate(files))) 77 | tqdm.pandas(desc="concat csvs") 78 | combined = pd.concat(df_list, ignore_index=True).progress_apply(lambda x: x) # apply dummy lambda fn to call tqdm.pandas() 79 | #combined.to_csv(path_or_buf=location+'loaded' + frm + too + '.csv', header=False) 80 | else: 81 | combined = pd.read_csv(location+'loaded' + frm + too + '.csv', header=None, 82 | low_memory=False, dtype={1: float}, usecols=[0, 1], skiprows=2, na_values=0) 83 | print('loaded ' + str(combined.shape[0]) + ' ticks of data') 84 | return combined 85 | -------------------------------------------------------------------------------- /new_data.py: -------------------------------------------------------------------------------- 1 | import wget 2 | import datetime 3 | import glob 4 | import gzip 5 | import pandas as pd 6 | import argparse 7 | 8 | def download_new(location): 9 | 10 | date = datetime.date.today() - datetime.timedelta(days=1) 11 | if location+date.strftime("%Y%m%d")+'.csv' not in glob.glob(location+'*.csv'): 12 | print('downloading new days data') 13 | wget.download(url="https://s3-eu-west-1.amazonaws.com/public.bitmex.com/data/quote/{}.csv.gz".format(date.strftime('%Y%m%d')), out=location) 14 | input = gzip.GzipFile(location+date.strftime("%Y%m%d")+'.csv.gz', 'rb') 15 | s = input.read() 16 | input.close() 17 | output = open(location+date.strftime("%Y%m%d")+'.csv', 'wb') 18 | output.write(s) 19 | output.close() 20 | # Parse: 21 | print('\nparsing') 22 | asset = 'XBTUSD' 23 | data = pd.read_csv(location+date.strftime("%Y%m%d")+'.csv', header=None, low_memory=False, usecols=[0, 1, 3], dtype={0: str, 1: str, 3: float}, skiprows=2) 24 | for n, j in enumerate(data[1]): 25 | if j == asset: 26 | data = pd.DataFrame(data.values[n:]) 27 | break 28 | for n, k in enumerate(data[1]): 29 | if k != asset: 30 | data = pd.DataFrame(data.values[:n]) 31 | break 32 | del data[1] 33 | data.to_csv(location+date.strftime("%Y%m%d")+'.csv') 34 | print('done') 35 | return 1 36 | else: 37 | print('have all data') 38 | return 0 39 | 40 | 41 | def download_abs(day): 42 | day = str(day) 43 | print(day) 44 | print("https://s3-eu-west-1.amazonaws.com/public.bitmex.com/data/quote/{}.csv.gz".format(day)) 45 | wget.download(url="https://s3-eu-west-1.amazonaws.com/public.bitmex.com/data/quote/{}.csv.gz".format(day), out='../') 46 | input = gzip.GzipFile("../"+day+".csv.gz", 'rb') 47 | s = input.read() 48 | input.close() 49 | output = open("../"+day+".csv","wb") 50 | output.write(s) 51 | output.close() 52 | print("\nparsing") 53 | asset = "XBTUSD" 54 | data = pd.read_csv("../"+day+'.csv', header=None, low_memory=False, usecols=[0, 1, 3], dtype={0: str, 1: str, 3: float}, skiprows=2) 55 | for n, j in enumerate(data[1]): 56 | if j == asset: 57 | data=pd.DataFrame(data.values[n:]) 58 | break 59 | for n, k in enumerate(data[1]): 60 | if k != asset: 61 | data=pd.DataFrame(data.values[:n]) 62 | break 63 | del data[1] 64 | data.to_csv("../"+day+".csv") 65 | print("done") 66 | 67 | if __name__ == "__main__": 68 | parser = argparse.ArgumentParser(description='Process some integers.') 69 | parser.add_argument('day', type=str, nargs=1) 70 | args = parser.parse_args() 71 | download_abs(args.day[0]) 72 | -------------------------------------------------------------------------------- /out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackWBoynton/python_trader/c38370254a2ec60db4b3bc0660ae1cf6856fcd95/out.jpg -------------------------------------------------------------------------------- /pyrenko.py: -------------------------------------------------------------------------------- 1 | ''' 2 | python3 3 | renko brick calculation python implementation 4 | Jack Boynton 2019 5 | ''' 6 | from tqdm import tqdm 7 | import numpy as np 8 | import pandas as pd 9 | from math import floor 10 | import datetime 11 | import sys 12 | import requests 13 | from data import new_trade 14 | from engines import BitmexTrader, BinanceTrader, RobinhoodTrader, AlpacaTrader 15 | import threading 16 | from calculate_pred import main as pred 17 | import statistics 18 | from b_trader import trader as b_trader 19 | """ 20 | sys.path.insert(1, '/home/jayce/rest_api/') 21 | from balance import update as update_ # not working 22 | """ 23 | 24 | class renko: 25 | def __init__(self, plot, j_backtest, fast, slow, signal_l, to_trade, strategy, ordtype): 26 | 27 | self.trade = BitmexTrader( 28 | trade=to_trade, leverage=10, tp=0.5, test=False, ord_type=ordtype) 29 | self.j_backtest = j_backtest 30 | self.fast = int(fast) 31 | self.slow = int(slow) 32 | self.signal_l = int(signal_l) 33 | self.source_prices = [] 34 | self.returns = [] 35 | self.trades_ = [] 36 | self.renko_prices = [] 37 | self.renko_directions = [] 38 | self.out = [] 39 | self.last_loaded = 0 40 | self.plot = plot # unused 41 | self.timestamps = [] 42 | self.macdaa = [] 43 | self.n = 20 # ema--rsi 44 | self.smaa = [] 45 | self.act_timestamps = [] 46 | self.end_backtest = datetime.datetime.now() 47 | self.strategy = strategy 48 | self.use_ml = False 49 | self.b_ = b_trader(0.1) # 0.1 BTC starting balance 50 | 51 | def set_brick_size(self, HLC_history=None, auto=True, brick_size=10): 52 | if auto: 53 | self.brick_size = self.__get_optimal_brick_size( 54 | HLC_history.iloc[:, [0, 1, 2]]) 55 | else: 56 | self.brick_size = brick_size 57 | return self.brick_size 58 | 59 | def __renko_rule(self, last_price, ind): 60 | # determines if should plot new bricks 61 | # returns number of new bricks to plot 62 | # print(last_price,ind) 63 | try: 64 | gap_div = int( 65 | float(last_price - self.renko_prices[-1]) / self.brick_size) 66 | except Exception: 67 | gap_div = 0 68 | is_new_brick = False 69 | start_brick = 0 70 | num_new_bars = 0 71 | 72 | if gap_div != 0: 73 | if (gap_div > 0 and (self.renko_directions[-1] > 0 or self.renko_directions[-1] == 0)) or (gap_div < 0 and (self.renko_directions[-1] < 0 or self.renko_directions[-1] == 0)): 74 | num_new_bars = gap_div 75 | is_new_brick = True 76 | start_brick = 0 77 | elif np.abs(gap_div) >= 2: 78 | num_new_bars = gap_div 79 | num_new_bars -= np.sign(gap_div) 80 | start_brick = 2 81 | self.renko_prices.append( 82 | self.renko_prices[-1] + 2 * self.brick_size * np.sign(gap_div)) 83 | #print(self.timestamps[ind]) 84 | self.act_timestamps.append(self.timestamps[ind+1]) 85 | self.renko_directions.append(np.sign(gap_div)) 86 | if is_new_brick: 87 | # add each brick 88 | for d in range(start_brick, np.abs(gap_div)): 89 | self.renko_prices.append( 90 | self.renko_prices[-1] + self.brick_size * np.sign(gap_div)) 91 | self.act_timestamps.append(self.timestamps[ind+1]) 92 | self.renko_directions.append(np.sign(gap_div)) 93 | return num_new_bars 94 | 95 | def build_history(self, prices, timestamps): 96 | # builds backtest bricks 97 | 98 | print(len(self.renko_prices)) 99 | self.orig_prices = prices 100 | print(prices[1].values[-1]) 101 | if len(prices) > 0: 102 | self.timestamps = prices[1].values 103 | self.source_prices = pd.DataFrame(prices[2].values) 104 | self.renko_prices.append(prices[2].values[-1]) 105 | self.act_timestamps.append(prices[1].values[-1]) 106 | self.renko_directions.append(0) 107 | if self.last_loaded == 0: 108 | for n, p in tqdm(enumerate(self.source_prices[1:].values), total=len(self.source_prices[1:].values), desc='build renko'): # takes long time 109 | # print(type(p),p) 110 | self.__renko_rule(p, n) # performs __renko_rule on each price tick 111 | self.last_loaded = len(self.renko_prices) 112 | #self.source_prices = [] 113 | else: 114 | for n, p in tqdm(enumerate(self.source_prices[1:].values), total=len(self.source_prices[1:].values), desc=f'build renko {self.last_loaded}:{self.last_loaded+len(self.source_prices[1:].values)}'): # takes long time 115 | # print(type(p),p) 116 | self.__renko_rule(p, n) 117 | self.last_loaded = len(self.renko_prices) 118 | 119 | # map(lambda x: self.__renko_rule(x[1], x[0]), enumerate(self.source_prices[1:].values)) 120 | return len(self.renko_prices) 121 | 122 | def do_next(self, last_price): 123 | # function that is used to plot live data 124 | # calls __renko_rule on each new live price tick 125 | last_price = float(last_price) 126 | if len(self.renko_prices) == 0: 127 | self.source_prices.append(last_price) 128 | self.renko_prices.append(last_price) 129 | self.renko_directions.append(0) 130 | return 1 131 | else: 132 | self.source_prices.append(last_price) 133 | return self.__renko_rule(last_price, 1) 134 | 135 | def get_renko_prices(self): 136 | return self.renko_prices 137 | 138 | def get_renko_directions(self): 139 | return self.renko_directions 140 | 141 | def plot_renko(self, col_up='g', col_down='r'): 142 | self.last_timestamp = datetime.datetime( 143 | year=2018, month=7, day=12, hour=7, minute=9, second=33) # random day in the past to make sure all data gets loaded as backtest 144 | self.ys = [] 145 | self.xs = [] 146 | self.U = [] # ema--rsi 147 | self.D = [] # ema--rsi 148 | self.lll = 0 149 | self.prices = [] 150 | self.next_brick = 0 151 | self.backtest = True 152 | self.backtest_bal_usd = 0.0028655 153 | self.init = self.backtest_bal_usd 154 | self.backtest_fee = 0.00075 # 0.075% 155 | self.backtest_slippage = 12 * 0.5 # ticks*tick_size=$slip 156 | self.leverage = 50 157 | self.w = 1 158 | self.l = 1 159 | self.runs = 0 160 | self.balances = [] 161 | self.ff = True 162 | self.long = False 163 | self.short = False 164 | self.open = 0 165 | self.profit = 0 166 | 167 | self.first = True 168 | 169 | for i in tqdm(range(1, len(self.renko_prices)),total=len(self.renko_prices)): # start backtest 170 | self.col = col_up if self.renko_directions[i] == 1 else col_down 171 | self.x = i 172 | self.y = self.renko_prices[i] - self.brick_size if self.renko_directions[i] == 1 else self.renko_prices[i] 173 | self.last = self.renko_prices[-1] 174 | self.aaa = self.last 175 | self.animate(i) 176 | 177 | self.last = self.renko_prices[-1] 178 | self.backtest = False 179 | self.bricks = 0 180 | 181 | #self.source_prices = [] 182 | # self.ys = [0] 183 | self.l = 1 184 | self.w = 1 185 | #print(self.returns) 186 | #self.returns = map(lambda x: x*100, self.returns) 187 | sr = pd.DataFrame(self.returns[2:]).cumsum() 188 | sra = (sr - sr.shift(1))/sr.shift(1) 189 | srb = sra.mean()/sra.std() * np.sqrt(365) # calculate sharpe ratio for 1 year trading every day 190 | #self.trade.end_backtest(self.pricea) 191 | self.b_.end(self.pricea,self.act_timestamps[-1]) 192 | #print('net backtest profit: BTC ' + str(self.backtest_bal_usd - self.init) + ' :: ' + str(round(((self.backtest_bal_usd-self.init)/self.init)*100, 3)) + ' percent') 193 | #print('net backtest profit: BTC ' + str(self.backtest_bal_usd - self.init), 'max drawdown: ' + str(round(min(self.trades_), 8)) + ' BTC', 'max trade: ' + str(round(max(self.trades_), 8)) + ' BTC', 'average: ' + str(round(statistics.mean(self.trades_), 8)) + ' BTC', 'SR: ' + str(round(srb[0], 5))) 194 | if not self.j_backtest: 195 | self.trade.end_backtest(self.pricea) 196 | while True: 197 | # starts live trading 198 | self.check_for_new() 199 | else: 200 | return self.out 201 | 202 | def check_for_new(self): 203 | # connects to hosted BITMEX delta server running in nodejs on port 4444 204 | data = requests.get( 205 | 'http://132.198.249.205:4444/quote?symbol=XBTUSD').json() 206 | 207 | for key in data: 208 | #print(str(key['timestamp'])) 209 | if datetime.datetime.strptime(key['timestamp'].replace('T', ''), '%Y-%m-%d%H:%M:%S.%fZ') > self.last_timestamp: 210 | self.add_to_plot(float(key['bidPrice']), self.do_next( 211 | np.array(float(key['bidPrice']), dtype=float))) 212 | update_() 213 | act_price = 0 214 | print(str(float(key['bidPrice'])) + ' brick: ' + str(self.last) + ' sma: ' + str(self.smaa[-1]) + ' macd: ' + str( 215 | self.macdaa[-1]) + ' len: ' + str(len(self.ys)) + ' bricks: ' + str(self.bricks) + ' y: ' + str(self.renko_prices[-1]) + ' act: ' + str(act_price), end="\r") 216 | self.last_timestamp = datetime.datetime.strptime( 217 | key['timestamp'].replace('T', ''), '%Y-%m-%d%H:%M:%S.%fZ') 218 | #print(self.last_timestamp) 219 | #self.bid = float(key['bidPrice']) 220 | 221 | def add_to_plot(self, price, bricks): 222 | self.aaa = self.last 223 | self.bricks = bricks 224 | self.prices.append(self.last) 225 | if bricks >= 1: 226 | bricks += 1 227 | for i in range(1, bricks): 228 | self.x = self.x + i 229 | #print() 230 | self.y = self.renko_prices[(-bricks + i) - 1] - self.brick_size if self.renko_directions[( 231 | -bricks + i) - 1] == 1 else self.renko_prices[(-bricks + i) - 1] 232 | self.last = self.renko_prices[(-bricks + i) - 1] 233 | self.aaa = self.last 234 | self.animate(1) 235 | self.last = self.renko_prices[-1] 236 | 237 | def animate(self, i): 238 | self.lll += 1 239 | self.ys.append(self.y) # all bricks for indicator calculation 240 | self.xs.append(self.x) # num bars 241 | if self.next_brick == 1: 242 | self.col = 'b' 243 | elif self.next_brick == 2: 244 | self.col = 'y' 245 | self.balances.append(self.profit) 246 | self.calc_indicator(i) # calculates given indicator 247 | 248 | def ma_(self): 249 | # vanilla moving average 250 | return pd.DataFrame(self.ys).rolling(window=self.n).mean() 251 | 252 | def ma(self): 253 | # calculates simple moving averages on brick prices 254 | if (1==1): 255 | fast_ma = pd.DataFrame(self.ys).rolling(window=self.fast).mean() 256 | slow_ma = pd.DataFrame(self.ys).rolling(window=self.slow).mean() 257 | return fast_ma.values, slow_ma.values 258 | else: 259 | # doesnt work... calculates the mas for the entire source prices df not changing with time 260 | fast_ma = pd.DataFrame(self.source_prices).rolling(window=self.fast).mean() 261 | slow_ma = pd.DataFrame(self.source_prices).rolling(window=self.slow).mean() 262 | return fast_ma.values, slow_ma.values 263 | 264 | def macd(self): 265 | # calculated moving average convergence divergence on brick prices 266 | fast, slow = self.ma() 267 | macda = [] 268 | for n, i in enumerate(fast): 269 | macda.append(i - slow[n]) 270 | self.macdaa = macda 271 | return macda 272 | 273 | def sma(self): 274 | # simple moving average to compare against macd 275 | self.smaa = (pd.DataFrame(self.macd()).rolling( 276 | window=self.signal_l).mean()).values 277 | return (pd.DataFrame(self.macd()).rolling(window=self.signal_l).mean()).values 278 | 279 | def ema_(self, t, n): 280 | # exponential moving average 281 | return pd.DataFrame(t).ewm(span=n, adjust=False).mean().values 282 | 283 | def rsi(self): 284 | if len(self.ys) > 10: 285 | if self.ys[-1] > self.ys[-2]: 286 | self.U.append(self.ys[-1] - self.ys[-2]) 287 | self.D.append(0) 288 | elif self.ys[-2] > self.ys[-1]: 289 | self.U.append(0) 290 | self.D.append(self.ys[-2] - self.ys[-1]) 291 | else: 292 | self.D.append(0) 293 | self.U.append(0) 294 | try: 295 | RS = (self.ema_(self.U, self.n)/self.ema_(self.D, self.n)) 296 | except: 297 | RS = 0 298 | if RS.any() != 0: 299 | return list(map(lambda x: 100-(100/(1+x)),RS)) 300 | 301 | def wma(self,p): 302 | # weighted moving average 303 | ret = [] 304 | times = [] 305 | for n,num in enumerate(self.ys): 306 | if n > p: 307 | use = self.ys[n-p:n] 308 | times.append(self.act_timestamps[n]) 309 | weights = [x/sum(list(range(1,p+1))) for x in range(1,len(use)+1)] 310 | ret.append(sum([use[x]*weights[x] for x in range(len(weights))])) 311 | return ret,times 312 | 313 | 314 | 315 | 316 | def cross(self, a, b): 317 | # determines if signal price and macd cross or had crossed one brick ago 318 | try: 319 | if (a[-2] > b[-2] and b[-1] > a[-1]) or (b[-2] > a[-2] and a[-1] > b[-1]) or (a[-2] > b[-2] and b[-1] == a[-1]) or (b[-2] > a[-2] and b[-1] == a[-1]): 320 | return True 321 | return False 322 | except Exception: 323 | return False 324 | 325 | def close_short(self, price): 326 | # calculates profit on close of short trade 327 | net = round(((1 / self.pricea - 1 / (self.open)) * floor(self.risk*self.open)*self.leverage), 8) 328 | self.profit += net 329 | fee = round((self.risk*self.leverage * self.backtest_fee), 8) 330 | fee += round((self.risk*self.leverage * self.backtest_fee), 8) 331 | self.profit -= fee 332 | self.backtest_bal_usd += round((net - fee), 8) 333 | ret = round((net - fee), 8)/self.risk 334 | self.returns.append(ret) 335 | try: 336 | per = ((self.w + self.l) - self.l) / (self.w + self.l) 337 | except Exception: 338 | per = 0 339 | self.trades_.append(round((net-fee), 8)) 340 | print('trade: BTC ' + str(round((net - fee), 8)), 'net BTC: ' + str(round(self.profit, 8)), 341 | 'closed at: ' + str(self.pricea), 'profitable?: ' + str('yes') if price < self.open else str('no'), 'balance: BTC ' + str(self.backtest_bal_usd), 'percentage profitable ' + str(round(per * 100, 3)) + '%', 'w:' + str(self.w), 'l:' + str(self.l)) 342 | if price < self.open: 343 | self.w += 1 344 | else: 345 | self.l += 1 346 | 347 | def close_long(self, price): 348 | # calculates profit on close of long trade 349 | if price > self.open: 350 | self.w += 1 351 | else: 352 | self.l += 1 353 | net = round(((1 / self.open - 1 / (self.pricea)) * floor(self.risk*self.open)*self.leverage), 8) 354 | self.profit += net 355 | fee = round((self.risk*self.leverage * self.backtest_fee), 8) 356 | fee += round((self.risk*self.leverage * self.backtest_fee), 8) 357 | self.profit -= fee 358 | # print(type(net-fee)) 359 | self.backtest_bal_usd += round((net - fee), 8) 360 | ret = round((net - fee), 8)/self.risk 361 | self.returns.append(ret) 362 | try: 363 | per = ((self.w + self.l) - self.l) / (self.w + self.l) 364 | except Exception: 365 | per = 0 366 | self.trades_.append(round((net - fee), 8)) 367 | print('trade: BTC ' + str(round((net - fee), 8)), 'net BTC: ' + str(round(self.profit, 8)), 368 | 'closed at: ' + str(self.pricea), 'profitable?: ' + str('no') if price < self.open else str('yes'), 'balance $' + str(self.backtest_bal_usd), 'percentage profitable: ' + str(round(per * 100, 3)) + '%', 'w:' + str(self.w), 'l:' + str(self.l)) 369 | 370 | def calc_indicator(self, ind): 371 | # calculates indicator 372 | #print(f"using {self.strategy}") 373 | if self.strategy == "macd": # can add more indicators by expanding if condition: 374 | self.pricea = self.y + self.brick_size # calculates indicator on each new brick 375 | if self.cross(self.macd(), self.sma()) and self.macd()[-1] > self.sma()[-1] and not self.long: 376 | self.long = True 377 | self.short = False 378 | if self.runs > 0: 379 | #print(f"closed trade at {self.trade.close_backtest_short(self.pricea):.8f} of profit") 380 | self.b_.close(self.pricea) 381 | if self.long: 382 | side = 0 383 | elif self.short: 384 | side = 1 385 | if len(self.renko_prices) > 10 and len(self.macd()) > 10: 386 | # write trades to file 387 | new_trade(past_bricks=self.ys_open, price_open=self.open, price_close=self.pricea, side=side, macd_open=self.macd_open, macd_close=self.macd()[-1], sma_open=self.sma_open, sma_close=self.sma()[-1], time_open=self.open_time, time_close=self.act_timestamps[ind]) 388 | if self.end_backtest <= self.last_timestamp and not self.j_backtest and len(self.ys) > 35 and self.trade.backtest_over == True: 389 | predi = pred(self.ys[-10:], self.macd()[-10:], self.sma()[-10:], self.pricea) 390 | threading.Thread(target=self.trade.buy_long, args=("BITMEX", "XBT-USD", self.pricea, self.pricea-self.brick_size, predi, )).start() 391 | if self.ff: # if done backtest: 392 | #self.trade.end_backtest(self.pricea) 393 | self.b_.end(self.pricea) 394 | print('net backtest profit: BTC ' + str(self.backtest_bal_usd) + 395 | ' with $' + str(self.backtest_slippage) + ' of slippage per trade', 'max drawdown: ' + str(min(self.trades_)), 'max trade: ' + str(max(self.trades_)), 'average: ' + str(statistics.mean(self.trades_))) 396 | print('proceeding to live...') 397 | self.backtest_bal_usd = self.init 398 | self.profit = 0 399 | self.ff = False 400 | act_price = float(requests.get("https://www.bitmex.com/api/v1/orderBook/L2?symbol=xbt&depth=1").json()[1]['price']) 401 | print('BUY at: ' + str(self.pricea), ' act: ' + str(act_price), 402 | str(datetime.datetime.now()), 'pred: ' + str(pred(self.ys[-10:], self.macd()[-10:], self.sma()[-10:], self.pricea))) 403 | else: 404 | if ind != 1: 405 | sss = self.act_timestamps[ind] 406 | else: 407 | sss = 'undef' 408 | if len(self.macd()) > 10 and len(self.sma()) > 10 and len(self.ys) > 10: 409 | if self.use_ml: 410 | predi = pred(self.ys[-10:], self.macd()[-10:], self.sma()[-10:], self.pricea) 411 | else: 412 | predi = 1 413 | self.risk = self.backtest_bal_usd * predi 414 | #self.trade.backtest_buy(self.pricea) 415 | self.b_.buy(self.pricea) 416 | #print('backtest BUY at: ' + str(self.pricea), 'time: ' + str(sss), 'amount: ' + str(self.risk), 417 | # 'fee: $' + str(round(((floor(self.risk*self.pricea)*self.leverage / self.pricea) * self.backtest_fee * self.pricea), 3)), 'pred: ' + str(predi)) 418 | self.out.append([1,sss,self.pricea]) 419 | self.open = self.pricea 420 | self.open_time = self.act_timestamps[ind] 421 | self.macd_open = self.macd()[-10:] 422 | self.ys_open = self.ys[-10:] 423 | self.sma_open = self.sma()[-10:] 424 | self.next_brick = 1 425 | self.runs += 1 426 | elif self.cross(self.macd(), self.sma()) and self.sma()[-1] > self.macd()[-1] and not self.short: 427 | self.short = True 428 | self.long = False 429 | if self.runs > 0: 430 | #print(f"closed trade at {self.trade.close_backtest_long(self.pricea):.8f} of profit") 431 | self.b_.close(self.pricea) 432 | if self.long: 433 | side = 0 434 | elif self.short: 435 | side = 1 436 | if len(self.renko_prices) > 10 and len(self.macd()) > 10 and len(self.ys) > 10: 437 | # write trades to file 438 | new_trade(past_bricks=self.ys_open, price_open=self.open, price_close=self.pricea, side=side, macd_open=self.macd_open, macd_close=self.macd()[-1], sma_open=self.sma_open, sma_close=self.sma()[-1], time_open=self.open_time, time_close=self.act_timestamps[ind]) 439 | 440 | if self.end_backtest <= self.last_timestamp and not self.j_backtest and len(self.ys) > 35 and self.trade.backtest_over == True: 441 | predi = pred(self.ys[-10:], self.macd()[-10:], self.sma()[-10:], self.pricea) 442 | threading.Thread(target=self.trade.sell_short, args=("BITMEX", "XBT-USD", self.pricea, self.pricea+self.brick_size, predi, )).start() 443 | if self.ff: 444 | #self.trade.end_backtest(self.pricea) 445 | self.b_.end(self.pricea) 446 | print('net backtest profit: BTC ' + str(self.backtest_bal_usd) + 447 | ' with $' + str(self.backtest_slippage) + ' of slippage per trade', 'max drawdown: ' + str(min(self.trades_)), 'max trade: ' + str(max(self.trades_)), 'average: ' + str(statistics.mean(self.trades_))) 448 | print('proceeding to live...') 449 | self.backtest_bal_usd = self.init 450 | self.profit = 0 451 | self.ff = False 452 | act_price = float(requests.get("https://www.bitmex.com/api/v1/orderBook/L2?symbol=xbt&depth=1").json()[1]['price']) 453 | print('SELL at: ' + str(self.pricea), 'act: ' + str(act_price), 454 | str(datetime.datetime.now()), 'pred: ' + str(pred(self.ys[-10:], self.macd()[-10:], self.sma()[-10:], self.pricea))) 455 | 456 | else: 457 | if ind != 1: 458 | sss = self.act_timestamps[ind] 459 | else: 460 | sss = 'undef' 461 | if len(self.macd()) > 10 and len(self.sma()) > 10 and len(self.ys) > 10: 462 | if self.use_ml: 463 | predi = pred(self.ys[-10:], self.macd()[-10:], self.sma()[-10:], self.pricea) 464 | else: 465 | predi = 1 466 | self.risk = self.backtest_bal_usd * predi 467 | #self.trade.backtest_sell(self.pricea) 468 | self.b_.sell(self.pricea) 469 | #print('backtest SELL at: ' + str(self.pricea), 'time: ' + str(sss), 'amount: ' + str(self.risk), 470 | #'fee: $' + str(round(((floor(self.risk*self.pricea)*self.leverage / self.pricea) * self.backtest_fee * self.pricea), 3)), 'pred: ' + str(predi)) 471 | self.out.append([2,sss,self.pricea]) 472 | self.open = self.pricea 473 | self.open_time = self.act_timestamps[ind] 474 | self.macd_open = self.macd()[-10:] 475 | self.ys_open = self.ys[-10:] 476 | self.sma_open = self.sma()[-10:] 477 | self.next_brick = 2 478 | self.runs += 1 479 | else: 480 | self.next_brick = 0 481 | elif self.strategy == "rsi": 482 | #print(ind) 483 | if self.rsi() is not None: 484 | self.pricea = self.y + self.brick_size 485 | rsi = [] 486 | for i in self.rsi(): 487 | rsi.append(i[0]) 488 | 489 | if rsi[-1] > 10 and rsi[-2] < 10 and not self.long: 490 | if self.long or self.short: 491 | self.b_.close(self.pricea, self.act_timestamps[ind]) 492 | self.b_.buy(self.pricea) 493 | #print(f"BUY at {self.pricea} @ {self.act_timestamps[ind]}") 494 | self.long = True 495 | self.short = False 496 | 497 | elif rsi[-1] < 70 and rsi[-2] > 70 and not self.short: 498 | if self.long or self.short: 499 | self.b_.close(self.pricea, self.act_timestamps[ind]) 500 | self.b_.sell(self.pricea) 501 | #print(f"SELL at {self.pricea} @ {self.act_timestamps[ind]}") 502 | self.short = True 503 | self.long = False 504 | 505 | 506 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.7.1 2 | astor==0.7.1 3 | gast==0.2.2 4 | grpcio==1.20.1 5 | h5py==2.9.0 6 | Keras-Applications==1.0.7 7 | Keras-Preprocessing==1.0.9 8 | Markdown==3.1 9 | mock==2.0.0 10 | numpy==1.16.3 11 | opencv-python==4.1.0.25 12 | pbr==5.2.0 13 | Pillow==6.0.0 14 | protobuf==3.7.1 15 | scipy==1.2.1 16 | six==1.12.0 17 | tensorboard==1.13.1 18 | tensorflow==1.13.1 19 | tensorflow-estimator==1.13.0 20 | termcolor==1.1.0 21 | Werkzeug==0.15.2 22 | -------------------------------------------------------------------------------- /scaler.save: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackWBoynton/python_trader/c38370254a2ec60db4b3bc0660ae1cf6856fcd95/scaler.save --------------------------------------------------------------------------------