Skip to content

Commit

Permalink
Add multi-currency support
Browse files Browse the repository at this point in the history
* introduced concept of money and currency
* introduced concept of base currency and quote currency
* changed model so it queries goxapi's orderbook directly
  instead of maintaining its own data (for perfomance)
* added appropriate unit tests

Note: because goxgui does not keep its own order book any more,
      order grouping and the volume column had to go temporarily
  • Loading branch information
Sebastian committed May 8, 2013
1 parent f81286c commit d6c874b
Show file tree
Hide file tree
Showing 13 changed files with 732 additions and 319 deletions.
36 changes: 36 additions & 0 deletions goxgui/currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Currency(object):
'''
Represents a currency.
'''

# The amount of decimals is used when rendering the currency.
# Also used to determine the size of a pip.
# All currencies not listed here will have 5 decimals.
__DECIMALS = {
'BTC': 8,
'JPY': 3,
}

def __init__(self, symbol):

if symbol in self.__DECIMALS:
self.__decimals = Currency.__DECIMALS[symbol]
else:
self.__decimals = 5

self.__symbol = symbol

def __eq__(self, other):
return self.symbol == other.symbol

def get_symbol(self):
return self.__symbol

def get_decimals(self):
return self.__decimals

def __str__(self):
return self.symbol

symbol = property(get_symbol, None, None, None)
decimals = property(get_decimals, None, None, None)
186 changes: 159 additions & 27 deletions goxgui/market.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,154 @@
from PyQt4.QtCore import QObject
from PyQt4.QtCore import pyqtSignal
import goxapi
import utilities
import time

from preferences import Preferences
from currency import Currency
from PyQt4.QtCore import QObject
from PyQt4.QtCore import pyqtSignal
from money import Money


class Market(QObject):
'''
Wrapper for gox object used to decouple gui code
from market implementation.
'''

# all available fiat currencies for this market
__FIAT_CURRENCIES = [
Currency('USD'),
Currency('EUR'),
Currency('JPY'),
Currency('CAD'),
Currency('GBP'),
Currency('CHF'),
Currency('RUB'),
Currency('AUD'),
Currency('SEK'),
Currency('DKK'),
Currency('HKD'),
Currency('PLN'),
Currency('CNY'),
Currency('SGD'),
Currency('THB'),
Currency('NZD'),
Currency('NOK'),
]

# constants to select order type
# when querying order book data
ORDER_TYPE_BID = 0
ORDER_TYPE_ASK = 1

signal_log = pyqtSignal(str)
signal_wallet = pyqtSignal()
signal_orderlag = pyqtSignal('long long', str)
signal_userorder = pyqtSignal('long long', 'long long', str, str, str)
signal_orderbook_changed = pyqtSignal(object)
signal_userorder = pyqtSignal(object, object, str, str, str)
signal_orderbook_changed = pyqtSignal()

def __init__(self, preferences):
QObject.__init__(self)
self.__key = ''
self.__secret = ''
self.__preferences = preferences
self.__preferences.set_fiat_currencies(Market.__FIAT_CURRENCIES)

def __create_gox(self):

# these settings are currently recommended by prof7bit
goxapi.FORCE_PROTOCOL = 'websocket'
goxapi.FORCE_HTTP_API = 'True'

# initialize config from our preferences
config = goxapi.GoxConfig("goxtool.ini")
config.set('gox', 'quote_currency',
self.__preferences.get_currency(
Preferences.CURRENCY_INDEX_QUOTE).symbol)
config.save()

# initialize secret from our preferences
secret = goxapi.Secret(config)
secret.key = self.__preferences.get_key()
secret.secret = self.__preferences.get_secret()
gox = goxapi.Gox(secret, config)

gox.signal_debug.connect(self.slot_log)
gox.signal_wallet.connect(self.slot_wallet_changed)
gox.signal_orderlag.connect(self.slot_orderlag)
gox.signal_userorder.connect(self.slot_userorder)
gox.orderbook.signal_changed.connect(self.slot_orderbook_changed)
# connect to gox' signals
gox.signal_debug.connect(self.__slot_log)
gox.signal_wallet.connect(self.__slot_wallet_changed)
gox.signal_orderlag.connect(self.__slot_orderlag)
gox.signal_userorder.connect(self.__slot_userorder)
gox.signal_fulldepth.connect(self.__slot_fulldepth)
gox.signal_depth.connect(self.__slot_depth)

return gox

# start slots
# start private methods

def __get_currency_shift(self, index):
'''
Retrieves the difference in decimal places between the
external representation (i.e. market) and the
internal representation (i.e. application).
'''
symbol = self.__preferences.get_currency(index).symbol

