class CoinbaseExchange(Exchange): def __init__(self, settings): super(CoinbaseExchange, self).__init__(settings) self._trading = CoinbaseAccount(api_key = settings['COINBASE']['api']['key']) ''' How much can you buy a coin for, fee included for the exchange ''' def _buy_price(self): price = self._trading.buy_price() return price ''' How much can you get if you sell a coin, fee subtracted ''' def _sell_price(self): price = self._trading.sell_price() return price
class CoinBaseLibraryTests(unittest.TestCase): def setUp(self): self.account = CoinbaseAccount(oauth2_credentials=TEMP_CREDENTIALS) @httprettified def test_retrieve_balance(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/account/balance", body='''{"amount":"0.00000000","currency":"BTC"}''', content_type='text/json') this(self.account.balance).should.equal(0.0) this(self.account.balance.currency).should.equal('BTC') #TODO: Switch to decimals #this(self.account.balance).should.equal(CoinbaseAmount('0.00000000', 'USD')) #this(self.account.balance.currency).should.equal(CoinbaseAmount('0.00000000', 'USD').currency) @httprettified def test_receive_addresses(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/account/receive_address", body='''{"address" : "1DX9ECEF3FbGUtzzoQhDT8CG3nLUEA2FJt"}''', content_type='text/json') this(self.account.receive_address).should.equal(u'1DX9ECEF3FbGUtzzoQhDT8CG3nLUEA2FJt') @httprettified def test_contacts(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/contacts", body='''{"contacts":[{"contact":{"email":"*****@*****.**"}}],"total_count":1,"num_pages":1,"current_page":1}''', content_type='text/json') this(self.account.contacts).should.equal([{u'email': u'*****@*****.**'}]) @httprettified def test_buy_price_1(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/prices/buy?qty=1", body='''{"amount":"63.31","currency":"USD"}''', content_type='text/json') buy_price_1 = self.account.buy_price(1) this(buy_price_1).should.be.an(float) this(buy_price_1).should.be.lower_than(100) this(buy_price_1.currency).should.equal('USD') @httprettified def test_buy_price_2(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/prices/buy?qty=10", body='''{"amount":"633.25","currency":"USD"}''', content_type='text/json') buy_price_10 = self.account.buy_price(10) this(buy_price_10).should.be.greater_than(100) @httprettified def test_sell_price(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/prices/sell?qty=1", body='''{"amount":"63.31","currency":"USD"}''', content_type='text/json') sell_price_1 = self.account.sell_price(1) this(sell_price_1).should.be.an(float) this(sell_price_1).should.be.lower_than(100) this(sell_price_1.currency).should.equal('USD') @httprettified def test_sell_price_10(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/prices/sell?qty=1", body='''{"amount":"630.31","currency":"USD"}''', content_type='text/json') sell_price_10 = self.account.sell_price(10) this(sell_price_10).should.be.greater_than(100) @httprettified def test_request_bitcoin(self): HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/transactions/request_money", body='''{"success":true,"transaction":{"id":"514e4c37802e1bf69100000e","created_at":"2013-03-23T17:43:35-07:00","hsh":null,"notes":"Testing","amount":{"amount":"1.00000000","currency":"BTC"},"request":true,"status":"pending","sender":{"id":"514e4c1c802e1bef9800001e","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"}}}''', content_type='text/json') new_request = self.account.request('*****@*****.**', 1, 'Testing') this(new_request.amount).should.equal(1) this(new_request.request).should.equal(True) this(new_request.sender.email).should.equal('*****@*****.**') this(new_request.recipient.email).should.equal('*****@*****.**') this(new_request.notes).should.equal('Testing') @httprettified def test_send_bitcoin(self): HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/transactions/send_money", body='''{"success":true,"transaction":{"id":"5158b227802669269c000009","created_at":"2013-03-31T15:01:11-07:00","hsh":null,"notes":"","amount":{"amount":"-0.10000000","currency":"BTC"},"request":false,"status":"pending","sender":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient_address":"15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP"}} ''', content_type='text/json') new_transaction_with_btc_address = self.account.send('15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP', amount=0.1) this(new_transaction_with_btc_address.amount).should.equal(-0.1) this(new_transaction_with_btc_address.request).should.equal(False) this(new_transaction_with_btc_address.sender.email).should.equal('*****@*****.**') this(new_transaction_with_btc_address.recipient).should.equal(None) this(new_transaction_with_btc_address.recipient_address).should.equal('15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP') HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/transactions/send_money", body='''{"success":true,"transaction":{"id":"5158b2920b974ea4cb000003","created_at":"2013-03-31T15:02:58-07:00","hsh":null,"notes":"","amount":{"amount":"-0.10000000","currency":"BTC"},"request":false,"status":"pending","sender":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"4efec8d7bedd320001000003","email":"*****@*****.**","name":"Brian Armstrong"},"recipient_address":"*****@*****.**"}} ''', content_type='text/json') new_transaction_with_email = self.account.send('*****@*****.**', amount=0.1) this(new_transaction_with_email.recipient.email).should.equal('*****@*****.**') @httprettified def test_transaction_list(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/transactions", body='''{"current_user":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"balance":{"amount":"0.00000000","currency":"BTC"},"total_count":4,"num_pages":1,"current_page":1,"transactions":[{"transaction":{"id":"514e4c37802e1bf69100000e","created_at":"2013-03-23T17:43:35-07:00","hsh":null,"notes":"Testing","amount":{"amount":"1.00000000","currency":"BTC"},"request":true,"status":"pending","sender":{"id":"514e4c1c802e1bef9800001e","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"}}},{"transaction":{"id":"514e4c1c802e1bef98000020","created_at":"2013-03-23T17:43:08-07:00","hsh":null,"notes":"Testing","amount":{"amount":"1.00000000","currency":"BTC"},"request":true,"status":"pending","sender":{"id":"514e4c1c802e1bef9800001e","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"}}},{"transaction":{"id":"514b9fb1b8377ee36500000d","created_at":"2013-03-21T17:02:57-07:00","hsh":"42dd65a18dbea0779f32021663e60b1fab8ee0f859db7172a078d4528e01c6c8","notes":"You gave me this a while ago. It's turning into a fair amount of cash and thought you might want it back :) Building something on your API this weekend. Take care!","amount":{"amount":"-1.00000000","currency":"BTC"},"request":false,"status":"complete","sender":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"4efec8d7bedd320001000003","email":"*****@*****.**","name":"Brian Armstrong"},"recipient_address":"*****@*****.**"}},{"transaction":{"id":"509e01cb12838e0200000224","created_at":"2012-11-09T23:27:07-08:00","hsh":"ac9b0ffbe36dbe12c5ca047a5bdf9cadca3c9b89b74751dff83b3ac863ccc0b3","notes":"","amount":{"amount":"1.00000000","currency":"BTC"},"request":false,"status":"complete","sender":{"id":"4efec8d7bedd320001000003","email":"*****@*****.**","name":"Brian Armstrong"},"recipient":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient_address":"*****@*****.**"}}]}''', content_type='text/json') transaction_list = self.account.transactions() this(transaction_list).should.be.an(list) @httprettified def test_getting_transaction(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/transactions/5158b227802669269c000009", body='''{"transaction":{"id":"5158b227802669269c000009","created_at":"2013-03-31T15:01:11-07:00","hsh":"223a404485c39173ab41f343439e59b53a5d6cba94a02501fc6c67eeca0d9d9e","notes":"","amount":{"amount":"-0.10000000","currency":"BTC"},"request":false,"status":"pending","sender":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient_address":"15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP"}}''', content_type='text/json') transaction = self.account.get_transaction('5158b227802669269c000009') this(transaction.status).should.equal('pending') this(transaction.amount).should.equal(-0.1) @httprettified def test_getting_user_details(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/users", body='''{"users":[{"user":{"id":"509f01da12837e0201100212","name":"New User","email":"*****@*****.**","time_zone":"Pacific Time (US & Canada)","native_currency":"USD","buy_level":1,"sell_level":1,"balance":{"amount":"1225.86084181","currency":"BTC"},"buy_limit":{"amount":"10.00000000","currency":"BTC"},"sell_limit":{"amount":"50.00000000","currency":"BTC"}}}]}''', content_type='text/json') user = self.account.get_user_details() this(user.id).should.equal("509f01da12837e0201100212") this(user.balance).should.equal(1225.86084181) @httprettified def test_creating_a_button(self): HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/buttons", body='''{"button": {"style": "buy_now_large", "code": "b123456783q812e381cd9d39a5783277", "name": "Test Button", "info_url": null, "text": "Pay With Bitcoin", "price": {"cents": 2000, "currency_iso": "USD"}, "include_email": false, "custom": "", "cancel_url": null, "auto_redirect": false, "success_url": null, "variable_price": false, "include_address": false, "callback_url": null, "type": "buy_now", "choose_price": false, "description": ""}, "success": true}''', content_type='text/json') button = self.account.create_button('Test Button', '20.00', 'USD') this(button.code).should.equal('b123456783q812e381cd9d39a5783277') this(button.name).should.equal('Test Button') this(button.price['cents']).should.equal(2000)
class Trader(object): _orderbookLock = threading.Lock() _executeLock = threading.Lock() _stopTradeLock = threading.Lock() stopTrade = False def __init__(self, api_key=None, oauth2_credentials=None, orderbook=None, logname="traderlog.txt"): if (api_key is None) and (oauth2_credentials is None): raise ValueError("api_key and oauth2_credentials cannot be None") if (api_key is not None) and (oauth2_credentials is not None): raise ValueError( "User Provided both api_key and oauth2_credentials, select one" ) # I might want to instead just use one or the other with _traderIdLock: global TRADER_ID self.traderid = TRADER_ID TRADER_ID += 1 self.orderbook = [] if orderbook is None else orderbook f = open(logname, 'w') f.close() self.logname = logname self.logwrite(str(datetime.now()) + '\n') self.account = CoinbaseAccount(oauth2_credentials, api_key) def logwrite(self, message): """ Writes to the logfile with appended newline. """ with open(self.logname, 'a') as log: with _logwriteLock: log.write(message + '\n') def logexecution(self, order, result): """ Write the result and order to the log file """ self.logorder(order) logstr = "Order Type: " + str(result.type) + " Code: " + str( result.code) + " Executed at: " + str(result.created_at) logstr = logstr + "\nBTC Amount: " + str( result.btc_amount) + " Total Price: " + str( result.total_amount) + " Fees: " + str(result.fees_bank + result.fees_coinbase) logstr = "Result of Order\n" + logstr self.logwrite(logstr) def logorder(self, order, header=None): """ Writes the order the log file """ logstr = header + '\n' if isinstance(header, str) else '' logstr = "OrderID: %s OrderType: %s Quantity: %s Price: %s" % ( order.orderid, order.ordertype, order.qty, order.price) logstr = logstr + " Change Value: " + str(order.changeval) logstr = logstr + " Executed: " + str(order.executed) + '\n' if order.parentorder is not None: self.logorder(order.parentorder, "Parent Order:") self.logwrite(logstr) def ExecuteOrder(self, order): """ Executes an order based on its order.ordertype Returns None if the trade is valid is not yet active(i.e limit not met) Returns a CoinbaseError or CoinbaseTransfer object if order attempted to execute Returns False if the order should be Deleted or removed from the orderbook. """ with self._executeLock: traderesult = None currentprice = -1 if order.ordertype in [MARKET_BUY, LIMIT_BUY]: currentprice = self.account.buy_price(qty=order.qty) print "Current Buy Price: " + str(currentprice / order.qty) elif order.ordertype in [ MARKET_SELL, LIMIT_SELL, STOP_LOSS, TRAIL_STOP_VALUE, TRAIL_STOP_PERCENT ]: currentprice = self.account.sell_price(qty=order.qty) print "Current Sell Price: " + str(currentprice / order.qty) if order.ordertype == MARKET_BUY: traderesult = self.account.buy_btc(qty=order.qty) elif order.ordertype == MARKET_SELL: traderesult = self.account.sell_btc(qty=order.qty) elif order.ordertype == LIMIT_BUY: if currentprice <= order.price: traderesult = self.account.buy_btc(qty=order.qty) elif order.ordertype == LIMIT_SELL: if currentprice >= order.price: traderesult = self.account.sell_btc(qty=order.qty) elif order.ordertype == STOP_LOSS: if currentprice <= order.price: traderesult = CoinOrder(ordertype=MARKET_SELL, qty=order.qty, parentorder=order) elif order.ordertype == TRAIL_STOP_VALUE: if currentprice > order.price: order.price = currentprice elif currentprice <= (order.price - order.changeval): traderesult = CoinOrder(ordertype=MARKET_SELL, qty=order.qty, parentorder=order) elif order.ordertype == TRAIL_STOP_PERCENT: if currentprice > order.price: order.price = currentprice elif currentprice <= (order.price * (1.0 - order.changeval)): traderesult = CoinOrder(ordertype=MARKET_SELL, qty=order.qty, parentorder=order) else: traderesult = False # deletes the order from the order book if isinstance(order.nextOrder, CoinOrder): if isinstance(traderesult, CoinbaseTransfer): traderesult = (traderesult, order.nextOrder) if isinstance(traderesult, CoinbaseError): print "Triggered Order Lost due to error" return traderesult def trade(self, runtime=None, sleeptime=60, startNewThread=False): """ Call this function to execute trades in added to the order book. Returns True on success and Writes the specified log file. :param runtime: Number of seconds to trade should execute, infinity (None) is the default. :param sleeptime: Interval of time between checking orders (coinbase updates their prices once per 60 seconds) :param startNewThread: Optionally run trade in a new thread, orders can then be added while trade() runs (using the usual methods) """ if startNewThread == True: newThread = threading.Thread(target=self.trade, args=[runtime, sleeptime, False]) newThread.daemon = True newThread.start() return True with self._stopTradeLock: self.stopTrade = False initialBtcBal = self.account.balance initialUsdVal = self.account.sell_price(initialBtcBal) initialSellRate = initialUsdVal / initialBtcBal if initialBtcBal != 0 else 0 self.logwrite("Initial BTC Balance: " + str(initialBtcBal) + " Initial USD Value: " + str(initialUsdVal) + " Price Per Coin: " + str(initialSellRate)) while ((runtime is None) or (runtime > 0)) and (len(self.orderbook) > 0): with self._stopTradeLock: if self.traderid in _stoppedTraders: _stoppedTraders.remove(self.traderid) return True sleep = True temporderbook = [] with self._orderbookLock: constantorderbook = self.orderbook for order in constantorderbook: result = self.ExecuteOrder(order) if isinstance(result, tuple): # Append next order and process the trade result temporderbook.append(result[1]) result = result[0] if result is False: # Invalid Order ID, discard order pass elif isinstance(result, CoinbaseError): # There is an error, check if its due to improper supply. self.logorder(order, result.error[0]) if result.error[ 0] == "You must acknowledge that the price can vary by checking the box below.": temporderbook.append( order ) # Means the order failed due to low supply and not agreeing to the price varying. elif len(self.orderbook) == 1: print result.error sleep = False # This is done to exit quickly if the last trade errors elif isinstance(result, CoinOrder): order.executed = True temporderbook.append(result) sleep = False # If a coinorder is returned it should be executed asap. sleeptime = 1 # If I can't be executed after the first time, keep trying every second otherwise elif isinstance(result, CoinbaseTransfer): # Trade executed order.executed = True self.logexecution(order, result) elif result is None: temporderbook.append(order) with self._orderbookLock: self.orderbook = temporderbook if sleep is True: if runtime is not None: runtime = runtime - sleeptime if runtime is None or runtime > 0: time.sleep(sleeptime) return True def stoptrade(self): """ Call to stop trade() method from executing. Only needed for threading mode. """ with self._stopTradeLock: _stoppedTraders.append(self.traderid) def _addOrder(self, ordertype, qty, price=0, changeval=None, queueExecution=True): """ Generic Order Adding Function. price is in price per share. User shouldn't call. ordertype: type of order, constant qty: qty in order price: price to execute changeval: change to observer for stoploss trades queueExecution: True or False to add to orderbook list, True if you want to execute with trade(). False if you just want the order object to be returned """ price = price * qty order = CoinOrder(ordertype=ordertype, qty=qty, price=price, changeval=changeval) if queueExecution is True: with self._orderbookLock: self.orderbook.append(order) self.logorder(order, "Added Order:") return order def setMarketBuy(self, qty, queue=True): """ Buy qty bitcoins as soon as possible """ return self._addOrder(ordertype=MARKET_BUY, qty=qty, queueExecution=queue) def setMarketSell(self, qty, queue=True): """ Sell qty bitcoins as soon as possible """ return self._addOrder(ordertype=MARKET_SELL, qty=qty, queueExecution=queue) def setLimitBuy(self, qty, price, queue=True): """ Helps buy low, Buy at specified price or lower. Input price per share """ return self._addOrder(ordertype=LIMIT_BUY, qty=qty, price=price, queueExecution=queue) def setLimitSell(self, qty, price, queue=True): """ Helps sell high, Sell at specified price or higher. Input execution price per share """ return self._addOrder(ordertype=LIMIT_SELL, qty=qty, price=price, queueExecution=queue) def setStopLoss(self, qty, price, queue=True): """ If the price goes below a specified price, sell it all ASAP via market sell order. Input execution price per share """ return self._addOrder(ordertype=STOP_LOSS, qty=qty, price=price, queueExecution=queue) def setTrailStopLossValue(self, qty, changeval, queue=True): """ Sell qty bitcoins when they drop "value" below their maximum per share value since purchase. Basically a Moving Limit sell at: maxPriceSeen - value """ return self._addOrder(ordertype=TRAIL_STOP_VALUE, qty=qty, price=0, changeval=changeval * qty, queueExecution=queue) def setTrailStopLossPercent(self, qty, changeval, maxprice=0, queue=True): """ Sell qty bitcoins when they have a changepercent drop below their maximum value since purchase. Basically a Moving Limit sell at: maxPriceSeen * (1 - (changepercent/100) ) """ return self._addOrder(ordertype=TRAIL_STOP_PERCENT, qty=qty, price=maxprice, changeval=changeval / 100.0, queueExecution=queue) def oneStartsAnother(self, initialOrder, triggerOrder, queue=True): """ Input two orders then once ExecuteOrder() executes the order (in trade) it Returns that value to the trade() method which will then begin executing the next order specified. This can be used to execute a buy which upon execution will trigger a sell order at a higher price. These orders could be combined multiple times by having any order trigger another oneStartsAnother order. These order should NOT be added to the queue (i.e. set queue = False upon order creation). """ initialOrder.nextOrder = triggerOrder with self._orderbookLock: if queue is True: self.orderbook.append(initialOrder) self.logorder(initialOrder, "Added Order:") return initialOrder def RemoveOrder(self, orderid): """ Accepts either the orderid (starts at 0 and increments to the number of total orders) Or the CoinOrder object returned when the order was created. """ removedOrder = None if isinstance(orderid, int): temp = [] with self._orderbookLock: constorderbook = self.orderbook for order in self.orderbook: if order.id != orderid: temp.append(order) else: removedOrder = order with self._orderbookLock: self.orderbook = temp if isinstance(orderid, CoinOrder): try: with self._orderbookLock: self.orderbook.remove(order) removedOrder = order except: pass return removedOrder
class CoinBaseLibraryTests(unittest.TestCase): def setUp(self): self.account = CoinbaseAccount(oauth2_credentials=TEMP_CREDENTIALS) @httprettified def test_status_error(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/account/balance", body='''{"error": "Invalid api_key"}''', content_type='text/json', status=401) try: self.account.balance except CoinbaseError as e: e.error.should.equal("Invalid api_key") unicode(e).should.equal(u"Invalid api_key") str(e).should.equal("Invalid api_key") except Exception: assert False @httprettified def test_success_false(self): # Success is added when posting, putting or deleting HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/account/balance", body='''{"success": false, "errors":["Error 1","Error 2"]}''', content_type='text/json') try: self.account.balance except CoinbaseError as e: e.error.should.equal(["Error 1", "Error 2"]) except Exception: assert False @httprettified def test_retrieve_balance(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/account/balance", body='''{"amount":"0.00000000","currency":"BTC"}''', content_type='text/json') this(float(self.account.balance)).should.equal(0.0) this(self.account.balance.currency).should.equal('BTC') @httprettified def test_receive_addresses(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/account/receive_address", body='''{"address" : "1DX9ECEF3FbGUtzzoQhDT8CG3nLUEA2FJt"}''', content_type='text/json') this(self.account.receive_address).should.equal(u'1DX9ECEF3FbGUtzzoQhDT8CG3nLUEA2FJt') @httprettified def test_contacts(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/contacts", body='''{"contacts":[{"contact":{"email":"*****@*****.**"}}],"total_count":1,"num_pages":1,"current_page":1}''', content_type='text/json') this(self.account.contacts).should.equal([{u'email': u'*****@*****.**'}]) @httprettified def test_buy_price_1(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/prices/buy?qty=1", body='''{"amount":"63.31","currency":"USD"}''', content_type='text/json') buy_price_1 = self.account.buy_price(1) this(buy_price_1).should.be.a(Decimal) this(buy_price_1).should.be.lower_than(100) this(buy_price_1.currency).should.equal('USD') @httprettified def test_buy_price_2(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/prices/buy?qty=10", body='''{"amount":"633.25","currency":"USD"}''', content_type='text/json') buy_price_10 = self.account.buy_price(10) this(buy_price_10).should.be.greater_than(100) @httprettified def test_sell_price(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/prices/sell?qty=1", body='''{"amount":"63.31","currency":"USD"}''', content_type='text/json') sell_price_1 = self.account.sell_price(1) this(sell_price_1).should.be.a(Decimal) this(sell_price_1).should.be.lower_than(100) this(sell_price_1.currency).should.equal('USD') @httprettified def test_sell_price_10(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/prices/sell?qty=1", body='''{"amount":"630.31","currency":"USD"}''', content_type='text/json') sell_price_10 = self.account.sell_price(10) this(sell_price_10).should.be.greater_than(100) @httprettified def test_request_bitcoin(self): HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/transactions/request_money", body='''{"success":true,"transaction":{"id":"514e4c37802e1bf69100000e","created_at":"2013-03-23T17:43:35-07:00","hsh":null,"notes":"Testing","amount":{"amount":"1.00000000","currency":"BTC"},"request":true,"status":"pending","sender":{"id":"514e4c1c802e1bef9800001e","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"}}}''', content_type='text/json') new_request = self.account.request('*****@*****.**', 1, 'Testing') this(new_request.amount).should.equal(CoinbaseAmount(Decimal(1), 'BTC')) this(new_request.request).should.equal(True) this(new_request.sender.email).should.equal('*****@*****.**') this(new_request.recipient.email).should.equal('*****@*****.**') this(new_request.notes).should.equal('Testing') @httprettified def test_send_bitcoin(self): HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/transactions/send_money", body='''{"success":true,"transaction":{"id":"5158b227802669269c000009","created_at":"2013-03-31T15:01:11-07:00","hsh":null,"notes":"","amount":{"amount":"-0.10000000","currency":"BTC"},"request":false,"status":"pending","sender":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient_address":"15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP"}} ''', content_type='text/json') new_transaction_with_btc_address = self.account.send('15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP', amount=0.1) this(new_transaction_with_btc_address.amount).should.equal(CoinbaseAmount(Decimal('-0.1'), 'BTC')) this(new_transaction_with_btc_address.request).should.equal(False) this(new_transaction_with_btc_address.sender.email).should.equal('*****@*****.**') this(new_transaction_with_btc_address.recipient).should.equal(None) this(new_transaction_with_btc_address.recipient_address).should.equal('15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP') HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/transactions/send_money", body='''{"success":true,"transaction":{"id":"5158b2920b974ea4cb000003","created_at":"2013-03-31T15:02:58-07:00","hsh":null,"notes":"","amount":{"amount":"-0.10000000","currency":"BTC"},"request":false,"status":"pending","sender":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"4efec8d7bedd320001000003","email":"*****@*****.**","name":"Brian Armstrong"},"recipient_address":"*****@*****.**"}}''', content_type='text/json') new_transaction_with_email = self.account.send('*****@*****.**', amount=0.1) this(new_transaction_with_email.recipient.email).should.equal('*****@*****.**') @httprettified def test_transaction_list(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/transactions", body='''{"current_user":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"balance":{"amount":"0.00000000","currency":"BTC"},"total_count":4,"num_pages":1,"current_page":1,"transactions":[{"transaction":{"id":"514e4c37802e1bf69100000e","created_at":"2013-03-23T17:43:35-07:00","hsh":null,"notes":"Testing","amount":{"amount":"1.00000000","currency":"BTC"},"request":true,"status":"pending","sender":{"id":"514e4c1c802e1bef9800001e","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"}}},{"transaction":{"id":"514e4c1c802e1bef98000020","created_at":"2013-03-23T17:43:08-07:00","hsh":null,"notes":"Testing","amount":{"amount":"1.00000000","currency":"BTC"},"request":true,"status":"pending","sender":{"id":"514e4c1c802e1bef9800001e","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"}}},{"transaction":{"id":"514b9fb1b8377ee36500000d","created_at":"2013-03-21T17:02:57-07:00","hsh":"42dd65a18dbea0779f32021663e60b1fab8ee0f859db7172a078d4528e01c6c8","notes":"You gave me this a while ago. It's turning into a fair amount of cash and thought you might want it back :) Building something on your API this weekend. Take care!","amount":{"amount":"-1.00000000","currency":"BTC"},"request":false,"status":"complete","sender":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient":{"id":"4efec8d7bedd320001000003","email":"*****@*****.**","name":"Brian Armstrong"},"recipient_address":"*****@*****.**"}},{"transaction":{"id":"509e01cb12838e0200000224","created_at":"2012-11-09T23:27:07-08:00","hsh":"ac9b0ffbe36dbe12c5ca047a5bdf9cadca3c9b89b74751dff83b3ac863ccc0b3","notes":"","amount":{"amount":"1.00000000","currency":"BTC"},"request":false,"status":"complete","sender":{"id":"4efec8d7bedd320001000003","email":"*****@*****.**","name":"Brian Armstrong"},"recipient":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient_address":"*****@*****.**"}}]}''', content_type='text/json') transaction_list = self.account.transactions() this(transaction_list).should.be.a(list) @httprettified def test_getting_transaction(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/transactions/5158b227802669269c000009", body='''{"transaction":{"id":"5158b227802669269c000009","created_at":"2013-03-31T15:01:11-07:00","hsh":"223a404485c39173ab41f343439e59b53a5d6cba94a02501fc6c67eeca0d9d9e","notes":"","amount":{"amount":"-0.10000000","currency":"BTC"},"request":false,"status":"pending","sender":{"id":"509e01ca12838e0200000212","email":"*****@*****.**","name":"*****@*****.**"},"recipient_address":"15yHmnB5vY68sXpAU9pR71rnyPAGLLWeRP"}}''', content_type='text/json') transaction = self.account.get_transaction('5158b227802669269c000009') this(transaction.status).should.equal('pending') this(transaction.amount).should.equal(CoinbaseAmount(Decimal("-0.1"), "BTC")) @httprettified def test_getting_user_details(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/users", body='''{"users":[{"user":{"id":"509f01da12837e0201100212","name":"New User","email":"*****@*****.**","time_zone":"Pacific Time (US & Canada)","native_currency":"USD","buy_level":1,"sell_level":1,"balance":{"amount":"1225.86084181","currency":"BTC"},"buy_limit":{"amount":"10.00000000","currency":"BTC"},"sell_limit":{"amount":"50.00000000","currency":"BTC"}}}]}''', content_type='text/json') user = self.account.get_user_details() this(user.id).should.equal("509f01da12837e0201100212") this(user.balance).should.equal(CoinbaseAmount(Decimal("1225.86084181"), "BTC")) # The following tests use the example request/responses from the coinbase API docs: # test_creating_button, test_creating_order, test_getting_order @httprettified def test_creating_button(self): HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/buttons", body='''{"success":true,"button":{"code":"93865b9cae83706ae59220c013bc0afd","type":"buy_now","style":"custom_large","text":"Pay With Bitcoin","name":"test","description":"Sample description","custom":"Order123","callback_url":"http://www.example.com/my_custom_button_callback","price":{"cents":123,"currency_iso":"USD"}}}''', content_type='text/json') button = self.account.create_button( name="test", type="buy_now", price=1.23, currency="USD", custom="Order123", callback_url="http://www.example.com/my_custom_button_callback", description="Sample description", style="custom_large", include_email=True ) this(button.code).should.equal("93865b9cae83706ae59220c013bc0afd") this(button.name).should.equal("test") this(button.price).should.equal(CoinbaseAmount(Decimal('1.23'), "USD")) this(button.include_address).should.equal(None) @httprettified def test_creating_order(self): HTTPretty.register_uri(HTTPretty.POST, "https://coinbase.com/api/v1/buttons/93865b9cae83706ae59220c013bc0afd/create_order", body='''{"success":true,"order":{"id":"7RTTRDVP","created_at":"2013-11-09T22:47:10-08:00","status":"new","total_btc":{"cents":100000000,"currency_iso":"BTC"},"total_native":{"cents":100000000,"currency_iso":"BTC"},"custom":"Order123","receive_address":"mgrmKftH5CeuFBU3THLWuTNKaZoCGJU5jQ","button":{"type":"buy_now","name":"test","description":"Sample description","id":"93865b9cae83706ae59220c013bc0afd"},"transaction":null}}''', content_type='text/json') order = self.account.create_order("93865b9cae83706ae59220c013bc0afd") this(order.order_id).should.equal("7RTTRDVP") this(order.total_btc).should.equal(CoinbaseAmount(1, 'BTC')) this(order.button.button_id).should.equal("93865b9cae83706ae59220c013bc0afd") this(order.transaction).should.equal(None) @httprettified def test_order_list(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/orders", body='''{"orders":[{"order":{"id":"A7C52JQT","created_at":"2013-03-11T22:04:37-07:00","status":"completed","total_btc":{"cents":100000000,"currency_iso":"BTC"},"total_native":{"cents":3000,"currency_iso":"USD"},"custom":"","button":{"type":"buy_now","name":"Order #1234","description":"order description","id":"eec6d08e9e215195a471eae432a49fc7"},"transaction":{"id":"513eb768f12a9cf27400000b","hash":"4cc5eec20cd692f3cdb7fc264a0e1d78b9a7e3d7b862dec1e39cf7e37ababc14","confirmations":0}}}],"total_count":1,"num_pages":1,"current_page":1}''', content_type='text/json') orders = self.account.orders() this(orders).should.be.a(list) this(orders[0].order_id).should.equal("A7C52JQT") @httprettified def test_getting_order(self): HTTPretty.register_uri(HTTPretty.GET, "https://coinbase.com/api/v1/orders/A7C52JQT", body='''{"order":{"id":"A7C52JQT","created_at":"2013-03-11T22:04:37-07:00","status":"completed","total_btc":{"cents":10000000,"currency_iso":"BTC"},"total_native":{"cents":10000000,"currency_iso":"BTC"},"custom":"","button":{"type":"buy_now","name":"test","description":"","id":"eec6d08e9e215195a471eae432a49fc7"},"transaction":{"id":"513eb768f12a9cf27400000b","hash":"4cc5eec20cd692f3cdb7fc264a0e1d78b9a7e3d7b862dec1e39cf7e37ababc14","confirmations":0}}}''', content_type='text/json') order = self.account.get_order("A7C52JQT") this(order.order_id).should.equal("A7C52JQT") this(order.status).should.equal("completed") this(order.total_btc).should.equal(CoinbaseAmount(Decimal(".1"), "BTC")) this(order.button.name).should.equal("test") this(order.transaction.transaction_id).should.equal("513eb768f12a9cf27400000b")
class Trader(object): _orderbookLock = threading.Lock() _executeLock = threading.Lock() _stopTradeLock = threading.Lock() stopTrade = False def __init__(self,api_key = None, oauth2_credentials = None, orderbook = None, logname = "traderlog.txt"): if (api_key is None) and (oauth2_credentials is None): raise ValueError("api_key and oauth2_credentials cannot be None") if (api_key is not None) and (oauth2_credentials is not None): raise ValueError("User Provided both api_key and oauth2_credentials, select one") # I might want to instead just use one or the other with _traderIdLock: global TRADER_ID self.traderid = TRADER_ID TRADER_ID+=1 self.orderbook = [] if orderbook is None else orderbook f = open(logname, 'w') f.close() self.logname = logname self.logwrite(str(datetime.now()) + '\n') self.account = CoinbaseAccount(oauth2_credentials,api_key) def logwrite(self, message): """ Writes to the logfile with appended newline. """ with open(self.logname, 'a') as log: with _logwriteLock: log.write(message + '\n') def logexecution(self, order, result): """ Write the result and order to the log file """ self.logorder(order) logstr = "Order Type: " + str(result.type) + " Code: " + str(result.code) + " Executed at: " + str(result.created_at) logstr = logstr + "\nBTC Amount: " + str(result.btc_amount) + " Total Price: " + str(result.total_amount) + " Fees: " + str(result.fees_bank+result.fees_coinbase) logstr = "Result of Order\n" + logstr self.logwrite(logstr) def logorder(self, order, header = None): """ Writes the order the log file """ logstr = header + '\n' if isinstance(header,str) else '' logstr = "OrderID: %s OrderType: %s Quantity: %s Price: %s" % (order.orderid, order.ordertype, order.qty, order.price) logstr = logstr + " Change Value: " + str(order.changeval) logstr = logstr + " Executed: " + str(order.executed) + '\n' if order.parentorder is not None: self.logorder(order.parentorder, "Parent Order:") self.logwrite(logstr) def ExecuteOrder(self, order): """ Executes an order based on its order.ordertype Returns None if the trade is valid is not yet active(i.e limit not met) Returns a CoinbaseError or CoinbaseTransfer object if order attempted to execute Returns False if the order should be Deleted or removed from the orderbook. """ with self._executeLock: traderesult = None currentprice = -1 if order.ordertype in [MARKET_BUY, LIMIT_BUY]: currentprice = self.account.buy_price(qty = order.qty) print "Current Buy Price: " + str(currentprice/order.qty) elif order.ordertype in [MARKET_SELL, LIMIT_SELL, STOP_LOSS, TRAIL_STOP_VALUE, TRAIL_STOP_PERCENT]: currentprice = self.account.sell_price(qty = order.qty) print "Current Sell Price: " + str(currentprice/order.qty) if order.ordertype == MARKET_BUY: traderesult = self.account.buy_btc(qty = order.qty) elif order.ordertype == MARKET_SELL: traderesult = self.account.sell_btc(qty = order.qty) elif order.ordertype == LIMIT_BUY: if currentprice <= order.price: traderesult = self.account.buy_btc(qty = order.qty) elif order.ordertype == LIMIT_SELL: if currentprice >= order.price: traderesult = self.account.sell_btc(qty = order.qty) elif order.ordertype == STOP_LOSS: if currentprice <= order.price: traderesult = CoinOrder(ordertype = MARKET_SELL, qty = order.qty, parentorder = order) elif order.ordertype == TRAIL_STOP_VALUE: if currentprice > order.price: order.price = currentprice elif currentprice <= (order.price - order.changeval): traderesult = CoinOrder(ordertype = MARKET_SELL, qty = order.qty, parentorder = order) elif order.ordertype == TRAIL_STOP_PERCENT: if currentprice > order.price: order.price = currentprice elif currentprice <= (order.price * (1.0 - order.changeval) ): traderesult = CoinOrder(ordertype = MARKET_SELL, qty = order.qty, parentorder = order) else: traderesult = False # deletes the order from the order book if isinstance(order.nextOrder, CoinOrder): if isinstance(traderesult, CoinbaseTransfer): traderesult = (traderesult, order.nextOrder) if isinstance(traderesult, CoinbaseError): print "Triggered Order Lost due to error" return traderesult def trade(self, runtime = None, sleeptime = 60, startNewThread = False): """ Call this function to execute trades in added to the order book. Returns True on success and Writes the specified log file. :param runtime: Number of seconds to trade should execute, infinity (None) is the default. :param sleeptime: Interval of time between checking orders (coinbase updates their prices once per 60 seconds) :param startNewThread: Optionally run trade in a new thread, orders can then be added while trade() runs (using the usual methods) """ if startNewThread == True: newThread = threading.Thread(target=self.trade, args=[runtime, sleeptime, False]) newThread.daemon = True newThread.start() return True with self._stopTradeLock: self.stopTrade = False initialBtcBal = self.account.balance initialUsdVal = self.account.sell_price(initialBtcBal) initialSellRate = initialUsdVal/initialBtcBal if initialBtcBal != 0 else 0 self.logwrite("Initial BTC Balance: " + str(initialBtcBal) + " Initial USD Value: " + str(initialUsdVal) + " Price Per Coin: " + str(initialSellRate)) while ( (runtime is None) or (runtime>0) ) and (len(self.orderbook) > 0): with self._stopTradeLock: if self.traderid in _stoppedTraders: _stoppedTraders.remove(self.traderid) return True sleep = True temporderbook = [] with self._orderbookLock: constantorderbook = self.orderbook for order in constantorderbook: result = self.ExecuteOrder(order) if isinstance(result, tuple): # Append next order and process the trade result temporderbook.append(result[1]) result = result[0] if result is False: # Invalid Order ID, discard order pass elif isinstance(result, CoinbaseError): # There is an error, check if its due to improper supply. self.logorder(order, result.error[0]) if result.error[0] == "You must acknowledge that the price can vary by checking the box below.": temporderbook.append(order) # Means the order failed due to low supply and not agreeing to the price varying. elif len(self.orderbook) == 1: print result.error sleep = False # This is done to exit quickly if the last trade errors elif isinstance(result, CoinOrder): order.executed = True temporderbook.append(result) sleep = False # If a coinorder is returned it should be executed asap. sleeptime = 1 # If I can't be executed after the first time, keep trying every second otherwise elif isinstance(result, CoinbaseTransfer): # Trade executed order.executed = True self.logexecution(order, result) elif result is None: temporderbook.append(order) with self._orderbookLock: self.orderbook = temporderbook if sleep is True: if runtime is not None: runtime = runtime - sleeptime if runtime is None or runtime > 0: time.sleep(sleeptime) return True def stoptrade(self): """ Call to stop trade() method from executing. Only needed for threading mode. """ with self._stopTradeLock: _stoppedTraders.append(self.traderid) def _addOrder(self, ordertype, qty, price = 0, changeval = None, queueExecution = True): """ Generic Order Adding Function. price is in price per share. User shouldn't call. ordertype: type of order, constant qty: qty in order price: price to execute changeval: change to observer for stoploss trades queueExecution: True or False to add to orderbook list, True if you want to execute with trade(). False if you just want the order object to be returned """ price = price * qty order = CoinOrder(ordertype = ordertype, qty = qty, price = price, changeval = changeval) if queueExecution is True: with self._orderbookLock: self.orderbook.append(order) self.logorder(order, "Added Order:") return order def setMarketBuy(self, qty, queue = True): """ Buy qty bitcoins as soon as possible """ return self._addOrder(ordertype = MARKET_BUY, qty = qty, queueExecution = queue) def setMarketSell(self, qty, queue = True): """ Sell qty bitcoins as soon as possible """ return self._addOrder(ordertype = MARKET_SELL, qty = qty, queueExecution = queue) def setLimitBuy(self, qty, price, queue = True): """ Helps buy low, Buy at specified price or lower. Input price per share """ return self._addOrder(ordertype = LIMIT_BUY,qty = qty, price = price, queueExecution = queue) def setLimitSell(self, qty, price, queue = True): """ Helps sell high, Sell at specified price or higher. Input execution price per share """ return self._addOrder(ordertype = LIMIT_SELL, qty = qty, price = price, queueExecution = queue) def setStopLoss(self, qty, price, queue = True): """ If the price goes below a specified price, sell it all ASAP via market sell order. Input execution price per share """ return self._addOrder(ordertype = STOP_LOSS, qty = qty, price = price, queueExecution = queue) def setTrailStopLossValue(self, qty, changeval, queue = True): """ Sell qty bitcoins when they drop "value" below their maximum per share value since purchase. Basically a Moving Limit sell at: maxPriceSeen - value """ return self._addOrder(ordertype = TRAIL_STOP_VALUE, qty = qty, price = 0, changeval = changeval*qty, queueExecution = queue) def setTrailStopLossPercent(self, qty, changeval, maxprice = 0, queue = True): """ Sell qty bitcoins when they have a changepercent drop below their maximum value since purchase. Basically a Moving Limit sell at: maxPriceSeen * (1 - (changepercent/100) ) """ return self._addOrder(ordertype = TRAIL_STOP_PERCENT, qty = qty, price = maxprice, changeval = changeval/100.0, queueExecution = queue) def oneStartsAnother(self, initialOrder, triggerOrder, queue = True): """ Input two orders then once ExecuteOrder() executes the order (in trade) it Returns that value to the trade() method which will then begin executing the next order specified. This can be used to execute a buy which upon execution will trigger a sell order at a higher price. These orders could be combined multiple times by having any order trigger another oneStartsAnother order. These order should NOT be added to the queue (i.e. set queue = False upon order creation). """ initialOrder.nextOrder = triggerOrder with self._orderbookLock: if queue is True: self.orderbook.append(initialOrder) self.logorder(initialOrder, "Added Order:") return initialOrder def RemoveOrder(self, orderid): """ Accepts either the orderid (starts at 0 and increments to the number of total orders) Or the CoinOrder object returned when the order was created. """ removedOrder = None if isinstance(orderid, int): temp = [] with self._orderbookLock: constorderbook = self.orderbook for order in self.orderbook: if order.id != orderid: temp.append(order) else: removedOrder = order with self._orderbookLock: self.orderbook = temp if isinstance(orderid, CoinOrder): try: with self._orderbookLock: self.orderbook.remove(order) removedOrder = order except: pass return removedOrder
class Trader(object): def __init__(self,api_key = None, oauth2_credentials = None, orderbook = None, logname = "traderlog.txt"): if (api_key is None) and (oauth2_credentials is None): raise ValueError("api_key and oauth2_credentials cannot be None") if (api_key is not None) and (oauth2_credentials is not None): raise ValueError("User Provided both api_key and oauth2_credentials, select one") # I might want to instead just use one or the other self.orderbook = [] if orderbook is None else orderbook f = open(logname, 'w') f.close() self.logname = logname self.logwrite(str(datetime.now()) + '\n') self.account = CoinbaseAccount(oauth2_credentials,api_key) def logwrite(self, message): """ Writes to the logfile with appended newline. """ with open(self.logname, 'a') as log: log.write(message + '\n') def logexecution(self, order, result): """ Write the result and order to the log file """ self.logorder(order) logstr = "Order Type: " + str(result.type) + " Code: " + str(result.code) + " Executed at: " + str(result.created_at) logstr = logstr + "\nBTC Amount: " + str(result.btc_amount) + " Total Price: " + str(result.total_amount) + " Fees: " + str(result.fees_bank+result.fees_coinbase) self.logwrite(logstr) def logorder(self, order, header = None): """ Writes the order the log file """ logstr = header + '\n' if isinstance(header,str) else '' logstr = "OrderID: %s OrderType: %s Quantity: %s Price: %s" % (order.orderid, order.ordertype, order.qty, order.price) logstr = logstr + " Change Value: " + str(order.changeval) logstr = logstr + " Executed: " + str(order.executed) + '\n' if order.parentorder is not None: self.logorder(order.parentorder, "Parent Order:") self.logwrite(logstr) def ExecuteOrder(self, order): """ Executes an order based on its order.ordertype Returns None if the trade is valid is not yet active(i.e limit not met) Returns a CoinbaseError or CoinbaseTransfer object if order attempted to execute Returns False if the order should be Deleted or removed from the orderbook. """ traderesult = None currentprice = -1 if order.ordertype in [MARKET_BUY, LIMIT_BUY]: currentprice = self.account.buy_price(qty = order.qty) print "Current Buy Price: " + str(currentprice/order.qty) elif order.ordertype in [MARKET_SELL, LIMIT_SELL, STOP_LOSS, TRAIL_STOP_VALUE, TRAIL_STOP_PERCENT]: currentprice = self.account.sell_price(qty = order.qty) print "Current Sell Price: " + str(currentprice/order.qty) if order.ordertype == MARKET_BUY: traderesult = self.account.buy_btc(qty = order.qty) elif order.ordertype == MARKET_SELL: traderesult = self.account.sell_btc(qty = order.qty) elif order.ordertype == LIMIT_BUY: if currentprice <= order.price: traderesult = self.account.buy_btc(qty = order.qty) else: print "Did not met LIMIT_BUY price at: " + str(order.price / order.qty) + ' for ' + str(order.qty) + ' BTC' elif order.ordertype == LIMIT_SELL: if currentprice >= order.price: print currentprice, order.price traderesult = self.account.sell_btc(qty = order.qty) else: print "Did not met LIMIT_SELL price at: " + str(order.price / order.qty) + ' for ' + str(order.qty) + ' BTC' elif order.ordertype == STOP_LOSS: if currentprice <= order.price: traderesult = CoinOrder(ordertype = MARKET_SELL, qty = order.qty, parentorder = order) else: print "Did not met STOP_LOSS price at: " + str(order.price / order.qty) + ' for ' + str(order.qty) + ' BTC' elif order.ordertype == TRAIL_STOP_VALUE: if currentprice > order.price: order.price = currentprice print "Setting new TRAIL_STOP_VALUE max price seen: " + str(order.price / order.qty) elif currentprice <= (order.price - order.changeval): traderesult = CoinOrder(ordertype = MARKET_SELL, qty = order.qty, parentorder = order) else: print "Did not meet TRAIL_STOP_VALUE price at: " + str((order.price - order.changeval) / order.qty) + ' for ' + str(order.qty) + ' BTC' elif order.ordertype == TRAIL_STOP_PERCENT: if currentprice > order.price: order.price = currentprice print "Setting new TRAIL_STOP_PERCENT max price seen: " + str(order.price / order.qty) elif currentprice <= (order.price * (1.0 - order.changeval) ): traderesult = CoinOrder(ordertype = MARKET_SELL, qty = order.qty, parentorder = order) else: print "Did not meet TRAIL_STOP_PERCENT price at: " + str((order.price * (1.0 - order.changeval) ) / order.qty) + ' for ' + str(order.qty) + ' BTC' else: print order.ordertype # Could throw an error on CoinOrder initialize sys.exit(1) return traderesult def trade(self, runtime = None, sleeptime = 60): """ Returns a table of transaction results when time runs out or runs continously. Writes a log file. :param runtime: Number of seconds to trade should execute, infinity (None) is the default. :param sleeptime: Interval of time between checking orders (coinbase updates their prices once per 60 seconds) """ initialBtcBal = self.account.balance initialUsdVal = self.account.sell_price(initialBtcBal) initialSellRate = initialUsdVal/initialBtcBal if initialBtcBal != 0 else 0 self.logwrite("Initial BTC Balance: " + str(initialBtcBal) + " Initial USD Value: " + str(initialUsdVal) + " Price Per Coin: " + str(initialSellRate)) while ( (runtime is None) or (runtime>0) ) and (len(self.orderbook) > 0): try: temporderbook = [] sleep = True os.system('clear') print "Current Balance: " + str(self.account.balance) order_counter = 0 for order in self.orderbook: order_counter = order_counter + 1 print str(order_counter) + ": ", result = self.ExecuteOrder(order) if result is False: # Invalid Order ID pass elif isinstance(result, CoinbaseError): # THere is an error, check if its due to improper supply. print result.error self.logorder(order, result.error[0]) if result.error[0] == "You must acknowledge that the price can vary by checking the box below.": temporderbook.append(order) # Means the order failed due to low supply and not agreeing to the price varying. elif len(self.orderbook) == 1: sleep = False # This is done to exit quickly if the only trade errors elif isinstance(result, CoinOrder): order.executed = True temporderbook.append(result) sleep = False # If a coinorder is returned it should be executed asap. elif isinstance(result, CoinbaseTransfer): # Trade executed order.executed = True self.logexecution(order, result) elif result is None: temporderbook.append(order) self.orderbook = temporderbook if sleep is True: if runtime is not None: runtime = runtime - sleeptime if runtime is None or runtime > 0: time.sleep(sleeptime) except KeyboardInterrupt: print "Modifying orders" return self.orderbook except : print "Exception in orders, skipping.." def _addOrder(self, ordertype, qty, price = 0, changeval = None): """ Generic Order Adding Function. price is in price per share. User shouldn't call. """ price = price * qty order = CoinOrder(ordertype = ordertype, qty = qty, price = price, changeval = changeval) self.orderbook.append(order) self.logorder(order, "Added Order:") return order def resumeOrder(self, order): return self._addOrder(order.ordertype, order.qty, order.price, order.changeval) def setMarketBuy(self, qty): """ Buy qty bitcoins as soon as possible """ return self._addOrder(ordertype = MARKET_BUY, qty = qty) def setMarketSell(self, qty): """ Sell qty bitcoins as soon as possible """ return self._addOrder(ordertype = MARKET_SELL, qty = qty) def setLimitBuy(self, qty, price): """ Helps buy low, Buy at specified price or lower. Input price per share """ return self._addOrder(ordertype = LIMIT_BUY,qty = qty, price = price) def setLimitSell(self, qty, price): """ Helps sell high, Sell at specified price or higher. Input execution price per share """ return self._addOrder(ordertype = LIMIT_SELL, qty = qty, price = price ) def setStopLoss(self, qty, price): """ If the price goes below a specified price, sell it all ASAP via market sell order. Input execution price per share """ return self._addOrder(ordertype = STOP_LOSS, qty = qty, price = price) def setTrailStopLossValue(self, qty, changeval): """ Sell qty bitcoins when they drop "value" below their maximum per share value since purchase. Basically a Moving Limit sell at: maxPriceSeen - value """ return self._addOrder(ordertype = TRAIL_STOP_VALUE, qty = qty, price = 0, changeval = changeval*qty) def setTrailStopLossPercent(self, qty, changeval, maxprice = 0): """ Sell qty bitcoins when they have a changepercent drop below their maximum value since purchase. Basically a Moving Limit sell at: maxPriceSeen * (1 - (changepercent/100) ) """ return self._addOrder(ordertype = TRAIL_STOP_PERCENT, qty = qty, price = maxprice, changeval = changeval/100.0) def RemoveOrder(self, orderid): """ Accepts either the orderid (starts at 0 and increments to the number of total orders) Or the CoinOrder object returned when the order was created. """ startlen = len(self.orderbook) # Remove the items if the order id matches removedOrder = None if isinstance(orderid, int): temp = [] for order in self.orderbook: if order.orderid != orderid: temp.append(order) else: removedOrder = order self.orderbook = temp if isinstance(orderid, CoinOrder): try: self.orderbook.remove(order) removedOrder = order except: pass return removedOrder