Skip to content

Instantly share code, notes, and snippets.

@benjaminchodroff
Created November 18, 2017 02:56
Show Gist options
  • Save benjaminchodroff/ca037379060de0f3b5f77e643be5215d to your computer and use it in GitHub Desktop.
Save benjaminchodroff/ca037379060de0f3b5f77e643be5215d to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"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