# in gox, BTC values have 8 decimals, just like our internal format,
# so no conversion is necessary
if symbol == 'BTC':
return 0

# in gox, JPY values have 3 decimals, so we have to add 5 decimals
# to convert to our internal format
if symbol == 'JPY':
return 5

# any other currency has 5 decimals in gox,
# so we have to add 3 decimals to convert to internal format
return 3

def __to_internal(self, index, value):
'''
Converts an external money value (integer value) into
an internal money value (a money object).
'''
shift = self.__get_currency_shift(index)
return Money(value * pow(10, shift),
self.__preferences.get_currency(index), False)

def __to_external(self, index, money):
'''
Converts an internal money value (money object) into
an external money value (integer)
'''
shift = self.__get_currency_shift(index)
return money.value / pow(10, shift)

def slot_log(self, dummy_gox, (text)):
def __slot_log(self, dummy, (text)):
self.signal_log.emit(text)

def slot_orderbook_changed(self, orderbook, dummy):
self.signal_orderbook_changed.emit(orderbook)
def __slot_fulldepth(self, dummy, data):
self.signal_orderbook_changed.emit()

def __slot_depth(self, dummy, data):
self.signal_orderbook_changed.emit()

def slot_orderlag(self, dummy_sender, (ms, text)):
def __slot_orderlag(self, dummy, (ms, text)):
self.signal_orderlag.emit(ms, text)

def slot_wallet_changed(self, dummy_gox, (text)):
def __slot_wallet_changed(self, dummy, (text)):
self.signal_wallet.emit()

def slot_userorder(self, dummy_sender, data):
def __slot_userorder(self, dummy, data):

(price, size, order_type, oid, status_message) = data

price = self.__to_internal(Preferences.CURRENCY_INDEX_QUOTE, price)
size = self.__to_internal(Preferences.CURRENCY_INDEX_BASE, size)

self.signal_userorder.emit(
price, size, order_type, oid, status_message)

# end slots
# start public methods

def start(self):
'''
Expand All @@ -82,26 +169,71 @@ def buy(self, price, size):
'''
Places buy order
'''
sizeGox = utilities.internal2gox(size, 'BTC')
priceGox = utilities.internal2gox(price, 'USD')
self.gox.buy(priceGox, sizeGox)
price = self.__to_external(Preferences.CURRENCY_INDEX_QUOTE, price)
size = self.__to_external(Preferences.CURRENCY_INDEX_BASE, size)
self.gox.buy(price, size)

def sell(self, price, size):
'''
Places sell order
'''
sizeGox = utilities.internal2gox(size, 'BTC')
priceGox = utilities.internal2gox(price, 'USD')
self.gox.sell(priceGox, sizeGox)
price = self.__to_external(Preferences.CURRENCY_INDEX_QUOTE, price)
size = self.__to_external(Preferences.CURRENCY_INDEX_BASE, size)
self.gox.sell(price, size)

def cancel(self, order_id):
'''
Cancels order
'''
self.gox.cancel(order_id)

def get_balance(self, currency):
def get_balance(self, index):
'''
Returns the account balance for the currency with the specified index.
@param index: base or quote
@return: the balance or None if no balance available for this currency
'''
symbol = self.__preferences.get_currency(index).symbol
if not symbol in self.gox.wallet:
return None

return self.__to_internal(index, self.gox.wallet[symbol])

def get_order(self, typ, index):
'''
Returns the account balance for the specified currency
Retrieves the order of the specified type with the specified index.
@param typ: ask or bid
@param index: the row index
'''
return self.gox.wallet[currency]
if typ == Market.ORDER_TYPE_ASK:
data = self.gox.orderbook.asks[index]
elif typ == Market.ORDER_TYPE_BID:
data = self.gox.orderbook.bids[index]
else:
raise Exception('invalid order type {}'.format(typ))

price = self.__to_internal(
Preferences.CURRENCY_INDEX_QUOTE,
int(data.price))

volume = self.__to_internal(
Preferences.CURRENCY_INDEX_BASE,
int(data.volume))

return [price, volume]

def get_order_count(self, typ):
'''
Retrieves the amount of orders available of the specified type.
@param typ: ask or bid
'''
if not hasattr(self, 'gox'):
return 0

if typ == Market.ORDER_TYPE_ASK:
return len(self.gox.orderbook.asks)

if typ == Market.ORDER_TYPE_BID:
return len(self.gox.orderbook.bids)

raise Exception('invalid order type {}'.format(typ))
Loading

0 comments on commit d6c874b

Please sign in to comment.