├── Algorithm ├── BinanceAPI.py ├── Logic.py ├── README.md ├── Trading_Algorithm.py ├── config.py ├── get_ticker_data.py └── technical_indicators.py ├── Auxiliary Programs ├── README.md ├── Testing.py └── corr_matrix ├── LICENSE ├── README.md └── SQL Code ├── Create_Ticker_Data_Table ├── README.md ├── create trade_log table └── view_database_connections /Algorithm/BinanceAPI.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | # @yasinkuyu 3 | 4 | import time 5 | import hashlib 6 | import requests 7 | import config 8 | import hmac 9 | 10 | try: 11 | from urllib import urlencode 12 | except ImportError: 13 | from urllib.parse import urlencode 14 | 15 | 16 | class BinanceAPI: 17 | BASE_URL_v1 = "https://www.binance.com/api/v1" 18 | BASE_URL_v3 = "https://api.binance.com/api/v3" 19 | PUBLIC_URL = "https://www.binance.com/exchange/public/product" 20 | 21 | def __init__(self, key, secret): 22 | self.key = key 23 | self.secret = secret 24 | 25 | 26 | def get_ticker(self, market): 27 | path = "%s/ticker/24hr" % self.BASE_URL_v1 28 | params = {"symbol": market} 29 | return self._get_no_sign(path, params) 30 | 31 | def get_ticker_info(self): 32 | path = "%s/exchangeInfo" % self.BASE_URL_v1 33 | return self._get_no_sign(path) 34 | 35 | def get_orderbooks(self, market, limit=50): 36 | path = "%s/depth" % self.BASE_URL_v1 37 | params = {"symbol": market, "limit": limit} 38 | return self._get_no_sign(path, params) 39 | 40 | 41 | def get_account(self): 42 | path = "%s/account" % self.BASE_URL_v3 43 | return self._get(path, {}) 44 | 45 | 46 | def get_open_orders(self, market, limit = 100): 47 | path = "%s/openOrders" % self.BASE_URL_v3 48 | params = {} 49 | return self._get(path, params) 50 | 51 | 52 | def buy_limit(self, market, quantity, rate): 53 | path = "%s/order" % self.BASE_URL_v3 54 | params = self._order(market, quantity, "BUY", rate) 55 | return self._post(path, params) 56 | 57 | def test_buy_limit(self, market, quantity, rate): 58 | path = "%s/order/test" % self.BASE_URL_v3 59 | params = self._order(market, quantity, "BUY", rate) 60 | return self._post(path, params) 61 | 62 | def sell_limit(self, market, quantity, rate): 63 | path = "%s/order" % self.BASE_URL_v3 64 | params = self._order(market, quantity, "SELL", rate) 65 | return self._post(path, params) 66 | 67 | def test_sell_limit(self, market, quantity, rate): 68 | path = "%s/order/test" % self.BASE_URL_v3 69 | params = self._order(market, quantity, "SELL", rate) 70 | return self._post(path, params) 71 | 72 | def buy_market(self, market, quantity): 73 | path = "%s/order" % self.BASE_URL_v3 74 | params = {"symbol": market, "side": "BUY", \ 75 | "type": "MARKET", "quantity": quantity} 76 | return self._post(path, params) 77 | 78 | 79 | def sell_market(self, market, quantity): 80 | path = "%s/order" % self.BASE_URL_v3 81 | params = {"symbol": market, "side": "SELL", \ 82 | "type": "MARKET", "quantity": quantity} 83 | return self._post(path, params) 84 | 85 | 86 | def query_order(self, market, orderId): 87 | path = "%s/order" % self.BASE_URL_v3 88 | params = {"symbol": market, "orderId": orderId} 89 | return self._get(path, params) 90 | 91 | 92 | def cancel(self, market, order_id): 93 | path = "%s/order" % self.BASE_URL_v3 94 | params = {"symbol": market, "orderId": order_id} 95 | return self._delete(path, params) 96 | 97 | 98 | def _get_no_sign(self, path, params={}): 99 | query = urlencode(params) 100 | url = "%s?%s" % (path, query) 101 | return requests.get(url, timeout=30, verify=True).json() 102 | 103 | def _sign(self, params={}): 104 | data = params.copy() 105 | 106 | ts = str(int(1000 * time.time())) 107 | data.update({"timestamp": ts}) 108 | 109 | h = urlencode(data) 110 | b = bytearray() 111 | b.extend(self.secret.encode()) 112 | signature = hmac.new(b, msg=h.encode('utf-8'), digestmod=hashlib.sha256).hexdigest() 113 | data.update({"signature": signature}) 114 | return data 115 | 116 | def _get(self, path, params={}): 117 | q = self._sign(params) 118 | q_t = q.get('timestamp') 119 | q_s = q.get('signature') 120 | query = 'timestamp=%s&signature=%s' % (q_t, q_s) 121 | url = "%s?%s" % (path, query) 122 | header = {"X-MBX-APIKEY": self.key} 123 | return requests.get(url, headers=header, \ 124 | timeout=30, verify=True).json() 125 | 126 | def _post(self, path, params={}): 127 | params.update({"recvWindow": config.recv_window}) 128 | query = urlencode(self._sign(params)) 129 | url = "%s?%s" % (path, query) 130 | header = {"X-MBX-APIKEY": self.key} 131 | return requests.post(url, headers=header, \ 132 | timeout=30, verify=True).json() 133 | 134 | def _order(self, market, quantity, side, rate=None): 135 | params = {} 136 | 137 | if rate is not None: 138 | params["type"] = "LIMIT" 139 | params["price"] = self._format(rate) 140 | params["timeInForce"] = "GTC" 141 | else: 142 | params["type"] = "MARKET" 143 | 144 | params["symbol"] = market 145 | params["side"] = side 146 | params["quantity"] = '%.8f' % quantity 147 | 148 | return params 149 | 150 | def _format(self, price): 151 | return "{:.8f}".format(price) 152 | 153 | def _delete(self, path, params={}): 154 | params.update({"recvWindow": config.recv_window}) 155 | query = urlencode(self._sign(params)) 156 | url = "%s?%s" % (path, query) 157 | header = {"X-MBX-APIKEY": self.key} 158 | return requests.delete(url, headers=header, \ 159 | timeout=30, verify=True).json() 160 | 161 | def balance(self, asset): 162 | self.client = BinanceAPI(config.api_key,config.api_secret) 163 | balances = self.client.get_account() 164 | balances['balances'] = {item['asset']: item for item in balances['balances']} 165 | return balances['balances'][asset]['free'] 166 | -------------------------------------------------------------------------------- /Algorithm/Logic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pandas as pd 4 | from technical_indicators import TechInd 5 | from sqlalchemy import create_engine 6 | import config 7 | 8 | conn = config.conn 9 | cur = config.cur 10 | 11 | tickers = config.ticker 12 | 13 | for x in tickers: 14 | ticker = x 15 | cur.execute("""select closetime,lastprice,highprice,lowprice from binance.ticker_data where symbol='%s' order by closetime desc limit 720""" % ticker.upper()) 16 | df = pd.DataFrame(cur.fetchall()).rename(index=str, columns={0: "closetime", 1: "lastprice", 2: "highprice", 3: "lowprice"}).sort_values(by=['closetime']) 17 | 18 | minutes = 15 19 | 20 | ##### MACD Indicators ##### 21 | 22 | ema_short = df['lastprice'].ewm(span=(12*minutes),min_periods=0,adjust=True,ignore_na=False).mean() 23 | ema_long = df['lastprice'].ewm(span=24*minutes,min_periods=0,adjust=True,ignore_na=False).mean() 24 | sma7 = df['lastprice'].ewm(span=6*minutes,min_periods=0,adjust=True,ignore_na=False).mean() 25 | MACD = ema_short - ema_long 26 | MACDsignal = MACD.ewm(span=6*minutes,min_periods=0,adjust=True,ignore_na=False).mean() 27 | MACDcross = MACD-MACDsignal 28 | WMS = TechInd.WMS(df,8*minutes) 29 | 30 | #MACDsignal 31 | ###Bands 32 | 33 | MACDsignal_mean = MACDsignal.rolling(window=9*minutes,center=False).mean() 34 | MACDsignal_std = MACDsignal.rolling(window=9*minutes,center=False).std() 35 | 36 | MACDsignal_ub2 = MACDsignal_mean + (MACDsignal_std*2) 37 | MACDsignal_lb2 = MACDsignal_mean - (MACDsignal_std*2) 38 | MACDsignal_ub1 = MACDsignal_mean + (MACDsignal_std) 39 | MACDsignal_lb1 = MACDsignal_mean - (MACDsignal_std) 40 | MACDsignal_ub_5 = MACDsignal_mean + (MACDsignal_std*.5) 41 | MACDsignal_lb_5 = MACDsignal_mean - (MACDsignal_std*.5) 42 | ###DataFrame 43 | MACDsignal_df = MACDsignal.to_frame() 44 | MACDsignal_ub_df = MACDsignal_ub1.to_frame() 45 | MACDsignal_lb_df = MACDsignal_lb1.to_frame() 46 | ###DataFrame with Logic 47 | MACDsignal_final = MACDsignal_df.join(MACDsignal_ub_df, how='inner',rsuffix='_ub').join(MACDsignal_lb_df, how='inner',rsuffix='_lb').dropna() 48 | MACDsignal_final.loc[(MACDsignal_final['lastprice'] >= MACDsignal_final['lastprice_ub']),'test'] = 'Upper' 49 | MACDsignal_final.loc[(MACDsignal_final['lastprice'] <= MACDsignal_final['lastprice_lb']),'test'] = 'Lower' 50 | MACDsignal_final.loc[(MACDsignal_final['lastprice'] < MACDsignal_final['lastprice_ub']) & (MACDsignal_final['lastprice'] > MACDsignal_final['lastprice_lb']),'test'] = 'Mid' 51 | 52 | #MACDcross 53 | ###Bands 54 | 55 | MACDcross_mean = MACDcross.rolling(window=9*minutes,center=False).mean() 56 | MACDcross_std = MACDcross.rolling(window=9*minutes,center=False).std() 57 | MACDcross_ub2 = MACDcross_mean + (MACDcross_std*2) 58 | MACDcross_lb2 = MACDcross_mean - (MACDcross_std*2) 59 | MACDcross_ub1 = MACDcross_mean + (MACDcross_std) 60 | MACDcross_lb1 = MACDcross_mean - (MACDcross_std) 61 | MACDcross_ub_5 = MACDcross_mean + (MACDcross_std*.5) 62 | MACDcross_lb_5 = MACDcross_mean - (MACDcross_std*.5) 63 | ###DataFrame 64 | MACDcross_df = MACDcross.to_frame() 65 | MACDcross_ub_df = MACDcross_ub1.to_frame() 66 | MACDcross_lb_df = MACDcross_lb1.to_frame() 67 | ###DataFrame with Logic 68 | MACDcross_final = MACDcross_df.join(MACDcross_ub_df, how='inner',rsuffix='_ub').join(MACDcross_lb_df, how='inner',rsuffix='_lb').dropna() 69 | MACDcross_final.loc[(MACDcross_final['lastprice'] >= MACDcross_final['lastprice_ub']),'test'] = 'Upper' 70 | MACDcross_final.loc[(MACDcross_final['lastprice'] <= MACDcross_final['lastprice_lb']),'test'] = 'Lower' 71 | MACDcross_final.loc[(MACDcross_final['lastprice'] < MACDcross_final['lastprice_ub']) & (MACDcross_final['lastprice'] > MACDcross_final['lastprice_lb']),'test'] = 'Mid' 72 | 73 | #SMA 7 74 | ###Bands 75 | 76 | sma7_mean = sma7.rolling(window=9*minutes,center=False).mean() 77 | sma7_std = sma7.rolling(window=9*minutes,center=False).std() 78 | sma7_ub2 = sma7_mean + (sma7_std*2) 79 | sma7_lb2 = sma7_mean - (sma7_std*2) 80 | sma7_ub1_5 = sma7_mean + (sma7_std*1.5) 81 | sma7_lb1_5 = sma7_mean - (sma7_std*1.5) 82 | sma7_ub1_25 = sma7_mean + (sma7_std*1.25) 83 | sma7_lb1_25 = sma7_mean - (sma7_std*1.25) 84 | sma7_ub1 = sma7_mean + (sma7_std) 85 | sma7_lb1 = sma7_mean - (sma7_std) 86 | sma7_ub_5 = sma7_mean + (sma7_std*.5) 87 | sma7_lb_5 = sma7_mean - (sma7_std*.5) 88 | ###DataFrame 89 | sma7_df = sma7.to_frame() 90 | sma7_ub_df = sma7_ub1_5.to_frame() 91 | sma7_lb_df = sma7_lb1_5.to_frame() 92 | ###DataFrame with Logic 93 | df1 = pd.to_numeric(df['lastprice'],downcast='float').to_frame() 94 | sma7_final = df1.join(sma7_ub_df, how='inner',rsuffix='_ub').join(sma7_lb_df, how='inner',rsuffix='_lb').dropna() 95 | sma7_final.loc[(sma7_final['lastprice'] >= sma7_final['lastprice_ub']),'test'] = 'Upper' 96 | sma7_final.loc[(sma7_final['lastprice'] <= sma7_final['lastprice_lb']),'test'] = 'Lower' 97 | sma7_final.loc[(sma7_final['lastprice'] < sma7_final['lastprice_ub']) & (sma7_final['lastprice'] > sma7_final['lastprice_lb']),'test'] = 'Mid' 98 | 99 | #RSI 100 | RSI = TechInd.RSI(df1,(8*minutes)) 101 | 102 | #Final DataFrame 103 | logic = sma7_final.join(MACDcross_final['test'], how='inner',rsuffix='_MACDcross').join(MACDsignal_final['test'], how='inner',rsuffix='_MACDsignal').join(RSI,how='inner',rsuffix='_rsi').join(WMS, how='inner').dropna() 104 | 105 | ### MACD Signal 'Upper' 106 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Upper'),'orderType'] = 'uuu_Sell' 107 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 108 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 109 | 110 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 111 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 112 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 113 | 114 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 115 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 116 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 117 | 118 | ### MACD Signal 'Mid' 119 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 120 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 121 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 122 | 123 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 124 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 125 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 126 | 127 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 128 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 129 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 130 | 131 | ### MACD Signal 'Lower' 132 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 133 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 134 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 135 | 136 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 137 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 138 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 139 | 140 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 141 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 142 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Lower'),'orderType'] = 'lll_Buy' 143 | 144 | # Write logic to table 145 | engine = create_engine('$DB_CONNECTION') 146 | logic.to_sql(ticker, engine, schema='binance', if_exists='replace') -------------------------------------------------------------------------------- /Algorithm/README.md: -------------------------------------------------------------------------------- 1 | ## Design and Flow 2 | 3 | 4 | --- 5 | 6 | ## Conceptual Framework 7 | 8 | The algorithm runs every minute and uses 'Logic.py' to determine whether to Buy, Sell, or Hold. 9 | 10 | The algorithm is designed to look for meaningful deviations from the average price. It does this by using three technical indicators; MACD Signal, MACD Cross, and a simple moving average. For each indicator, a moving average and standard deviation is calculated and used to identify "meaningful deviations" from the average. 11 | 12 | If 'Buy' or 'Sell' signal has been identified, an order size is calculated using your account balance, Relative Strength Index (RSI), Williams % R (WMS), and a weighted-average of regression line slopes. Refer to [technical_indicators.py](https://github.com/Jacyle/binance-technical-algorithm/blob/master/Algorithm/technical_indicators.py) for the formulas. Once an order size is calculated and it is of sufficient size as to adhere to the Binance minimum/maximum order size rules, the order is placed, otherwise it is rejected by the algorithm and is recorded in the trade_log. It's designed this way to record and monitor the behavior of the algorithm, for example, when it decides to Buy/Sell/Hold and the order sizes being calculated at a given time. 13 | 14 | --- 15 | -------------------------------------------------------------------------------- /Algorithm/Trading_Algorithm.py: -------------------------------------------------------------------------------- 1 | 2 | from BinanceAPI import BinanceAPI 3 | import config 4 | import pandas as pd 5 | from technical_indicators import TechInd 6 | import datetime 7 | 8 | try: 9 | conn = config.conn 10 | cur = config.cur 11 | except: 12 | print ("I am unable to connect to the database") 13 | 14 | c = BinanceAPI(config.api_key,config.api_secret) 15 | 16 | time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 17 | 18 | tickers = config.ticker 19 | 20 | for x in tickers: 21 | ticker = x 22 | 23 | # CANCEL OPEN ORDERS 24 | open_order = c.get_open_orders('%s' % ticker.upper()) 25 | 26 | if type(open_order) is dict: 27 | order_msg = open_order['msg'] 28 | cur.execute("INSERT INTO binance.trade_log VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", (time,order_msg,'','','',0,0,0,0,0,0,0,0)) 29 | conn.commit() 30 | BinanceAPI.email(order_msg) 31 | exit() 32 | elif open_order == []: 33 | prev_order_status = "No open orders" 34 | else: 35 | if ticker.upper() == open_order[0]['symbol']: 36 | open_order_id = open_order[0]['orderId'] 37 | cancel_order = c.cancel('%s' % ticker.upper(),open_order_id) 38 | try: 39 | cancel_order['code'] 40 | order_msg = cancel_order 41 | BinanceAPI.email(order_msg) 42 | prev_order_status = 'Error cancelling order' 43 | except KeyError: 44 | prev_order_status = "Order " + str(cancel_order['orderId']) + " cancelled" 45 | else: 46 | prev_order_status = "No open orders" 47 | 48 | # FETCH TRADING LOGIC FROM DATABASE 49 | order_logic = pd.read_sql_query("select * from binance.%s order by index limit 1" % ticker,conn) 50 | 51 | order_ticker = '%s' % ticker.upper() 52 | order_type = order_logic['orderType'] 53 | order_type = order_type[0] 54 | order_price = float(order_logic['lastprice']) 55 | # GET CURRENCY VALUE 56 | if ticker[3:] == 'btc': 57 | pair_quote = c.get_ticker('BTCUSDT') 58 | elif ticker[3:] == 'eth': 59 | pair_quote = c.get_ticker('ETHUSDT') 60 | elif ticker[3:] == 'bnb': 61 | pair_quote = c.get_ticker('BNBUSDT') 62 | else: 63 | pair_quote= {'lastPrice' : str(1.0)} 64 | 65 | # GET CURRENCY PAIR VALUE AND ORDER MINIMUMS 66 | ticker_info = c.get_ticker_info()['symbols'] 67 | ticker_info_list = {item['symbol']: item for item in ticker_info} 68 | ticker_min_qty = float(ticker_info_list['%s' % ticker.upper()]['filters'][1]['minQty']) 69 | ticker_step_size = ticker_info_list['%s' % ticker.upper()]['filters'][1]['stepSize'].rstrip('0')[::-1].find('.') 70 | ticker_min_value = float(ticker_info_list['%s' % ticker.upper()]['filters'][2]['minNotional']) 71 | ticker_baseAssetPrecision = ticker_info_list['%s' % ticker.upper()]['baseAssetPrecision'] 72 | ticker_quotePrecision = ticker_info_list['%s' % ticker.upper()]['quotePrecision'] 73 | 74 | # GET ACCOUNT BALANCES FOR THE CURRENCY PAIR 75 | balance_ticker = round(float(c.balance('%s' % ticker[:3].upper())),ticker_quotePrecision) 76 | balance_pair = round(float(c.balance('%s' % ticker[3:].upper())),ticker_baseAssetPrecision) 77 | USDT_ticker = (balance_ticker*(order_price*float(pair_quote['lastPrice']))) 78 | USDT_pair = balance_pair * float(pair_quote['lastPrice']) 79 | balance = USDT_ticker + USDT_pair 80 | order_ticker_adj = (1-(USDT_ticker/balance)) 81 | order_pair_adj = (1-(USDT_pair/balance)) 82 | 83 | # SET BALANCE LOWER LIMITS BASED ON TOTAL ALLOCATION($) AND SPLIT BETWEEN TRADING PAIR 84 | min_balance = 0.10 * (balance/2.0) 85 | 86 | # RSI, WMS and Slope FOR ORDER SIZE 87 | rsi = float(order_logic['lastprice_rsi']) 88 | wms = float(order_logic['WMS']) 89 | 90 | cur.execute("""select closetime,lastprice from binance.ticker_data where symbol='%s' order by closetime desc limit %s""" % (ticker.upper(),1440)) 91 | df = pd.DataFrame(cur.fetchall()).rename(index=str, columns={0: "closetime", 1: "lastprice"}).sort_values(by=['closetime']).astype(float) 92 | 93 | weighted_slope = TechInd.SlopeWeighted(df) 94 | 95 | if weighted_slope > 0 and order_type[-4:] == 'Sell': 96 | slope = abs(weighted_slope) 97 | elif weighted_slope < 0 and order_type[-3:] == 'Buy': 98 | slope = abs(weighted_slope) 99 | else: 100 | slope = 1 101 | 102 | # ORDER QUERY 103 | if order_type[-3:] == 'Buy': 104 | order_qty = round(((((USDT_pair*(rsi*wms))/float(pair_quote['lastPrice']))/order_price)*order_pair_adj)*slope,ticker_step_size) 105 | balance_threshold_check = (USDT_pair - (USDT_pair*(rsi*wms))) 106 | if balance_threshold_check >= min_balance: 107 | order_value = round(order_qty*(order_price*float(pair_quote['lastPrice'])),ticker_quotePrecision) 108 | if order_value >= (ticker_min_value*float(pair_quote['lastPrice'])) and order_qty >= ticker_min_qty: 109 | order = c.buy_limit(order_ticker,order_qty,order_price) 110 | try: 111 | order_msg = order['msg'] 112 | order_id = 'ERROR' 113 | except KeyError: 114 | order_msg = order_ticker + " " + order_type + " order placed for " + str(order_qty) + " at " + str(order_price) 115 | order_id = order['clientOrderId'] 116 | #order_id = 'order made' 117 | else: 118 | order_msg = "BUY ORDER SIZE BELOW MINIMUM." + " Order:" + str(order_qty) + ", Value: " + str(order_qty*order_price) 119 | order_id = 'No Order' 120 | else: 121 | order_msg = "BUY ORDER EXCEEDS MINIMUM BALANCE LIMIT." + " Order:" + str(order_qty) + ", Value: " + str(order_qty*order_price) 122 | order_id = 'No Order' 123 | 124 | elif order_type[-4:] == 'Sell': 125 | order_qty = round(((((USDT_ticker*(rsi*(1-wms)))/float(pair_quote['lastPrice']))/order_price)*order_ticker_adj)*slope,ticker_step_size) 126 | balance_threshold_check = (USDT_ticker - (USDT_ticker*(rsi*wms))) 127 | if balance_threshold_check >= min_balance: 128 | order_value = round((order_qty*(order_price*float(pair_quote['lastPrice']))),ticker_quotePrecision) 129 | if order_value >= (ticker_min_value*float(pair_quote['lastPrice'])) and order_qty >= ticker_min_qty: 130 | order = c.sell_limit(order_ticker,order_qty,order_price) 131 | try: 132 | order_msg = order['msg'] 133 | order_id = 'ERROR' 134 | 135 | except KeyError: 136 | order_msg = order_ticker + " " + order_type + " order placed for " + str(order_qty) + " at " + str(order_price) 137 | order_id = order['clientOrderId'] 138 | #order_id = 'order made' 139 | else: 140 | order_msg = "SELL ORDER SIZE BELOW MINIMUM." + " Order:" + str(order_qty) + ", Value: " + str(order_qty*order_price) 141 | order_id = 'No Order' 142 | else: 143 | order_msg = "SELL ORDER EXCEEDS MINIMUM BALANCE LIMIT." + " Order:" + str(order_qty) + ", Value: " + str(order_qty*order_price) 144 | order_id = 'No Order' 145 | 146 | else: 147 | order_qty = 0 148 | try: 149 | order_msg = 'Hold - No Action' 150 | except NameError: 151 | order_msg = 'Hold - No Action' 152 | 153 | try: 154 | order_id = 'Hold - No Action' 155 | except NameError: 156 | order_id = 'Hold - No Action' 157 | 158 | cur.execute("INSERT INTO binance.trade_log VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",(time, order_msg, order_ticker, order_id, order_type, order_price, rsi, order_qty, balance_ticker, USDT_ticker, balance_pair, USDT_pair, balance)) 159 | conn.commit() -------------------------------------------------------------------------------- /Algorithm/config.py: -------------------------------------------------------------------------------- 1 | 2 | import psycopg2 3 | 4 | conn = psycopg2.connect("dbname='$DATABASE_NAME' user='$USER_NAME' host='localhost' password='$PASSWORD'") 5 | cur = conn.cursor() 6 | 7 | api_key = '$API_KEY' 8 | api_secret = '$API_SECRET' 9 | 10 | recv_window = 5000 11 | 12 | # Enter ticker symbol 13 | 14 | ticker = ['btcusdt'] 15 | -------------------------------------------------------------------------------- /Algorithm/get_ticker_data.py: -------------------------------------------------------------------------------- 1 | 2 | import requests 3 | import config 4 | from urllib.parse import urlencode 5 | 6 | class queryBinance: 7 | BASE_URL = "https://www.binance.com/api/v1" 8 | 9 | def __init__(self, key, secret): 10 | self.key = key 11 | self.secret = secret 12 | 13 | def get_ticker(self, market): 14 | path = "%s/ticker/24hr" % self.BASE_URL 15 | params = {"symbol": market} 16 | return self._get_no_sign(path, params) 17 | 18 | def _get_no_sign(self, path, params={}): 19 | query = urlencode(params) 20 | url = "%s?%s" % (path, query) 21 | return requests.get(url, timeout=30, verify=True).json() 22 | 23 | client = queryBinance(config.api_key,config.api_secret) 24 | 25 | try: 26 | conn = config.conn 27 | cur = config.cur 28 | except: 29 | print ("I am unable to connect to the database") 30 | 31 | # Select tickers to scrape data for 32 | 33 | tickers = ['BTCUSDT','ETHUSDT','NEOUSDT','BNBUSDT','XRPUSDT','BCCUSDT','EOSUSDT', 34 | 'BNBETH','NEOETH','XRPETH','EOSETH','IOTAETH','BCCETH', 35 | 'ETHBTC','EOSBTC','XRPBTC','IOTABTC','BCCBTC','BNBBTC','NEOBTC' 36 | ] 37 | 38 | for x in tickers: 39 | x = client.get_ticker("%s" % x) 40 | try: 41 | cur.execute("""INSERT INTO binance.ticker_data(timeId,closetime,lastid,prevcloseprice,lastprice,askqty,askprice,count,symbol,quotevolume,volume,bidprice,firstid,lastqty, 42 | lowprice,bidqty,pricechangepercent,pricechange,highprice,opentime,weightedavgprice,openprice) 43 | VALUES (to_timestamp(%(closeTime)s/1000),%(closeTime)s,%(lastId)s,%(prevClosePrice)s,%(lastPrice)s,%(askQty)s,%(askPrice)s,%(count)s,%(symbol)s,%(quoteVolume)s, 44 | %(volume)s,%(bidPrice)s,%(firstId)s,%(lastQty)s,%(lowPrice)s,%(bidQty)s,%(priceChangePercent)s,%(priceChange)s, 45 | %(highPrice)s,%(openTime)s,%(weightedAvgPrice)s,%(openPrice)s)""", x) 46 | conn.commit() 47 | except: 48 | print("Load fail") 49 | 50 | #Uncomment the following to run algorithm 51 | 52 | #import Logic 53 | 54 | #import Trading_Algorithm 55 | 56 | cur.close() 57 | conn.close() -------------------------------------------------------------------------------- /Algorithm/technical_indicators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Dec 28 15:06:41 2017 4 | 5 | @author: Jake 6 | """ 7 | import pandas as pd 8 | import numpy as np 9 | from scipy.stats import linregress 10 | 11 | class TechInd: 12 | 13 | #Simple Moving Average 14 | def sma(data, window): 15 | sma = pd.rolling_mean(data["lastprice"], window) 16 | return sma 17 | 18 | #Eponential Moving Average 19 | def ewma(data, window): 20 | ewma = pd.ewma(data["lastprice"], span = window) 21 | return ewma 22 | 23 | #Simple Moving Average Volitility 24 | def sma_ind(data, window): 25 | sma = pd.rolling_mean(data["lastprice"], window) 26 | sma_ratio = (data["lastprice"]/sma)-1 27 | sma_mean = pd.stats.moments.rolling_mean(sma_ratio,20) 28 | sma_std = pd.stats.moments.rolling_std(sma_ratio,20) 29 | sma_ub2 = sma_mean + (sma_std*2) 30 | sma_lb2 = sma_mean - (sma_std*2) 31 | 32 | if pd.Series(sma_ratio).lt(0) == True: 33 | sma_ind = (abs(sma_ratio)/abs(sma_lb2))*-100 34 | else: 35 | sma_ind = (sma_ratio/sma_ub2)*100 36 | return sma_ind 37 | 38 | #Exponential Moving Average Volitility 39 | def ewma_ind(data, window): 40 | ewma = pd.ewma(data["lastprice"], span = window) 41 | ewma_ratio = (data["lastprice"]/ewma)-1 42 | ewma_mean = pd.stats.moments.rolling_mean(ewma_ratio,60) 43 | ewma_std = pd.stats.moments.rolling_std(ewma_ratio,60) 44 | ewma_ub2 = ewma_mean + (ewma_std*2) 45 | ewma_lb2 = ewma_mean - (ewma_std*2) 46 | 47 | if pd.Series(ewma_ratio).any() < 1: 48 | ewma_ind = (abs(ewma_ratio)/abs(ewma_lb2))*-100 49 | else: 50 | ewma_ind = (ewma_ratio/ewma_ub2)*100 51 | return ewma_ind 52 | 53 | #MACD Line and Crossover 54 | def MACD(data, span_short, span_long): 55 | ema_short = pd.ewma(data["lastprice"], span=span_short) 56 | ema_long = pd.ewma(data["lastprice"], span=span_long) 57 | MACD = ema_short - ema_long 58 | return MACD 59 | 60 | #MACD Cross 61 | def MACDcross(data, span_short, span_long, span_signal): 62 | ema_short = pd.ewma(data["lastprice"], span=span_short) 63 | ema_long = pd.ewma(data["lastprice"], span=span_long) 64 | MACD = ema_short - ema_long 65 | MACDsigline = pd.ewma(MACD, span=span_signal) 66 | MACDcross = MACD-MACDsigline 67 | return MACDcross 68 | 69 | #Death/Golden Cross (50ma,200ma). Cross up is bullish trend, cross down is bearish trend. 70 | def DG_Cross(data,short,long): 71 | short_ma = pd.ewma(data["lastprice"], span = short) 72 | long_ma = pd.ewma(data["lastprice"], span = long) 73 | DGcross = short_ma - long_ma 74 | return DGcross 75 | 76 | #Williams Overbought/Oversold Index 77 | #Overbought market condition: 20 or less, 78 | #Oversold market condition: 80 to 100 79 | def WMS(data,period): 80 | Ct = data["lastprice"] 81 | # Hn = pd.ewma(data['highprice'],span = period) 82 | Hn = data['highprice'].ewm(span = period).mean() 83 | # Ln = pd.ewma(data['lowprice'],span = period) 84 | Ln = data['lowprice'].ewm(span = period).mean() 85 | WMS = (Hn - Ct)/((Hn - Ln) * 1) 86 | WMS = pd.Series(WMS,name='WMS') 87 | WMS = WMS.clip(0.2,0.8) 88 | return WMS 89 | 90 | #RSI 91 | def RSI(data,period): 92 | rsi_data = data['lastprice'] 93 | rsi_delta = rsi_data.diff().dropna() 94 | 95 | up, down = rsi_delta.copy(),rsi_delta.copy() 96 | up[up<0]=0 97 | down[down>0]=0 98 | 99 | rsi_sma_up = up.rolling(period).mean() 100 | down = down.abs() 101 | rsi_sma_down = down.rolling(period).mean() 102 | 103 | rsi_sma_1 = rsi_sma_up/rsi_sma_down 104 | rsi_sma = 100.0 - (100.0/(1.0+rsi_sma_1)) 105 | rsi_series = abs(rsi_sma - rsi_sma.mean())/100 106 | rsi = rsi_series.dropna() 107 | return rsi 108 | 109 | # Weighted SlopeWeighted 110 | def SlopeWeighted(data,minutes): 111 | 112 | length = minutes * 24 113 | trend = pd.DataFrame(np.arange(1,length+1,1),columns=['trend']).set_index(data.index) 114 | df24 = data.assign(trend = trend['trend']) 115 | df12 = df24[int(length/2):] 116 | df6 = df24[int(length/4):] 117 | df3 = df24[int(length/8):] 118 | df1 = df24[int(length/24):] 119 | 120 | df24norm = (df24 - df24.min()) / (df24.max() - df24.min()) 121 | df12norm = (df12 - df12.min()) / (df12.max() - df12.min()) 122 | df6norm = (df6 - df6.min()) / (df6.max() - df6.min()) 123 | df3norm = (df3 - df3.min()) / (df3.max() - df3.min()) 124 | df1norm = (df1 - df1.min()) / (df1.max() - df1.min()) 125 | 126 | output24 = linregress(df24norm['trend'],df24norm['lastprice']) 127 | output12 = linregress(df12norm['trend'],df12norm['lastprice']) 128 | output6 = linregress(df6norm['trend'],df6norm['lastprice']) 129 | output3 = linregress(df3norm['trend'],df3norm['lastprice']) 130 | output1 = linregress(df1norm['trend'],df1norm['lastprice']) 131 | 132 | weighted_slope = (output24[0]*(24/46)) + (output12[0]*(12/46)) + (output6[0]*(6/46)) + (output3[0]*(3/46)) + (output1[0]*(1/46)) 133 | return weighted_slope 134 | 135 | #Trading logic for backtesting 136 | def Trade_Logic(loop,data,engine,output,min_balance,USDT_ticker,USDT_pair,fee,ticker_step_size,ticker_quotePrecision,ticker_min_value,ticker_min_qty): 137 | 138 | length = loop[1] 139 | minutes = loop[0] 140 | 141 | df = data[:length].sort_values(by=['closetime']) 142 | 143 | ema_short = df['lastprice'].ewm(span=12*minutes,min_periods=0,adjust=True,ignore_na=False).mean() 144 | ema_long = df['lastprice'].ewm(span=26*minutes,min_periods=0,adjust=True,ignore_na=False).mean() 145 | sma7 = df['lastprice'].ewm(span=7*minutes,min_periods=0,adjust=True,ignore_na=False).mean() 146 | MACD = ema_short - ema_long 147 | MACDsignal = df['lastprice'].ewm(span=9*minutes,min_periods=0,adjust=True,ignore_na=False).mean() 148 | MACDcross = MACD-MACDsignal 149 | WMS = TechInd.WMS(df,8*minutes) 150 | 151 | #MACDsignal 152 | ###Bands 153 | MACDsignal_mean = MACDsignal.rolling(window=9*minutes,center=False).mean() 154 | MACDsignal_std = MACDsignal.rolling(window=9*minutes,center=False).std() 155 | 156 | MACDsignal_ub2 = MACDsignal_mean + (MACDsignal_std*2) 157 | MACDsignal_lb2 = MACDsignal_mean - (MACDsignal_std*2) 158 | MACDsignal_ub1 = MACDsignal_mean + (MACDsignal_std) 159 | MACDsignal_lb1 = MACDsignal_mean - (MACDsignal_std) 160 | MACDsignal_ub_5 = MACDsignal_mean + (MACDsignal_std*.5) 161 | MACDsignal_lb_5 = MACDsignal_mean - (MACDsignal_std*.5) 162 | ###DataFrame 163 | MACDsignal_df = MACDsignal.to_frame() 164 | MACDsignal_ub_df = MACDsignal_ub1.to_frame() 165 | MACDsignal_lb_df = MACDsignal_lb1.to_frame() 166 | ###DataFrame with Logic 167 | MACDsignal_final = MACDsignal_df.join(MACDsignal_ub_df, how='inner',rsuffix='_ub').join(MACDsignal_lb_df, how='inner',rsuffix='_lb') 168 | MACDsignal_final.loc[(MACDsignal_final['lastprice'] >= MACDsignal_final['lastprice_ub']),'test'] = 'Upper' 169 | MACDsignal_final.loc[(MACDsignal_final['lastprice'] <= MACDsignal_final['lastprice_lb']),'test'] = 'Lower' 170 | MACDsignal_final.loc[(MACDsignal_final['lastprice'] < MACDsignal_final['lastprice_ub']) & (MACDsignal_final['lastprice'] > MACDsignal_final['lastprice_lb']),'test'] = 'Mid' 171 | 172 | #MACDcross 173 | ###Bands 174 | MACDcross_mean = MACDcross.rolling(window=9*minutes,center=False).mean() 175 | MACDcross_std = MACDcross.rolling(window=9*minutes,center=False).std() 176 | MACDcross_ub2 = MACDcross_mean + (MACDcross_std*2) 177 | MACDcross_lb2 = MACDcross_mean - (MACDcross_std*2) 178 | MACDcross_ub1 = MACDcross_mean + (MACDcross_std) 179 | MACDcross_lb1 = MACDcross_mean - (MACDcross_std) 180 | MACDcross_ub_5 = MACDcross_mean + (MACDcross_std*.5) 181 | MACDcross_lb_5 = MACDcross_mean - (MACDcross_std*.5) 182 | ###DataFrame 183 | MACDcross_df = MACDcross.to_frame() 184 | MACDcross_ub_df = MACDcross_ub1.to_frame() 185 | MACDcross_lb_df = MACDcross_lb1.to_frame() 186 | ###DataFrame with Logic 187 | MACDcross_final = MACDcross_df.join(MACDcross_ub_df, how='inner',rsuffix='_ub').join(MACDcross_lb_df, how='inner',rsuffix='_lb') 188 | MACDcross_final.loc[(MACDcross_final['lastprice'] >= MACDcross_final['lastprice_ub']),'test'] = 'Upper' 189 | MACDcross_final.loc[(MACDcross_final['lastprice'] <= MACDcross_final['lastprice_lb']),'test'] = 'Lower' 190 | MACDcross_final.loc[(MACDcross_final['lastprice'] < MACDcross_final['lastprice_ub']) & (MACDcross_final['lastprice'] > MACDcross_final['lastprice_lb']),'test'] = 'Mid' 191 | 192 | #SMA 7 193 | ###Bands 194 | sma7_mean = sma7.rolling(window=7*minutes,center=False).mean() 195 | sma7_std = sma7.rolling(window=7*minutes,center=False).std() 196 | sma7_ub2 = sma7_mean + (sma7_std*2) 197 | sma7_lb2 = sma7_mean - (sma7_std*2) 198 | sma7_ub1_5 = sma7_mean + (sma7_std*1.5) 199 | sma7_lb1_5 = sma7_mean - (sma7_std*1.5) 200 | sma7_ub1_25 = sma7_mean + (sma7_std*1.25) 201 | sma7_lb1_25 = sma7_mean - (sma7_std*1.25) 202 | sma7_ub1 = sma7_mean + (sma7_std) 203 | sma7_lb1 = sma7_mean - (sma7_std) 204 | sma7_ub_5 = sma7_mean + (sma7_std*.5) 205 | sma7_lb_5 = sma7_mean - (sma7_std*.5) 206 | ###DataFrame 207 | sma7_df = sma7.to_frame() 208 | sma7_ub_df = sma7_ub1_5.to_frame() 209 | sma7_lb_df = sma7_lb1_5.to_frame() 210 | ###DataFrame with Logic 211 | df1 = df['lastprice'].to_frame() 212 | sma7_final = df1.join(sma7_ub_df, how='inner',rsuffix='_ub').join(sma7_lb_df, how='inner',rsuffix='_lb') 213 | sma7_final.loc[(sma7_final['lastprice'] >= sma7_final['lastprice_ub']),'test'] = 'Upper' 214 | sma7_final.loc[(sma7_final['lastprice'] <= sma7_final['lastprice_lb']),'test'] = 'Lower' 215 | sma7_final.loc[(sma7_final['lastprice'] < sma7_final['lastprice_ub']) & (sma7_final['lastprice'] > sma7_final['lastprice_lb']),'test'] = 'Mid' 216 | 217 | #RSI 218 | RSI = TechInd.RSI(df1,(8*minutes)) 219 | 220 | #Final DataFrame 221 | logic = sma7_final.join(MACDcross_final['test'], how='inner',rsuffix='_MACDcross').join(MACDsignal_final['test'], how='inner',rsuffix='_MACDsignal').join(RSI,how='inner',rsuffix='_rsi').join(WMS, how='inner').dropna() 222 | 223 | ### MACD Signal 'Upper' 224 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Upper'),'orderType'] = 'uuu_Sell' 225 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 226 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 227 | 228 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Upper'),'orderType'] = 'umu_Sell' 229 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 230 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 231 | 232 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 233 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 234 | logic.loc[(logic['test_MACDsignal'] == 'Upper') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Lower'),'orderType'] = 'ull_Buy' 235 | 236 | ### MACD Signal 'Mid' 237 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 238 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 239 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 240 | 241 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 242 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 243 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 244 | 245 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 246 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 247 | logic.loc[(logic['test_MACDsignal'] == 'Mid') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 248 | 249 | ### MACD Signal 'Lower' 250 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 251 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 252 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Upper') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 253 | 254 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 255 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 256 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Mid') & (logic['test'] == 'Lower'),'orderType'] = 'Hold' 257 | 258 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Upper'),'orderType'] = 'Hold' 259 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Mid'),'orderType'] = 'Hold' 260 | logic.loc[(logic['test_MACDsignal'] == 'Lower') & (logic['test_MACDcross'] == 'Lower') & (logic['test'] == 'Lower'),'orderType'] = 'lll_Buy' 261 | 262 | balance_ticker = USDT_ticker/(logic['lastprice'][0]) 263 | 264 | logic['order_msg'], logic['order_id'], logic['order_qty'], logic['order_value'], logic['fee_charged'], logic['USDT_pair'], logic['USDT_ticker'], logic['balance_ticker'] = zip(*logic.apply(TechInd.TradeTest, axis=1, args =[min_balance,USDT_ticker,USDT_pair,balance_ticker,fee,ticker_step_size,ticker_quotePrecision,ticker_min_value,ticker_min_qty])) 265 | 266 | logic['USDT_total']= logic['USDT_pair'] + logic['USDT_ticker'] 267 | 268 | old = float(logic['USDT_total'][:1]) 269 | new = float(logic['USDT_total'][-1:]) 270 | 271 | roi = ((new-old)/old)*100 272 | output['ROI_%s' % int(length/(24 * 60))].iloc[minutes] = roi 273 | print(roi) 274 | return output,logic 275 | 276 | #Trading algorithm for backtesting 277 | def TradeTest(row, min_balance, USDT_ticker, USDT_pair, balance_ticker, fee, ticker_step_size, ticker_quotePrecision, ticker_min_value, ticker_min_qty): 278 | 279 | if row.orderType[-3:] == 'Buy': 280 | order_qty = round(((((USDT_pair*(row['lastprice_rsi']*row['WMS']))/float(1.00))/row['lastprice'])*(1-(USDT_pair/(USDT_ticker+USDT_pair)))),ticker_step_size) 281 | balance_threshold_check = (USDT_pair - (order_qty*row['lastprice'])) 282 | if balance_threshold_check > min_balance: 283 | order_value = round(order_qty*(row['lastprice']*float(1.00)),ticker_quotePrecision) 284 | if order_value > (ticker_min_value*float(1.00)) and order_qty > ticker_min_qty: 285 | order_msg = "Buy" + " Order: " + str(order_qty) + ", Value: " + str(order_value) 286 | order_id = 'BUY' 287 | fee_charged = order_value*fee 288 | USDT_pair -= order_value 289 | balance_ticker += order_qty 290 | USDT_ticker = ((balance_ticker * row['lastprice']) - fee_charged) 291 | 292 | else: 293 | order_msg = "BUY ORDER SIZE BELOW MINIMUM." + " Order:" + str(order_qty) + ", Value: " + str(order_value) 294 | order_id = 'No Order' 295 | fee_charged = 0 296 | USDT_pair 297 | balance_ticker 298 | USDT_ticker = balance_ticker * row['lastprice'] 299 | 300 | else: 301 | order_value = round(order_qty*(row['lastprice']*float(1.00)),ticker_quotePrecision) 302 | order_msg = "BUY ORDER EXCEEDS MINIMUM BALANCE LIMIT." + " Order:" + str(order_qty) + ", Value: " + str(order_value) 303 | order_id = 'No Order' 304 | fee_charged = 0 305 | USDT_pair 306 | balance_ticker 307 | USDT_ticker = balance_ticker * row['lastprice'] 308 | 309 | 310 | elif row.orderType[-4:] == 'Sell': 311 | order_qty = round(((((USDT_ticker*(row['lastprice_rsi']*(1-row['WMS'])))/float(1.00))/row['lastprice'])*(1-(USDT_ticker/(USDT_ticker+USDT_pair)))),ticker_step_size) 312 | balance_threshold_check = (USDT_ticker - (order_qty*row['lastprice'])) 313 | if balance_threshold_check > min_balance: 314 | order_value = round((order_qty*(row['lastprice']*float(1.00))),ticker_quotePrecision) 315 | if order_value >= (ticker_min_value*float(1.00)) and order_qty >= ticker_min_qty: 316 | order_msg = "Sell" + " Order: " + str(order_qty) + ", Value: " + str(order_value) 317 | order_id = 'SELL' 318 | fee_charged = order_value*fee 319 | USDT_pair += (order_value - fee_charged) 320 | balance_ticker -= order_qty 321 | USDT_ticker = balance_ticker * row['lastprice'] 322 | 323 | else: 324 | order_msg = "SELL ORDER SIZE BELOW MINIMUM." + " Order:" + str(order_qty) + ", Value: " + str(order_value) 325 | order_id = 'No Order' 326 | fee_charged = 0 327 | USDT_pair 328 | balance_ticker 329 | USDT_ticker = balance_ticker * row['lastprice'] 330 | 331 | else: 332 | order_value = round((order_qty*(row['lastprice']*float(1.00))),ticker_quotePrecision) 333 | order_msg = "SELL ORDER EXCEEDS MINIMUM BALANCE LIMIT." + " Order:" + str(order_qty) + ", Value: " + str(order_value) 334 | order_id = 'No Order' 335 | fee_charged = 0 336 | USDT_pair 337 | balance_ticker 338 | USDT_ticker = balance_ticker * row['lastprice'] 339 | 340 | else: 341 | order_msg = 'Hold - No Action' 342 | order_id = 'Hold - No Action' 343 | order_qty = 0 344 | order_value = 0 345 | fee_charged = 0 346 | USDT_pair 347 | balance_ticker 348 | USDT_ticker = balance_ticker * row['lastprice'] 349 | 350 | return [order_msg, order_id, order_qty, order_value, fee_charged, USDT_pair, USDT_ticker, balance_ticker] 351 | -------------------------------------------------------------------------------- /Auxiliary Programs/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Auxiliary Programs/Testing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Jan 19 11:46:30 2018 4 | 5 | @author: Jacyle 6 | """ 7 | 8 | from BinanceAPI import BinanceAPI 9 | from technical_indicators import TechInd 10 | import pandas as pd 11 | import psycopg2 12 | import itertools 13 | from sqlalchemy import create_engine 14 | import config 15 | import time 16 | 17 | c = BinanceAPI(config.api_key,config.api_secret) 18 | 19 | start_time = time.time() 20 | 21 | ticker = config.ticker 22 | 23 | roi_len = 60 24 | 25 | days = 7 26 | length = days * 24 * 60 27 | 28 | USDT_ticker = 5000.00 29 | USDT_pair = 5000.00 30 | 31 | min_balance = 0.05 * ((USDT_ticker + USDT_pair)/2.0) 32 | 33 | fee = .0005 34 | 35 | df_roi = pd.DataFrame(index=range(0,roi_len+1)) 36 | 37 | for i in range(1,days+1): 38 | df_roi['ROI_%s' % i] = pd.Series(index=range(0,roi_len+1)) 39 | 40 | loop = itertools.product(range(1,(roi_len+1)), range((24*60),((days+1) * 24 * 60),(24*60))) 41 | 42 | # GET CURRENCY PAIR VALUE AND ORDER MINIMUMS 43 | ticker_info = c.get_ticker_info()['symbols'] 44 | ticker_info_list = {item['symbol']: item for item in ticker_info} 45 | ticker_min_qty = float(ticker_info_list['%s' % ticker.upper()]['filters'][1]['minQty']) 46 | ticker_step_size = ticker_info_list['%s' % ticker.upper()]['filters'][1]['stepSize'].rstrip('0')[::-1].find('.') 47 | ticker_min_value = float(ticker_info_list['%s' % ticker.upper()]['filters'][2]['minNotional']) 48 | ticker_baseAssetPrecision = ticker_info_list['%s' % ticker.upper()]['baseAssetPrecision'] 49 | ticker_quotePrecision = ticker_info_list['%s' % ticker.upper()]['quotePrecision'] 50 | 51 | engine = create_engine('$DB_CONNECTION') 52 | 53 | try: 54 | conn = psycopg2.connect("$DB_CONNECTION") 55 | cur = conn.cursor() 56 | except: 57 | print ("I am unable to connect to the database") 58 | 59 | cur.execute("""SELECT closetime, to_number(lastprice,'99999999.99999999') as lastprice, to_number(highprice,'99999999.99999999') as highprice,to_number(lowprice,'99999999.99999999') as lowprice from binance.ticker_data where symbol = %s order by closetime desc limit %s """,(ticker.upper(),length)) 60 | df = pd.DataFrame(cur.fetchall()).rename(columns={0: "closetime", 1: "lastprice", 2:"highprice", 3: "lowprice"}).astype(float) 61 | 62 | 63 | for i in loop: 64 | TechInd.Trade_Logic(i,df,engine,df_roi,min_balance,USDT_ticker,USDT_pair,fee,ticker_step_size,ticker_quotePrecision,ticker_min_value,ticker_min_qty) 65 | 66 | df_roi.index.name = 'Window' 67 | df_roi[1:].to_csv('roi_back_testing.csv') 68 | 69 | cur.close() 70 | conn.close() 71 | print("--- %s seconds ---" % (((time.time() - start_time)/60)*60)) -------------------------------------------------------------------------------- /Auxiliary Programs/corr_matrix: -------------------------------------------------------------------------------- 1 | 2 | 3 | import pandas as pd 4 | import psycopg2 5 | 6 | try: 7 | conn = psycopg2.connect("dbname='$DATABASE_NAME' user='$USER_NAME' host='localhost' password='$PASSWORD'") 8 | cur = conn.cursor() 9 | except: 10 | print ("I am unable to connect to the database") 11 | 12 | # Create correlation matrix for the last 1440 minutes (24 hours) 13 | 14 | corr_data = pd.read_sql_query("select symbol,lastprice from binance.ticker_data order by closetime desc limit 1440",conn) 15 | symbols = corr_data.drop_duplicates(subset='symbol') 16 | 17 | table = pd.DataFrame() 18 | 19 | for i in symbols['symbol']: 20 | table[i] = pd.DataFrame({'%s' % i: corr_data['lastprice'][corr_data['symbol'] == '%s' % i]}).reset_index()['%s' % i].to_frame().astype(float) 21 | 22 | corr_table = table.corr() 23 | 24 | conn.close() 25 | cur.close() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jacyle 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Binance Technical Trading Algorithm 2 | This project is developing a technical based trading algorithm to be used with Binance. 3 | 4 | [How the algorithm works](https://github.com/Jacyle/binance-technical-algorithm/blob/master/Algorithm/README.md) 5 | 6 | --- 7 | 8 | ## Performance 9 | 10 | https://public.tableau.com/profile/jacyle/#!/ 11 | 12 | --- 13 | 14 | ## Requirements 15 | 16 | - Binance API Key 17 | - Python 3.5 18 | - PostgreSQL 9.6 19 | 20 | --- 21 | 22 | ## My Setup 23 | - Debian 9.4 24 | - Python 3.5 25 | - [Anaconda](https://conda.io/docs/user-guide/install/linux.html) 26 | - Spyder IDE 27 | - PostgreSQL 9.6 28 | - PgAdmin 4 29 | 30 | --- 31 | 32 | ## Binance Official Documentation for the APIs and Streams 33 | 34 | https://github.com/binance-exchange/binance-official-api-docs 35 | 36 | --- 37 | 38 | ## Binance Trading Rules 39 | 40 | https://support.binance.com/hc/en-us/articles/115000594711-Trading-Rule 41 | 42 | --- 43 | 44 | ## Telegram Channel 45 | https://t.me/binance_technical_algorithm 46 | 47 | --- 48 | -------------------------------------------------------------------------------- /SQL Code/Create_Ticker_Data_Table: -------------------------------------------------------------------------------- 1 | create schema binance 2 | 3 | drop table binance.ticker_data; 4 | 5 | create table binance.ticker_data 6 | (timeId text, closeTime bigint, lastId integer, prevClosePrice text, lastPrice text, askQty text, askPrice text, count integer, symbol text, 7 | quoteVolume text, volume text, bidPrice text, firstId integer, lastQty text, lowPrice text, bidQty text, 8 | priceChangePercent text, priceChange text, highPrice text, openTime bigint, weightedAvgPrice text, openPrice text, 9 | primary key(closeTime,openTime,symbol)); 10 | -------------------------------------------------------------------------------- /SQL Code/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /SQL Code/create trade_log table: -------------------------------------------------------------------------------- 1 | drop table binance.trade_log; 2 | 3 | create table binance.trade_log 4 | (time text, order_msg text, order_ticker text, order_id text, order_type text, 5 | order_price real, rsi real, order_qty real, balance_ticker real, USDT_ticker real, 6 | balance_pair real, USDT_pair real, balance real); 7 | -------------------------------------------------------------------------------- /SQL Code/view_database_connections: -------------------------------------------------------------------------------- 1 | SELECT 2 | pid, 3 | now() - query_start AS duration, 4 | query, 5 | state 6 | FROM pg_stat_activity 7 | -- state = 'idle in transaction' 8 | 9 | select * from pg_locks 10 | 11 | SELECT pg_terminate_backend(pid) 12 | FROM pg_stat_activity 13 | WHERE datname='postgres' 14 | AND state = 'idle in transaction' 15 | 16 | select * from binance.ticker_data where symbol='ETHUSDT' order by closetime desc limit 720 --------------------------------------------------------------------------------