Skip to content
This repository has been archived by the owner on Nov 13, 2023. It is now read-only.

Update the bitstamp implementation to make it possible to trade all available pairs #115

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pyalgotrade/bitstamp/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
38 changes: 26 additions & 12 deletions pyalgotrade/bitstamp/httpclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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"])
Expand All @@ -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):
Expand Down Expand Up @@ -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]

Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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(
Expand Down
34 changes: 13 additions & 21 deletions pyalgotrade/bitstamp/livebroker.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,15 @@
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():
action = broker.Order.Action.SELL
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
Expand Down Expand Up @@ -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):
Expand All @@ -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.

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyalgotrade/strategy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down