├── ta_stoch ├── description.md └── ta_stoch.py ├── bitfinex_websocket_basic ├── description.md └── bitfinex_websocket_basic.py ├── binance_pairs_ema ├── description.md └── binance_pairs_ema.py ├── bitmex_auto_sl_tp ├── description.md └── bitmex_auto_sl_tp.py ├── ta_ema_cross ├── description.md └── ta_ema_cross.py ├── bitfinex_websocket_multi ├── description.md └── bitfinex_websocket_multi.py ├── LICENSE └── README.md /ta_stoch/description.md: -------------------------------------------------------------------------------- 1 | # Stochastic Oscillator (Basic) 2 | 3 | A basic example of how to calculate Stochastic Oscillator values for the ETHUSD market, with 1 hour candle data from HitBTC. The ```bfxhfindicators``` module is used. 4 | 5 | # Dependencies 6 | 7 | - ```requests``` 8 | - ```bfxhfindicators``` 9 | -------------------------------------------------------------------------------- /bitfinex_websocket_basic/description.md: -------------------------------------------------------------------------------- 1 | # Basic Authenticated Websocket Connection to Bitfinex's API 2 | 3 | This script is an example of how to create an authenticated websocket connection to Bitfinex's API using the Python module ```websocket-client``` 4 | 5 | # Dependencies 6 | 7 | ```websocket-client``` 8 | -------------------------------------------------------------------------------- /binance_pairs_ema/description.md: -------------------------------------------------------------------------------- 1 | # Get all Binance USDT pairs and filter by {price vs EMA} 2 | 3 | This script demonstrates how to query Binance for all active USDT trading pairs, get candle data for them and calculate EMAs for each. The results are then used save txt files with symbols filtered by criteria such as: 4 | 5 | - Trading below EMA(50) 6 | - Trading above EMA(50) and below EMA(200) 7 | - Trading above EMA(200) 8 | 9 | ## Dependencies 10 | 11 | - ```bfxhfindicators``` (technical analysis library made by Bitfinex) 12 | - ```requests``` 13 | -------------------------------------------------------------------------------- /bitmex_auto_sl_tp/description.md: -------------------------------------------------------------------------------- 1 | # Automatic stop loss and take profit orders for BitMEX 2 | 3 | An example Python script that automatically places **Stop Loss** and **Take Profit** orders for positions you open while trading on BitMEX, according to set margins. 4 | 5 | Features include: 6 | 7 | - Configurable margins for stop loss or take profit (based on % distance from entry price of position) 8 | - Suppports XBTUSD and ETHUSD by default; additional markets can be added 9 | - Toggles to enable/disable stop loss or take profit independently 10 | 11 | # Dependencies 12 | 13 | ```requests``` 14 | -------------------------------------------------------------------------------- /ta_ema_cross/description.md: -------------------------------------------------------------------------------- 1 | # Detect EMA Crosses for a List of Markets 2 | 3 | This script uses 15 min candle data from HitBTC, for a predetermined number of market symbols, to calculate EMA values for two EMA periods (10 and 20). It then detects EMA crosses within a configurable window of historic EMA values (10 in this case) and prints the results on the terminal. EMA crosses in both directions are supported. 4 | 5 | # Dependencies 6 | 7 | - `requests` 8 | - `bfxhfindicators` 9 | 10 | # Default Configurations 11 | 12 | - EMA periods: `[10,20]` 13 | - Historic window: `10` 14 | - Symbols: `20 USD pairs` 15 | - Candle timeframes: `15m` 16 | -------------------------------------------------------------------------------- /bitfinex_websocket_multi/description.md: -------------------------------------------------------------------------------- 1 | # Subscribing to Multiple Channels via Websocket (Bitfinex) 2 | 3 | A script demonstrating how to subscribe to multiple ticker and candle channels via Bitfinex's Websocket API. It gets all USD pairs on Bitfinex and then subscribes to their ticker and 15m candle channels. It also features a simple interactive tool to view ticker and candle details for a symbol on the terminal. 4 | 5 | ## Dependencies 6 | 7 | - `requests` 8 | - `websocket-client` 9 | 10 | ## Using the interactive tool 11 | 12 | - After running the script, wait for it to print a statement saying `Tickers and candles loaded. You may query a symbol now.` 13 | - Type in a symbol name and press ENTER, e.g. btcusd (it is not case-sensetive) or ltcusd 14 | - The current ticker details as well as the latest candle will be printed 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Innocent Mwatsikesimbe 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 | # crypto-market-samples 2 | 3 | A collection of sample Python scripts that one can incorporate or build upon when developing customized systems for trading cryptocurrency 4 | 5 | --- 6 | 7 | # Current Sample Scripts 8 | 9 | ## Technical Analysis (bfxhfindicators) 10 | 11 | - Stochastic Oscillator (Basic): [ta_stoch](https://github.com/imwatsi/crypto-market-samples/tree/master/ta_stoch) 12 | - Detect EMA crosses: [ta_ema_cross](https://github.com/imwatsi/crypto-market-samples/tree/master/ta_ema_cross) 13 | 14 | --- 15 | 16 | ## Binance 17 | 18 | - Get all Binance USDT pairs and filter by {price vs EMA}: [binance_pairs_ema](https://github.com/imwatsi/crypto-market-samples/tree/master/binance_pairs_ema) 19 | 20 | --- 21 | 22 | ## Bitfinex 23 | 24 | - Basic authenticated websocket connection: [bitfinex_websocket_basic](https://github.com/imwatsi/crypto-market-samples/tree/master/bitfinex_websocket_basic) 25 | - Subscribing to multiple channels via websocket: [bitfinex_websocket_multi](https://github.com/imwatsi/crypto-market-samples/tree/master/bitfinex_websocket_multi) 26 | 27 | --- 28 | 29 | ## BitMEX 30 | 31 | - Automatic stop loss and take profit orders for BitMEX: [bitmex_auto_sl_tp](https://github.com/imwatsi/crypto-market-samples/tree/master/bitmex_auto_sl_tp) 32 | 33 | -------------------------------------------------------------------------------- /ta_stoch/ta_stoch.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | from bfxhfindicators import Stochastic 5 | from threading import Thread 6 | 7 | candles = [] 8 | 9 | def load_candles(): 10 | global candles 11 | while True: 12 | resp = requests.get('https://api.hitbtc.com/api/2/public/candles/ETHUSD?period=H1') 13 | raw_candles = json.loads(resp.content) 14 | parsed_candles = [] 15 | for raw_c in raw_candles: 16 | new_candle = { 17 | 'timestamp': raw_c['timestamp'], 18 | 'close': float(raw_c['close']), 19 | 'low': float(raw_c['min']), 20 | 'high': float(raw_c['max']) 21 | } 22 | parsed_candles.append(new_candle) 23 | candles = parsed_candles[:] 24 | time.sleep(5) 25 | 26 | # start loop that loads candles 27 | Thread(target=load_candles).start() 28 | 29 | # wait for candles to populate 30 | while len(candles) == 0: 31 | time.sleep(1) 32 | 33 | # calculate Stochastic Oscillator values 34 | while True: 35 | iStoch = Stochastic([14,3,3]) 36 | for candle in candles: 37 | iStoch.add(candle) 38 | stoch_values = iStoch.v() 39 | # print Stochastic values, identify basic levels 40 | str_print = 'ETHUSD: K:%s D:%s' %(round(stoch_values['k'],4), round(stoch_values['d'],4)) 41 | if stoch_values['k'] > 80 and stoch_values['d'] > 80: 42 | str_print += ' In overbought area...' 43 | elif stoch_values['k'] < 20 and stoch_values['d'] < 20: 44 | str_print += ' In oversold area...' 45 | print(str_print, end='\r', flush=True) 46 | time.sleep(1) 47 | -------------------------------------------------------------------------------- /binance_pairs_ema/binance_pairs_ema.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import os 4 | import time 5 | from threading import Thread 6 | from bfxhfindicators import EMA 7 | 8 | BASE_URL = 'https://api.binance.com' 9 | 10 | TIMEFRAME = '4h' 11 | EMA_PERIODS = [50, 200] 12 | 13 | symbols = [] 14 | candles = {} 15 | prices = {} 16 | ema_values = {} 17 | 18 | def load_candles(sym): 19 | global candles, prices, BASE_URL 20 | payload = { 21 | 'symbol': sym, 22 | 'interval': '4h', 23 | 'limit': 250 24 | } 25 | resp = requests.get(BASE_URL + '/api/v1/klines', params=payload) 26 | klines = json.loads(resp.content) 27 | # parse klines and store open, high, low, close and vol only 28 | parsed_klines = [] 29 | for k in klines: 30 | k_candle = { 31 | 'open': float(k[1]), 32 | 'high': float(k[2]), 33 | 'low': float(k[3]), 34 | 'close': float(k[4]), 35 | 'vol': float(k[5]) 36 | } 37 | parsed_klines.append(k_candle) 38 | candles[sym] = parsed_klines 39 | index = len(parsed_klines) - 1 # get index of latest candle 40 | prices[sym] = parsed_klines[index]['close'] # save current price 41 | 42 | # create results folder if it doesn't exist 43 | if not os.path.exists('results/'): 44 | os.makedirs('results/') 45 | # start with blank files 46 | open('results/below_50.txt', 'w').close() 47 | open('results/above_50_below_200.txt', 'w').close() 48 | open('results/above_200.txt', 'w').close() 49 | 50 | # load symbols information 51 | print('Getting list of BTC trade pairs...') 52 | resp = requests.get(BASE_URL + '/api/v1/ticker/allBookTickers') 53 | tickers_list = json.loads(resp.content) 54 | for ticker in tickers_list: 55 | if str(ticker['symbol'])[-4:] == 'USDT': 56 | symbols.append(ticker['symbol']) 57 | 58 | # get 4h candles for symbols 59 | print('Loading candle data for symbols...') 60 | for sym in symbols: 61 | Thread(target=load_candles, args=(sym,)).start() 62 | while len(candles) < len(symbols): 63 | print('%s/%s loaded' %(len(candles), len(symbols)), end='\r', flush=True) 64 | time.sleep(0.1) 65 | 66 | # calculate EMAs for each symbol 67 | print('Calculating EMAs...') 68 | for sym in candles: 69 | for period in EMA_PERIODS: 70 | iEMA = EMA([period]) 71 | lst_candles = candles[sym][:] 72 | for c in lst_candles: 73 | iEMA.add(c['close']) 74 | if sym not in ema_values: 75 | ema_values[sym] = {} 76 | ema_values[sym][period] = iEMA.v() 77 | 78 | # save filtered EMA results in txt files 79 | print('Saving filtered EMA results to txt files...') 80 | for sym in ema_values: 81 | ema_50 = ema_values[sym][50] 82 | ema_200 = ema_values[sym][200] 83 | price = prices[sym] 84 | entry = '' 85 | if price < ema_50: 86 | # save symbols trading below EMA (50) 87 | f = open('results/below_50.txt', 'a') 88 | entry = '%s: $%s\n' %(sym, round(price,3)) 89 | f.write(entry) 90 | elif price > ema_50 and price < ema_200: 91 | # save symbols trading above EMA(200) 92 | f = open('results/above_50_below_200.txt', 'a') 93 | entry = '%s: $%s\n' %(sym, round(price,3)) 94 | f.write(entry) 95 | elif price > ema_200: 96 | # save symbols trading above EMA(50) but below EMA(200) 97 | f = open('results/above_200.txt', 'a') 98 | entry = '%s: $%s\n' %(sym, round(price,3)) 99 | f.write(entry) 100 | f.close() 101 | del f # cleanup 102 | 103 | print('All done! Results saved in results folder.') 104 | 105 | -------------------------------------------------------------------------------- /bitfinex_websocket_basic/bitfinex_websocket_basic.py: -------------------------------------------------------------------------------- 1 | import websocket 2 | import hashlib 3 | import hmac 4 | import json 5 | import time 6 | import os 7 | from threading import Thread 8 | 9 | # INPUT API CREDENTIALS: 10 | API_KEY = '' 11 | API_SECRET = '' 12 | 13 | # GLOBAL VARIABLES 14 | channels = {0: 'Bitfinex'} 15 | tickers = {} 16 | 17 | 18 | def print_ticker(): 19 | global ticker 20 | symbol = 'BTCUSD' 21 | while len(tickers) == 0: 22 | # wait for tickers to populate 23 | time.sleep(1) 24 | while True: 25 | # print BTCUSD ticker every second 26 | details = tickers[symbol] 27 | print('%s: Bid: %s, Ask: %s, Last Price: %s, Volume: %s'\ 28 | %(symbol, details['bid'], details['ask'],\ 29 | details['last_price'], details['volume']), end="\r", flush=True) 30 | time.sleep(1) 31 | 32 | def new_order_market(symbol, amount): 33 | global ws 34 | cid = int(round(time.time() * 1000)) 35 | order_details = { 36 | 'cid': cid, 37 | 'type': 'EXCHANGE MARKET', 38 | 'symbol': 't' + symbol, 39 | 'amount': str(amount) 40 | } 41 | msg = [ 42 | 0, 43 | 'on', 44 | None, 45 | order_details 46 | ] 47 | ws.send(json.dumps(msg)) 48 | 49 | def on_message(ws, message): 50 | global channels, balances, tickers 51 | data = json.loads(message) 52 | # Handle events 53 | if 'event' in data: 54 | if data['event'] == 'info': 55 | pass # ignore info messages 56 | elif data['event'] == 'auth': 57 | if data['status'] == 'OK': 58 | print('API authentication successful') 59 | else: 60 | print(data['status']) 61 | # Capture all subscribed channels 62 | elif data['event'] == 'subscribed': 63 | if data['channel'] == 'ticker': 64 | channels[data['chanId']] = [data['channel'], data['pair']] 65 | # Handle channel data 66 | else: 67 | chan_id = data[0] 68 | if chan_id in channels: 69 | if 'ticker' in channels[chan_id]: # if channel is for ticker 70 | if data[1] == 'hb': 71 | pass 72 | else: 73 | # parse ticker and save to memory 74 | sym = channels[chan_id][1] 75 | ticker_raw = data[1] 76 | ticker_parsed = { 77 | 'bid': ticker_raw[0], 78 | 'ask': ticker_raw[2], 79 | 'last_price': ticker_raw[6], 80 | 'volume': ticker_raw[7], 81 | } 82 | tickers[sym] = ticker_parsed 83 | 84 | def on_error(ws, error): 85 | print(error) 86 | 87 | def on_close(ws): 88 | print('### API connection closed ###') 89 | os._exit(0) 90 | 91 | def on_open(ws): 92 | global API_KEY, API_SECRET 93 | def authenticate(): 94 | # Authenticate connection 95 | nonce = str(int(time.time() * 10000)) 96 | auth_string = 'AUTH' + nonce 97 | auth_sig = hmac.new(API_SECRET.encode(), auth_string.encode(), 98 | hashlib.sha384).hexdigest() 99 | 100 | payload = {'event': 'auth', 'apiKey': API_KEY, 'authSig': auth_sig, 101 | 'authPayload': auth_string, 'authNonce': nonce, 'dms': 4} 102 | ws.send(json.dumps(payload)) 103 | print('API connected') 104 | authenticate() 105 | sub_ticker = { 106 | 'event': 'subscribe', 107 | 'channel': 'ticker', 108 | 'symbol': "tBTCUSD" 109 | } 110 | ws.send(json.dumps(sub_ticker)) 111 | # start printing the ticker 112 | Thread(target=print_ticker).start() 113 | 114 | def connect_api(): 115 | global ws 116 | websocket.enableTrace(False) 117 | ws = websocket.WebSocketApp('wss://api.bitfinex.com/ws/2', 118 | on_message = on_message, 119 | on_error = on_error, 120 | on_close = on_close, 121 | on_open = on_open) 122 | ws.run_forever() 123 | 124 | # initialize api connection 125 | connect_api() 126 | -------------------------------------------------------------------------------- /ta_ema_cross/ta_ema_cross.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | from bfxhfindicators import EMA 5 | from threading import Thread 6 | 7 | BASE_URL = 'https://api.hitbtc.com' 8 | 9 | historic_window = 10 10 | symbols = [] 11 | candles = {} 12 | ema_values = {} 13 | ema_periods = [10,20] 14 | go_on = False 15 | 16 | 17 | def import_candles(symbol): 18 | global candles 19 | # get candles 20 | resp = requests.get(BASE_URL + '/api/2/public/candles/%s?period=M15&limit=250' 21 | %(symbol)) 22 | raw_candles = json.loads(resp.content) 23 | # parse candles and save to memory 24 | parsed_candles = [] 25 | for raw_c in raw_candles: 26 | new_candle = { 27 | 'timestamp': raw_c['timestamp'], 28 | 'close': float(raw_c['close']), 29 | 'low': float(raw_c['min']), 30 | 'high': float(raw_c['max']) 31 | } 32 | parsed_candles.append(new_candle) 33 | candles[symbol] = parsed_candles[:] 34 | 35 | def show_progress(): 36 | global go_on 37 | #wait for symbols to load 38 | while True: 39 | time.sleep(0.2) 40 | print('Importing candles: %s/%s symbols loaded' 41 | %(len(candles), len(symbols)), end='\r') 42 | if len(candles) == len(symbols): # break when equal 43 | break 44 | go_on = True 45 | 46 | 47 | # get 20 USD symbols 48 | print('Retrieving the first 20 USD symbols') 49 | resp = requests.get(BASE_URL + '/api/2/public/symbol') 50 | all_sym = json.loads(resp.content) 51 | for x in all_sym: 52 | if 'USD' in x['id']: 53 | symbols.append(x['id']) 54 | if len(symbols) == 20: 55 | break 56 | print('Found (%s) symbols.' %(len(symbols))) 57 | 58 | # import candles for each symbol 59 | Thread(target=show_progress).start() # show progress 60 | for sym in symbols: 61 | Thread(target=import_candles, args=(sym,)).start() 62 | 63 | # wait until all candles are loaded 64 | while go_on == False: 65 | time.sleep(1) 66 | print('\nAll candles loaded.') 67 | 68 | # calculate EMA values 69 | print('Calculating EMA values and scanning for crosses...', end='', flush=True) 70 | for sym in symbols: 71 | for period in ema_periods: 72 | iEMA = EMA([period]) # define EMA object 73 | for candle in candles[sym]: 74 | iEMA.add(candle['close']) # add all close prices 75 | lst_ema = [] 76 | lst_ema.append(iEMA.v()) # add current EMA value 77 | for i in range(historic_window): 78 | # add historic EMA values 79 | lst_ema.append(iEMA.prev(i+1)) 80 | if sym not in ema_values: # add symbol key to dictionary 81 | ema_values[sym] = {} 82 | ema_values[sym][period] = lst_ema # save EMA values 83 | 84 | # identify EMA crosses 85 | ema_results = { 86 | 'cross-downs': [], 87 | 'cross-ups': [] 88 | } 89 | for sym in symbols: 90 | # get primary and secondary EMA lists, and reverse for oldest first 91 | ema_first = ema_values[sym][ema_periods[0]][:] 92 | ema_second = ema_values[sym][ema_periods[1]][:] 93 | ema_first.reverse() 94 | ema_second.reverse() 95 | 96 | # determine type of cross to look for 97 | if ema_first[0] > ema_second[0]: 98 | look_for = 'cross-down' 99 | elif ema_first[0] < ema_second[0]: 100 | look_for = 'cross-up' 101 | 102 | # filter out symbols that meet criteria 103 | for i in range(1, historic_window + 1): 104 | if look_for == 'cross-down': 105 | if ema_first[i] < ema_second[i]: 106 | # primary EMA has gone below secondary 107 | tmp = ema_results['cross-downs'] 108 | if sym not in tmp: 109 | tmp.append(sym) # update list 110 | ema_results['cross-downs'] = tmp # save list 111 | del tmp 112 | elif look_for == 'cross-up': 113 | if ema_first[i] > ema_second[i]: 114 | # primary EMA has gone above secondary 115 | tmp = ema_results['cross-ups'] 116 | if sym not in tmp: 117 | tmp.append(sym) # update list 118 | ema_results['cross-ups'] = tmp # save list 119 | del tmp 120 | print('done') 121 | 122 | # print results 123 | print('Primary EMA Period: %s' %(ema_periods[0])) 124 | print('Secondary EMA Period: %s\n' %(ema_periods[1])) 125 | print('EMA(%s) cross below EMA(%s):\n' %(ema_periods[0], ema_periods[1])) 126 | for x_down in ema_results['cross-downs']: 127 | print(x_down) 128 | print('\nEMA(%s) cross above EMA(%s):\n' %(ema_periods[0], ema_periods[1])) 129 | for x_up in ema_results['cross-ups']: 130 | print(x_up) 131 | -------------------------------------------------------------------------------- /bitfinex_websocket_multi/bitfinex_websocket_multi.py: -------------------------------------------------------------------------------- 1 | import websocket 2 | import requests 3 | import hashlib 4 | import hmac 5 | import json 6 | import time 7 | import os 8 | from threading import Thread 9 | 10 | # GLOBAL VARIABLES 11 | channels = {0: 'Bitfinex'} 12 | symbols = [] 13 | tickers = {} # [market][bid/ask] 14 | candles = {} # [market][candle1,candle2...] 15 | 16 | def update_tickers(data): 17 | global tickers 18 | sym = channels[data[0]][1] 19 | ticker_raw = data[1] 20 | ticker_parsed = { 21 | 'bid': ticker_raw[0], 22 | 'ask': ticker_raw[2], 23 | 'last_price': ticker_raw[6], 24 | 'volume': ticker_raw[7], 25 | } 26 | tickers[sym] = ticker_parsed 27 | 28 | def update_candles(data): 29 | global candles 30 | def truncate_market(str_data): 31 | # Get market symbol from channel key 32 | col1 = str_data.find(':t') 33 | res = str_data[col1+2:] 34 | return res 35 | def parse_candle(lst_data): 36 | # Get candle dictionary from list 37 | return { 38 | 'mts': lst_data[0], 39 | 'open': lst_data[1], 40 | 'close': lst_data[2], 41 | 'high': lst_data[3], 42 | 'low': lst_data[4], 43 | 'vol': lst_data[5] 44 | } 45 | 46 | market = truncate_market(channels[data[0]][1]) 47 | # Identify snapshot (list=snapshot, int=update) 48 | if type(data[1][0]) is list: 49 | lst_candles = [] 50 | for raw_candle in data[1]: 51 | candle = parse_candle(raw_candle) 52 | lst_candles.append(candle) 53 | candles[market] = lst_candles 54 | elif type(data[1][0]) is int: 55 | raw_candle = data[1] 56 | lst_candles = candles[market] 57 | candle = parse_candle(raw_candle) 58 | if candle['mts'] == candles[market][0]['mts']: 59 | # Update latest candle 60 | lst_candles[0] = candle 61 | candles[market] = lst_candles 62 | elif candle['mts'] > candles[market][0]['mts']: 63 | # Insert new (latest) candle 64 | lst_candles.insert(0, candle) 65 | candles[market] = lst_candles 66 | 67 | def print_details(): 68 | # interactive function to view tickers and candles 69 | while len(tickers) == 0 or len(candles) == 0: 70 | # wait for tickers to populate 71 | time.sleep(1) 72 | print('Tickers and candles loaded. You may query a symbol now.') 73 | while True: 74 | symbol = input() 75 | symbol = symbol.upper() 76 | if symbol not in symbols: 77 | print('%s not in list of symbols.' %(symbol)) 78 | continue 79 | details = tickers[symbol] 80 | print('%s: Bid: %s, Ask: %s, Last Price: %s, Volume: %s'\ 81 | %(symbol, details['bid'], details['ask'],\ 82 | details['last_price'], details['volume'])) 83 | print('%s: currently has (%s) candles, latest candle: %s'\ 84 | %(symbol, len(candles[symbol]), str(candles[symbol][0]))) 85 | 86 | def on_message(ws, message): 87 | global channels, balances, tickers 88 | data = json.loads(message) 89 | # Handle events 90 | if 'event' in data: 91 | if data['event'] == 'info': 92 | pass # ignore info messages 93 | elif data['event'] == 'auth': 94 | if data['status'] == 'OK': 95 | print('API authentication successful') 96 | else: 97 | print(data['status']) 98 | # Capture all subscribed channels 99 | elif data['event'] == 'subscribed': 100 | if data['channel'] == 'ticker': 101 | channels[data['chanId']] = [data['channel'], data['pair']] 102 | elif data['channel'] == 'candles': 103 | channels[data['chanId']] = [data['channel'], data['key']] 104 | # Handle channel data 105 | else: 106 | chan_id = data[0] 107 | if chan_id in channels: 108 | if 'ticker' in channels[chan_id]: 109 | # if channel is for ticker 110 | if data[1] == 'hb': 111 | # Ignore heartbeat messages 112 | pass 113 | else: 114 | # parse ticker and save to memory 115 | Thread(target=update_tickers, args=(data,)).start() 116 | elif 'candles' in channels[chan_id]: 117 | # if channel is for candles 118 | if data[1] == 'hb': 119 | # Ignore heartbeat messages 120 | pass 121 | else: 122 | # parse candle update and save to memory 123 | Thread(target=update_candles, args=(data,)).start() 124 | 125 | def on_error(ws, error): 126 | print(error) 127 | 128 | def on_close(ws): 129 | print('### API connection closed ###') 130 | os._exit(0) 131 | 132 | def on_open(ws): 133 | print('API connected') 134 | for sym in symbols: 135 | sub_tickers = { 136 | 'event': 'subscribe', 137 | 'channel': 'ticker', 138 | 'symbol': sym 139 | } 140 | ws.send(json.dumps(sub_tickers)) 141 | sub_candles = { 142 | 'event': 'subscribe', 143 | 'channel': 'candles', 144 | 'key': 'trade:15m:t' + sym 145 | } 146 | ws.send(json.dumps(sub_candles)) 147 | # start printing the books 148 | Thread(target=print_details).start() 149 | 150 | def connect_api(): 151 | global ws 152 | websocket.enableTrace(False) 153 | ws = websocket.WebSocketApp('wss://api.bitfinex.com/ws/2', 154 | on_message = on_message, 155 | on_error = on_error, 156 | on_close = on_close, 157 | on_open = on_open) 158 | ws.run_forever() 159 | 160 | # load USD tickers 161 | res = requests.get("https://api.bitfinex.com/v1/symbols") 162 | all_sym = json.loads(res.content) 163 | for x in all_sym: 164 | if "usd" in x: 165 | symbols.append(x.upper()) 166 | print('Found (%s) USD symbols' %(len(symbols))) 167 | 168 | # initialize api connection 169 | connect_api() 170 | -------------------------------------------------------------------------------- /bitmex_auto_sl_tp/bitmex_auto_sl_tp.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import hmac 3 | import hashlib 4 | import json 5 | import time 6 | import urllib 7 | import os 8 | from threading import Thread 9 | 10 | BASE_URL = 'https://www.bitmex.com/api/v1/' 11 | 12 | API_KEY = '' 13 | API_SECRET = '' 14 | 15 | STOP_LOSS = 0.004 # i.e. default = 0.4% 16 | TAKE_PROFIT = 0.01 # i.e. default = 1% 17 | ENABLE_STOP_LOSS = True 18 | ENABLE_TAKE_PROFIT = True 19 | 20 | trade_symbols = ["XBTUSD", "ETHUSD"] 21 | positions = { 22 | 'XBTUSD': {'qty': 0}, 23 | 'ETHUSD': {'qty': 0} 24 | } 25 | orders = {'XBTUSD': [], 'ETHUSD': []} 26 | 27 | def rounded_price(number, symbol): 28 | if symbol == "XBTUSD": 29 | return round(number * 2.0) / 2.0 30 | elif symbol == "ETHUSD": 31 | return round(number * 20.0) / 20.0 32 | 33 | def auth_req_get(endpoint, query): 34 | # make authenticated GET requests 35 | global API_SECRET, API_KEY 36 | bln_pass = False 37 | path = BASE_URL + endpoint 38 | e_path = '/api/v1/' + endpoint # path for encrypted message 39 | if query != '': # add query to paths 40 | path = path + "?" + query 41 | e_path = e_path + "?" + query 42 | expires = int(round(time.time()) + 10) 43 | message = str ('GET' + e_path + str(expires)) 44 | signature = hmac.new(bytes(API_SECRET, 'utf8'),\ 45 | bytes(message,'utf8'), digestmod=hashlib.sha256)\ 46 | .hexdigest() 47 | request_headers = { 48 | 'api-expires' : str(expires), 49 | 'api-key' : API_KEY, 50 | 'api-signature' : signature, 51 | } 52 | while True: 53 | resp = requests.get(path, headers=request_headers) 54 | if resp.status_code == 200: 55 | return json.loads(resp.content) 56 | else: 57 | print(resp.status_code) 58 | print(resp.content) 59 | data = json.loads(resp.content) 60 | # ignore expires message from BitMEX (another fix can be implemented), otherwise stop bot 61 | if "error" in data: 62 | if "message" in data["error"]: 63 | if "expired" in data["error"]["message"]: 64 | bln_pass = True 65 | if bln_pass == False: 66 | os._exit(1) 67 | time.sleep(1) 68 | 69 | def auth_req_post(endpoint, payload): 70 | # make authenticated POST requests 71 | global API_KEY, API_SECRET 72 | bln_pass = False 73 | path = BASE_URL + endpoint 74 | e_path = '/api/v1/' + endpoint # path for encrypted message 75 | expires = int(round(time.time()) + 10) 76 | payload2 = str(payload.replace(' ', '')) # remove extra spaces 77 | message = str ('POST' + e_path + str(expires) + payload2) 78 | signature = hmac.new(bytes(API_SECRET, 'utf8'),\ 79 | bytes(message,'utf8'), digestmod=hashlib.sha256).\ 80 | hexdigest() 81 | request_headers = { 82 | 'Content-type' : 'application/json', 83 | 'api-expires' : str(expires), 84 | 'api-key' : API_KEY, 85 | 'api-signature' : signature, 86 | } 87 | resp = requests.post(path, headers=request_headers, data=payload2) 88 | if resp.status_code == 200: 89 | return resp 90 | else: 91 | print(resp.status_code) 92 | print(resp.content) 93 | data = json.loads(resp.content) 94 | # ignore expires message from BitMEX (another fix can be implemented), otherwise stop bot 95 | if "error" in data: 96 | if "message" in data["error"]: 97 | if "expired" in data["error"]["message"]: 98 | bln_pass = True 99 | if bln_pass == False: 100 | os._exit(1) 101 | 102 | def place_order(symbol, side, qty, ref_price, stop=False): 103 | if side == 'Buy': # it means we are SHORT 104 | if stop == True: 105 | # stop loss above entry 106 | price = ref_price * (1+STOP_LOSS) 107 | else: 108 | # take profit below entry 109 | price = ref_price * (1-TAKE_PROFIT) 110 | elif side == 'Sell': # it means we are LONG 111 | if stop == True: 112 | # stop loss below entry 113 | price = ref_price * (1-STOP_LOSS) 114 | else: 115 | # take profit above entry 116 | price = ref_price * (1+TAKE_PROFIT) 117 | order_details = { 118 | 'symbol': symbol, 119 | 'side': side, 120 | 'orderQty': qty, 121 | } 122 | if stop == True: # add extra info for stop orders 123 | order_details['ordType'] = 'Stop' 124 | order_details['stopPx'] = rounded_price(price, symbol) 125 | else: 126 | order_details['price'] = rounded_price(price, symbol) 127 | result = auth_req_post('order', json.dumps(order_details)) 128 | if result.status_code == 200: 129 | print('Order placed successfully.') 130 | get_positions() 131 | get_orders() 132 | else: 133 | print(result.content) 134 | 135 | def get_positions(): 136 | global positions 137 | # load positions in memory 138 | req = auth_req_get('position', '') 139 | for pos in req: 140 | sym = pos['symbol'] 141 | positions[sym]['qty'] = pos['currentQty'] 142 | positions[sym]['entry_price'] = pos['avgEntryPrice'] 143 | 144 | def get_orders(): 145 | global trade_symbols, orders 146 | # load open orders in memory 147 | query = "filter=" + urllib.parse.quote_plus('{"open":true}') 148 | req = auth_req_get('order', query) 149 | for sym in trade_symbols: 150 | lst_orders = [] 151 | for order in req: 152 | if order['symbol'] == sym: 153 | ord_details = { 154 | 'side': order['side'], 155 | 'o_id': order['orderID'], 156 | 'type': order['ordType'] 157 | } 158 | lst_orders.append(ord_details) 159 | orders[sym] = lst_orders 160 | 161 | def maintain_positions(): 162 | global positions 163 | print('Positions are now loaded...') 164 | while True: 165 | get_positions() 166 | time.sleep(10) 167 | 168 | def maintain_orders(): 169 | global orders 170 | print('Orders are now loaded...') 171 | while True: 172 | get_orders() 173 | time.sleep(10) 174 | 175 | def cover_positions(): 176 | global positions, orders, STOP_LOSS, TAKE_PROFIT 177 | print('Actively scanning for open positions now.') 178 | while True: 179 | # cover open positions that do not have stop loss / take profit 180 | for sym in positions: 181 | if positions[sym]['qty'] > 0: # long position entered 182 | price = positions[sym]['entry_price'] 183 | has_tp = False 184 | has_sl = False 185 | for od in orders[sym]: 186 | if od['side'] == 'Sell' and od['type'] == 'Stop': 187 | has_sl = True # found stop loss 188 | elif od['side'] == 'Sell': 189 | has_tp = True # found take profit 190 | if has_sl == False: 191 | if ENABLE_STOP_LOSS == True: 192 | place_order(sym, 'Sell', abs(positions[sym]['qty']),\ 193 | price, True) 194 | if has_tp == False: 195 | if ENABLE_TAKE_PROFIT == True: 196 | place_order(sym, 'Sell', abs(positions[sym]['qty']),\ 197 | price) 198 | elif positions[sym]['qty'] < 0: # short position entered 199 | price = positions[sym]['entry_price'] 200 | has_tp = False 201 | has_sl = False 202 | for od in orders[sym]: 203 | if od['side'] == 'Buy' and od['type'] == 'Stop': 204 | has_sl = True # found stop loss 205 | elif od['side'] == 'Buy': 206 | has_tp = True # found take profit 207 | if has_sl == False: 208 | if ENABLE_STOP_LOSS == True: 209 | place_order(sym, 'Buy', abs(positions[sym]['qty']),\ 210 | price, True) 211 | if has_tp == False: 212 | if ENABLE_TAKE_PROFIT == True: 213 | place_order(sym, 'Buy', abs(positions[sym]['qty']),\ 214 | price) 215 | time.sleep(1) 216 | 217 | if __name__ == '__main__': 218 | # start main threads 219 | Thread(target=maintain_positions).start() 220 | Thread(target=maintain_orders).start() 221 | Thread(target=cover_positions).start() 222 | --------------------------------------------------------------------------------