Created
November 18, 2017 02:56
-
-
Save benjaminchodroff/ca037379060de0f3b5f77e643be5215d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": true | |
}, | |
"outputs": [], | |
"source": [ | |
"# Parameters\n", | |
"# Modify secrets.sample.json with your Bittrex key and secret, and rename to secrets.json\n", | |
"flip=0.01\n", | |
"waittrade=43200 # 12 hours to wait for a trade to complete\n", | |
"marketcharge=0.0025\n", | |
"market=\"ETH-NEO\"" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Enable Logging\n", | |
"import logging\n", | |
"\n", | |
"# create logger\n", | |
"logger = logging.getLogger('BittrexFlipper')\n", | |
"logger.setLevel(logging.DEBUG)\n", | |
"# create file handler which logs even debug messages\n", | |
"fh = logging.FileHandler('flipper.log')\n", | |
"fh.setLevel(logging.DEBUG)\n", | |
"# create console handler\n", | |
"ch = logging.StreamHandler()\n", | |
"ch.setLevel(logging.DEBUG)\n", | |
"# create formatter and add it to the handlers\n", | |
"formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')\n", | |
"fh.setFormatter(formatter)\n", | |
"ch.setFormatter(formatter)\n", | |
"# add the handlers to the logger\n", | |
"logger.addHandler(fh)\n", | |
"logger.addHandler(ch)\n", | |
"logger.info(\"Starting BittrexFlipper\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Trade Initialization\n", | |
"from bittrex import *\n", | |
"import json\n", | |
"from time import sleep\n", | |
"import sys\n", | |
"from datetime import datetime\n", | |
"from dateutil import parser\n", | |
"\n", | |
"logger.info(\"Logging into Bittrex\")\n", | |
"try:\n", | |
" with open(\"secrets.json\") as secrets_file:\n", | |
" secrets = json.load(secrets_file)\n", | |
" secrets_file.close()\n", | |
" mybit = Bittrex(secrets['key'], secrets['secret'])\n", | |
" # Confirm we are logged in by pulling all balances\n", | |
" balances=mybit.get_balances()\n", | |
" if (balances['success']==False):\n", | |
" raise ValueError(balances['message'])\n", | |
"except Exception as e:\n", | |
" logger.error(\"Error while trying to log into Bittrex. Exiting.\")\n", | |
" logger.debug(e)\n", | |
" sys.exit(1)\n", | |
"logger.info(\"Logged into Bittrex successfully\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"logger.info(\"Initializing trading conditions\")\n", | |
"logger.info(\"flip=\"+str(flip))\n", | |
"logger.info(\"market_charge=\"+str(marketcharge))\n", | |
"logger.info(\"market=\"+market)\n", | |
"openorders=mybit.get_open_orders(market)['result']\n", | |
"neoquantity=mybit.get_balance('NEO')['result']['Available']\n", | |
"logger.info(\"NEO quantity=\"+str(neoquantity))\n", | |
"ethquantity=mybit.get_balance('ETH')['result']['Available']\n", | |
"logger.info(\"ETH quantity=\"+str(ethquantity))\n", | |
"#mylastorderlimit=mybit.get_order_history(market)['result'][0]['Limit']\n", | |
"#logger.info(\"mylastorderlimit=\"+str(mylastorderlimit))\n", | |
"lastneosell=mybit.get_orderbook(market,depth_type='sell',depth=1)['result'][0]['Rate']\n", | |
"logger.info(\"lastneosell=\"+str(lastneosell))\n", | |
"lastneobuy=mybit.get_orderbook(market,depth_type='buy',depth=1)['result'][0]['Rate']\n", | |
"logger.info(\"lastneobuy=\"+str(lastneobuy))\n", | |
"\n", | |
"# startbuy is a semaphore to control whether we create a buy_limit first if True. Default to False.\n", | |
"startbuy=False\n", | |
"\n", | |
"# existingorder is a semaphore to control whether we have an existing order at startup. Default to False\n", | |
"existingorder=False\n", | |
"orderuuid=\"\"\n", | |
"\n", | |
"# if we have a single previous open position, resume from here:\n", | |
"if (len(openorders)==1):\n", | |
" existingorder=True\n", | |
" orderuuid=openorders[0]['OrderUuid']\n", | |
" initialrate=openorders[0]['Limit']\n", | |
" # If sell_limit - find order uuid, set rate=order['Limit'], wait for bid to close, and then resume loop here\n", | |
" if (openorders[0]['OrderType']=='LIMIT_SELL'):\n", | |
" logger.warn(\"There is an existing LIMIT_SELL order uuid=\"+orderuuid)\n", | |
" startbuy=False\n", | |
" # If ask_limit - find order uuid, set rate=order['Limit'], and wait for ask to close, and then resume loop here\n", | |
" elif (openorders[0]['OrderType']=='LIMIT_BUY'):\n", | |
" logger.warn(\"There is an existing LIMIT_BUY order uuid=\"+orderuuid)\n", | |
" startbuy=True\n", | |
" else:\n", | |
" logger.error(\"There is an unexpected open order that is neither LIMIT_SELL or LIMIT_BUY. Exiting.\")\n", | |
" sys.exit(2)\n", | |
"elif (len(openorders)==0):\n", | |
" # Determine if we are buying or selling first based on which coin we have more of\n", | |
" if(neoquantity>ethquantity):\n", | |
" logger.info(\"Found more NEO than ETH. Selling NEO for ETH.\")\n", | |
" startbuy=False\n", | |
" # Sell high\n", | |
" # Determine the initial rate to sell at based on the market (we are long NEO)\n", | |
" initialrate=lastneosell\n", | |
" else:\n", | |
" logger.info(\"Found less NEO than ETH. Buying NEO with ETH.\")\n", | |
" logger.error(\"The bittrex python api no longer supports get_order_history() and we cannot proceed safely. Manually create an open order and start flipper again.\")\n", | |
" sys.exit(100)\n", | |
" # The rest of the code is dead until this bug is fixed in the bittrex python library \n", | |
" startbuy=True\n", | |
" # Buy low\n", | |
" # Determine the initial rate to buy at based on the smaller of either the current rate or the last order we closed (long NEO)\n", | |
" if(lastneobuy>mylastorderlimit):\n", | |
" initialrate=mylastorderlimit\n", | |
" else:\n", | |
" initialrate=lastneobuy\n", | |
"#if we have more than one previous open positions, error out\n", | |
"else:\n", | |
" logger.error(\"There are multiple open orders. Exiting.\")\n", | |
" sys.exit(1)\n", | |
"\n", | |
"logger.info(\"startbuy=\"+str(startbuy))\n", | |
"logger.info(\"existingorder=\"+str(existingorder))\n", | |
"logger.info(\"orderuuid=\"+orderuuid)\n", | |
"logger.info(\"initialrate=\"+str(initialrate))\n", | |
"rate=initialrate" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"collapsed": true | |
}, | |
"outputs": [], | |
"source": [ | |
"# Helper Functions\n", | |
"def spinning_cursor():\n", | |
" while True:\n", | |
" for cursor in '|/-\\\\':\n", | |
" yield cursor\n", | |
"\n", | |
"spinner = spinning_cursor()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"# Main Trade Loop\n", | |
"logger.info(\"Starting Main Trade Loop\")\n", | |
"while True:\n", | |
" # Sell Limit Order\n", | |
" if not startbuy:\n", | |
" # Keep trying to sell until it goes through\n", | |
" selling=True\n", | |
" while selling:\n", | |
" logger.info(\"Sell Limit Order\")\n", | |
" if not existingorder:\n", | |
" rate=rate*(1+flip+marketcharge)\n", | |
" logger.debug(\"rate=rate*(1+flip+marketcharge)=rate*(1+\"+str(flip+marketcharge)+\")=\"+str(rate))\n", | |
" while True:\n", | |
" try:\n", | |
" neoquantity=float(mybit.get_balance('NEO')['result']['Available'])\n", | |
" logger.info(\"Placing Sell Limit on market=\"+market+\"for neoquantity=\"+str(neoquantity)+\" NEO at rate=\"+str(rate)) \n", | |
" sleep(3)\n", | |
" sellresult=mybit.sell_limit(market,neoquantity,rate)\n", | |
" orderuuid=sellresult['result']['uuid']\n", | |
" break\n", | |
" except Exception as e:\n", | |
" logger.error(\"Exception while trying to place order, trying again\")\n", | |
" logger.debug(e)\n", | |
" pass\n", | |
" else:\n", | |
" logger.info(\"Resuming existing order\")\n", | |
" # Always clear the existingorder semaphore if initialized\n", | |
" existingorder=False\n", | |
" order_opened=mybit.get_order(orderuuid)['result']['Opened']\n", | |
" logger.info(\"Found Order UUID=\"+orderuuid+\" opened on \"+order_opened+\". Waiting for close.\")\n", | |
" \n", | |
" # Wait until trade close loop\n", | |
" while True:\n", | |
" try:\n", | |
" # Initialize our wait to 0 seconds timedelta\n", | |
" waited=datetime.utcnow()-datetime.utcnow()\n", | |
" while mybit.get_order(orderuuid)['result']['IsOpen']:\n", | |
" selling=True\n", | |
" waited=datetime.utcnow()-parser.parse(order_opened)\n", | |
" if(waited.total_seconds()>waittrade):\n", | |
" logger.warn(\"Waited longer than \"+str(waittrade)+\" seconds, cancelling order \"+orderuuid)\n", | |
" try:\n", | |
" ordercancel=mybit.cancel(uuid=orderuuid)\n", | |
" if(ordercancel['success']!=True):\n", | |
" raise ValueError(ordercancel['message'])\n", | |
" else:\n", | |
" logger.info(\"Successfully cancelled order \"+orderuuid)\n", | |
" lastneosell=mybit.get_orderbook(market,depth_type='sell',depth=1)['result'][0]['Rate']\n", | |
" rate=lastneosell\n", | |
" # Note - the rate will increase by flip+marketcharge on the next pass\n", | |
" logger.info(\"Adjusting new rate to lastneosell=\"+str(rate))\n", | |
" # Ensure selling is True to try again\n", | |
" selling=True\n", | |
" # break out of the wait for trade close loop\n", | |
" break\n", | |
" except Exception as e:\n", | |
" logger.error(\"Exception while attempting to cancel order \"+orderuuid+\" and setting the new rate. Exiting!\")\n", | |
" logger.debug(e)\n", | |
" sys.exit(3)\n", | |
" else:\n", | |
" sys.stdout.write(spinner.next())\n", | |
" sys.stdout.flush()\n", | |
" sleep(3)\n", | |
" sys.stdout.write('\\b')\n", | |
" # Set selling to false in assumption that it may be complete\n", | |
" selling=False\n", | |
" print(\"\")\n", | |
" # If we see that the order was cancelled when we did not, exit out\n", | |
" if (mybit.get_order(orderuuid)['result']['CancelInitiated']) and not selling:\n", | |
" logger.error(\"Order cancelled unexpectedly! Exiting.\")\n", | |
" sys.exit(2)\n", | |
" elif not selling:\n", | |
" logger.info(\"Sell Order \"+orderuuid+\" closed successfully!\")\n", | |
" logger.debug(\"Waited \"+str(waited)+\" to close order \"+orderuuid)\n", | |
" # Break out of the wait loop if we successfully make it here\n", | |
" break\n", | |
" except Exception as e:\n", | |
" logger.error(\"Exception while trying to get order status, trying again\")\n", | |
" logger.debug(e)\n", | |
" # Reattempt\n", | |
" pass\n", | |
"\n", | |
" # Buy Limit Order\n", | |
" # Always clear the startbuy semaphore if initialized\n", | |
" startbuy=False\n", | |
" print(\"INFO: Buy Limit Order\")\n", | |
" if not existingorder:\n", | |
" rate=rate*(1-flip-marketcharge)\n", | |
" logger.debug(\"rate=rate*(1-flip-marketcharge)=rate*(1-\"+str(flip-marketcharge)+\")=\"+str(rate))\n", | |
" while True:\n", | |
" try:\n", | |
" ethquantity=float(mybit.get_balance('ETH')['result']['Available'])\n", | |
" buyquantity=(ethquantity*(1-marketcharge))/rate\n", | |
" logger.info(\"Placing Buy Limit on market=\"+market+\" for buyquantity=\"+str(buyquantity)+\" NEO at rate=\"+str(rate))\n", | |
" sleep(3)\n", | |
" buyresult=mybit.buy_limit(market,buyquantity,rate)\n", | |
" orderuuid=buyresult['result']['uuid']\n", | |
" break\n", | |
" except Exception as e:\n", | |
" logger.error(\"Exception while trying to place the order, trying again\")\n", | |
" logger.debug(e)\n", | |
" pass\n", | |
" else:\n", | |
" print(\"INFO: Resuming existing order\")\n", | |
" # Always clear the existingorder semaphore if initialized\n", | |
" existingorder=False\n", | |
" order_opened=mybit.get_order(orderuuid)['result']['Opened']\n", | |
" logger.info(\"Found Order UUID=\"+orderuuid+\" opened on \"+order_opened+\". Waiting for close.\")\n", | |
" while True:\n", | |
" try:\n", | |
" while mybit.get_order(orderuuid)['result']['IsOpen']:\n", | |
" sys.stdout.write(spinner.next())\n", | |
" sys.stdout.flush()\n", | |
" sleep(3)\n", | |
" sys.stdout.write('\\b')\n", | |
" print(\"\")\n", | |
" if (mybit.get_order(orderuuid)['result']['CancelInitiated']):\n", | |
" logger.error(\"Order cancelled unexpectedly! Exiting.\")\n", | |
" sys.exit(2)\n", | |
" else:\n", | |
" logger.info(\"Buy Order closed!\")\n", | |
" break\n", | |
" except Exception as e:\n", | |
" logger.error(\"Exception while trying to get order status, trying again\")\n", | |
" logger.debug(e)\n", | |
" pass" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 2", | |
"language": "python", | |
"name": "python2" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 2 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython2", | |
"version": "2.7.13" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment