├── LICENSE ├── .gitignore └── main.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import requests 3 | import hashlib 4 | import hmac 5 | 6 | TICK_INTERVAL = 60 # seconds 7 | API_KEY = 'my-api-key' 8 | API_SECRET_KEY = b'my-secret-key' 9 | 10 | 11 | def main(): 12 | print('Starting trader bot...') 13 | 14 | while True: 15 | start = time.time() 16 | tick() 17 | end = time.time() 18 | 19 | # Sleep the thread if needed 20 | if end - start < TICK_INTERVAL: 21 | time.sleep(TICK_INTERVAL - (end - start)) 22 | 23 | 24 | def tick(): 25 | print('Running routine') 26 | 27 | market_summaries = simple_request('https://bittrex.com/api/v1.1/public/getmarketsummaries') 28 | for summary in market_summaries['result']: 29 | market = summary['MarketName'] 30 | day_close = summary['PrevDay'] 31 | last = summary['Last'] 32 | 33 | if day_close > 0: 34 | percent_chg = ((last / day_close) - 1) * 100 35 | else: 36 | print('day_close zero for ' + market) 37 | 38 | print(market + ' changed ' + str(percent_chg)) 39 | 40 | if 40 < percent_chg < 60: 41 | # Fomo strikes! Let's buy some 42 | if has_open_order(market, 'LIMIT_BUY'): 43 | print('Order already opened to buy 5 ' + market) 44 | else: 45 | print('Purchasing 5 units of ' + market + ' for ' + str(format_float(last))) 46 | res = buy_limit(market, 5, last) 47 | print(res) 48 | 49 | if percent_chg < -20: 50 | # Do we have any to sell? 51 | balance_res = get_balance_from_market(market) 52 | current_balance = balance_res['result']['Available'] 53 | 54 | if current_balance > 5: 55 | # Ship is sinking, get out! 56 | if has_open_order(market, 'LIMIT_SELL'): 57 | print('Order already opened to sell 5 ' + market) 58 | else: 59 | print('Selling 5 units of ' + market + ' for ' + str(format_float(last))) 60 | res = sell_limit(market, 5, last) 61 | print(res) 62 | else: 63 | print('Not enough ' + market + ' to open a sell order') 64 | 65 | 66 | def buy_limit(market, quantity, rate): 67 | url = 'https://bittrex.com/api/v1.1/market/buylimit?apikey=' + API_KEY + '&market=' + market + '&quantity=' + str(quantity) + '&rate=' + format_float(rate) 68 | return signed_request(url) 69 | 70 | 71 | def sell_limit(market, quantity, rate): 72 | url = 'https://bittrex.com/api/v1.1/market/selllimit?apikey=' + API_KEY + '&market=' + market + '&quantity=' + str(quantity) + '&rate=' + format_float(rate) 73 | return signed_request(url) 74 | 75 | 76 | def get_balance_from_market(market_type): 77 | markets_res = simple_request('https://bittrex.com/api/v1.1/public/getmarkets') 78 | markets = markets_res['result'] 79 | for market in markets: 80 | if market['MarketName'] == market_type: 81 | return get_balance(market['MarketCurrency']) 82 | 83 | # Return a fake response of 0 if not found 84 | return {'result': {'Available': 0}} 85 | 86 | 87 | def get_balance(currency): 88 | url = 'https://bittrex.com/api/v1.1/account/getbalance?apikey=' + API_KEY + '¤cy=' + currency 89 | res = signed_request(url) 90 | 91 | if res['result'] is not None and len(res['result']) > 0: 92 | return res 93 | 94 | # If there are no results, than your balance is 0 95 | return {'result': {'Available': 0}} 96 | 97 | 98 | def get_open_orders(market): 99 | url = 'https://bittrex.com/api/v1.1/market/getopenorders?apikey=' + API_KEY + '&market=' + market 100 | return signed_request(url) 101 | 102 | 103 | def has_open_order(market, order_type): 104 | orders_res = get_open_orders(market) 105 | orders = orders_res['result'] 106 | 107 | if orders is None or len(orders) == 0: 108 | return False 109 | 110 | # Check all orders for a LIMIT_BUY 111 | for order in orders: 112 | if order['OrderType'] == order_type: 113 | return True 114 | 115 | return False 116 | 117 | 118 | def signed_request(url): 119 | now = time.time() 120 | url += '&nonce=' + str(now) 121 | signed = hmac.new(API_SECRET_KEY, url.encode('utf-8'), hashlib.sha512).hexdigest() 122 | headers = {'apisign': signed} 123 | r = requests.get(url, headers=headers) 124 | return r.json() 125 | 126 | 127 | def simple_request(url): 128 | r = requests.get(url) 129 | return r.json() 130 | 131 | 132 | def format_float(f): 133 | return "%.8f" % f 134 | 135 | 136 | if __name__ == "__main__": 137 | main() 138 | 139 | --------------------------------------------------------------------------------