diff --git a/pyalgotrade/bitstamp/common.py b/pyalgotrade/bitstamp/common.py index b91e2d70b..6c04fb880 100644 --- a/pyalgotrade/bitstamp/common.py +++ b/pyalgotrade/bitstamp/common.py @@ -23,8 +23,18 @@ logger = pyalgotrade.logger.getLogger("bitstamp") + +# deprecated btc_symbol = "BTC" +available_fiats = {"EUR", "USD"} +available_cryptos = {"BTC", "ETH", "LTC", "XRP"} +available_pairs = {"EUR": {"BTC": "BTCEUR", "ETH": "ETHEUR", "LTC": "LTCEUR", "USD": "EURUSD", "XRP": "XRPEUR"}, + "USD": {"BTC": "BTCUSD", "ETH": "ETHUSD", "EUR": "EURUSD", "LTC": "LTCUSD", "XRP": "XRPUSD"}, + "BTC": {"EUR": "BTCEUR", "USD": "BTCUSD", "ETH": "ETHBTC", "LTC": "LTCBTC", "XRP": "XRPBTC"}, + "ETH": {"EUR": "ETHEUR", "USD": "ETHUSD", "BTC": "ETHBTC"}, + "XRP": {"EUR": "XRPEUR", "USD": "XRPUSD", "BTC": "XRPBTC"}, + "LTC": {"EUR": "LTCEUR", "USD": "LTCUSD", "BTC": "LTCBTC"}} class BTCTraits(broker.InstrumentTraits): def roundQuantity(self, quantity): diff --git a/pyalgotrade/bitstamp/httpclient.py b/pyalgotrade/bitstamp/httpclient.py index a6b3ba0a5..91a031a2f 100644 --- a/pyalgotrade/bitstamp/httpclient.py +++ b/pyalgotrade/bitstamp/httpclient.py @@ -47,11 +47,16 @@ def __init__(self, jsonDict): def getDict(self): return self.__jsonDict + def getAvailableCurrency(self, currency="USD"): + return float(self.__jsonDict["{}_available".format(currency.lower())]) + def getUSDAvailable(self): - return float(self.__jsonDict["usd_available"]) + # deprecated + return self.getAvailableCurrency("USD") def getBTCAvailable(self): - return float(self.__jsonDict["btc_available"]) + # deprecated + return self.getAvailableCurrency("BTC") class Order(object): @@ -87,11 +92,19 @@ def __init__(self, jsonDict): def getDict(self): return self.__jsonDict + def getCurrency(self, currency="BTC"): + return float(self.__jsonDict[currency.lower()]) + + def getCurrencyPairPrice(self, currency1="BTC", currency2="USD"): + return float(self.__jsonDict["{}_{}".format(currency1.lower(), currency2.lower())]) + def getBTC(self): - return float(self.__jsonDict["btc"]) + # deprecated + return self.getCurrency("BTC") def getBTCUSD(self): - return float(self.__jsonDict["btc_usd"]) + # deprecated + return self.getCurrencyPairPrice("BTC", "USD") def getDateTime(self): return parse_datetime(self.__jsonDict["datetime"]) @@ -106,7 +119,8 @@ def getOrderId(self): return int(self.__jsonDict["order_id"]) def getUSD(self): - return float(self.__jsonDict["usd"]) + # deprecated + return self.getCurrency("USD") class HTTPClient(object): @@ -170,12 +184,12 @@ def _post(self, url, params): return jsonResponse def getAccountBalance(self): - url = "https://www.bitstamp.net/api/balance/" + url = "https://www.bitstamp.net/api/v2/balance/" jsonResponse = self._post(url, {}) return AccountBalance(jsonResponse) def getOpenOrders(self): - url = "https://www.bitstamp.net/api/open_orders/" + url = "https://www.bitstamp.net/api/v2/open_orders/all" jsonResponse = self._post(url, {}) return [Order(json_open_order) for json_open_order in jsonResponse] @@ -186,8 +200,8 @@ def cancelOrder(self, orderId): if jsonResponse != True: raise Exception("Failed to cancel order") - def buyLimit(self, limitPrice, quantity): - url = "https://www.bitstamp.net/api/buy/" + def buyLimit(self, limitPrice, quantity, currency="USD", instrument="BTC"): + url = "https://www.bitstamp.net/api/v2/buy/{}/".format(common.available_pairs[currency][instrument].lower()) # Rounding price to avoid 'Ensure that there are no more than 2 decimal places' # error. @@ -203,8 +217,8 @@ def buyLimit(self, limitPrice, quantity): jsonResponse = self._post(url, params) return Order(jsonResponse) - def sellLimit(self, limitPrice, quantity): - url = "https://www.bitstamp.net/api/sell/" + def sellLimit(self, limitPrice, quantity, currency="USD", instrument="BTC"): + url = "https://www.bitstamp.net/api/v2/sell/{}/".format(common.available_pairs[currency][instrument].lower()) # Rounding price to avoid 'Ensure that there are no more than 2 decimal places' # error. @@ -221,7 +235,7 @@ def sellLimit(self, limitPrice, quantity): return Order(jsonResponse) def getUserTransactions(self, transactionType=None): - url = "https://www.bitstamp.net/api/user_transactions/" + url = "https://www.bitstamp.net/api/v2/user_transactions/" jsonResponse = self._post(url, {}) if transactionType is not None: jsonUserTransactions = filter( diff --git a/pyalgotrade/bitstamp/livebroker.py b/pyalgotrade/bitstamp/livebroker.py index 5b9141c9d..d487efbc1 100644 --- a/pyalgotrade/bitstamp/livebroker.py +++ b/pyalgotrade/bitstamp/livebroker.py @@ -26,8 +26,7 @@ from pyalgotrade.bitstamp import httpclient from pyalgotrade.bitstamp import common - -def build_order_from_open_order(openOrder, instrumentTraits): +def build_order_from_open_order(openOrder, instrumentTraits, currency="USD", instrument="BTC"): if openOrder.isBuy(): action = broker.Order.Action.BUY elif openOrder.isSell(): @@ -35,7 +34,7 @@ def build_order_from_open_order(openOrder, instrumentTraits): else: raise Exception("Invalid order type") - ret = broker.LimitOrder(action, common.btc_symbol, openOrder.getPrice(), openOrder.getAmount(), instrumentTraits) + ret = broker.LimitOrder(action, instrument, openOrder.getPrice(), openOrder.getAmount(), instrumentTraits) ret.setSubmitted(openOrder.getId(), openOrder.getDateTime()) ret.setState(broker.Order.State.ACCEPTED) return ret @@ -130,8 +129,7 @@ def __init__(self, clientId, key, secret): self.__stop = False self.__httpClient = self.buildHTTPClient(clientId, key, secret) self.__tradeMonitor = TradeMonitor(self.__httpClient) - self.__cash = 0 - self.__shares = {} + self.__currencies = {} self.__activeOrders = {} def _registerOrder(self, order): @@ -149,22 +147,16 @@ def buildHTTPClient(self, clientId, key, secret): return httpclient.HTTPClient(clientId, key, secret) def refreshAccountBalance(self): - """Refreshes cash and BTC balance.""" + """Refreshes cash and crypto balances.""" self.__stop = True # Stop running in case of errors. common.logger.info("Retrieving account balance.") balance = self.__httpClient.getAccountBalance() - # Cash - self.__cash = round(balance.getUSDAvailable(), 2) - common.logger.info("%s USD" % (self.__cash)) - # BTC - btc = balance.getBTCAvailable() - if btc: - self.__shares = {common.btc_symbol: btc} - else: - self.__shares = {} - common.logger.info("%s BTC" % (btc)) + # set all fiat and crypto currency values + for currency in sorted(common.available_fiats.union(common.available_cryptos)): + self.__currencies[currency] = balance.getAvailableCurrency(currency) + common.logger.info("{} {}".format(self.__currencies[currency], currency)) self.__stop = False # No errors. Keep running. @@ -270,16 +262,16 @@ def getPositions(self): def getActiveOrders(self, instrument=None): return self.__activeOrders.values() - def submitOrder(self, order): + def submitOrder(self, order, currency="USD", instrument="BTC"): if order.isInitial(): # Override user settings based on Bitstamp limitations. order.setAllOrNone(False) order.setGoodTillCanceled(True) if order.isBuy(): - bitstampOrder = self.__httpClient.buyLimit(order.getLimitPrice(), order.getQuantity()) + bitstampOrder = self.__httpClient.buyLimit(order.getLimitPrice(), order.getQuantity(), currency, instrument) else: - bitstampOrder = self.__httpClient.sellLimit(order.getLimitPrice(), order.getQuantity()) + bitstampOrder = self.__httpClient.sellLimit(order.getLimitPrice(), order.getQuantity(), currency, instrument) order.setSubmitted(bitstampOrder.getId(), bitstampOrder.getDateTime()) self._registerOrder(order) @@ -294,8 +286,8 @@ def createMarketOrder(self, action, instrument, quantity, onClose=False): raise Exception("Market orders are not supported") def createLimitOrder(self, action, instrument, limitPrice, quantity): - if instrument != common.btc_symbol: - raise Exception("Only BTC instrument is supported") + if instrument not in common.available_cryptos: + raise Exception("Instrument {} not supported".format(instrument)) if action == broker.Order.Action.BUY_TO_COVER: action = broker.Order.Action.BUY diff --git a/pyalgotrade/strategy/__init__.py b/pyalgotrade/strategy/__init__.py index 72f5198b6..766b2e539 100644 --- a/pyalgotrade/strategy/__init__.py +++ b/pyalgotrade/strategy/__init__.py @@ -173,7 +173,7 @@ def marketOrder(self, instrument, quantity, onClose=False, goodTillCanceled=Fals self.getBroker().submitOrder(ret) return ret - def limitOrder(self, instrument, limitPrice, quantity, goodTillCanceled=False, allOrNone=False): + def limitOrder(self, instrument, limitPrice, quantity, goodTillCanceled=False, allOrNone=False, currency="USD"): """Submits a limit order. :param instrument: Instrument identifier.