From d6c874b2ddea3c673234a388435995fdc7896e4e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 8 May 2013 19:01:14 +0200 Subject: [PATCH] Add multi-currency support * 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 --- goxgui/currency.py | 36 ++++++++ goxgui/market.py | 186 ++++++++++++++++++++++++++++++++------ goxgui/model.py | 152 ++++++++++++++----------------- goxgui/money.py | 94 +++++++++++++++++++ goxgui/preferences.py | 91 ++++++++++++++----- goxgui/ui/main_window.ui | 66 +++++++++----- goxgui/ui/main_window_.py | 62 +++++++------ goxgui/ui/preferences.ui | 2 +- goxgui/ui/preferences_.py | 6 +- goxgui/utilities.py | 68 -------------- goxgui/view.py | 174 ++++++++++++++++++++++------------- unittest/currency_test.py | 15 +++ unittest/money_test.py | 99 ++++++++++++++++++++ 13 files changed, 732 insertions(+), 319 deletions(-) create mode 100644 goxgui/currency.py create mode 100644 goxgui/money.py create mode 100644 unittest/currency_test.py create mode 100644 unittest/money_test.py diff --git a/goxgui/currency.py b/goxgui/currency.py new file mode 100644 index 0000000..5d76453 --- /dev/null +++ b/goxgui/currency.py @@ -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) diff --git a/goxgui/market.py b/goxgui/market.py index 64e497c..90e61d8 100644 --- a/goxgui/market.py +++ b/goxgui/market.py @@ -1,9 +1,12 @@ -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): ''' @@ -11,57 +14,141 @@ class Market(QObject): 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): ''' @@ -82,17 +169,17 @@ 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): ''' @@ -100,8 +187,53 @@ def cancel(self, order_id): ''' 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)) diff --git a/goxgui/model.py b/goxgui/model.py index 6193042..acb1ead 100644 --- a/goxgui/model.py +++ b/goxgui/model.py @@ -1,9 +1,11 @@ +import abc + from PyQt4.QtCore import QAbstractTableModel from PyQt4.QtCore import Qt from PyQt4.QtCore import QVariant from PyQt4.QtCore import SIGNAL -import utilities -import abc +from market import Market +from preferences import Preferences class Model(QAbstractTableModel): @@ -11,82 +13,26 @@ class Model(QAbstractTableModel): Model representing a collection of orders. ''' - # orders smaller than this value will be grouped - GROUP_ORDERS = 0.6 - - def __init__(self, parent, market, headerdata): + def __init__(self, parent): QAbstractTableModel.__init__(self, parent) - self.__market = market - self.__market.signal_orderbook_changed.connect(self.slot_changed) - self.__headerdata = headerdata - self.__data = [] - # start slots + # private methods - def slot_changed(self, orderbook): - self.__data = self.__parse_data(orderbook) + def _slot_changed(self): self.emit(SIGNAL("layoutChanged()")) - # end slots - - def __parse_data(self, book): - ''' - Parses the incoming data from gox, - converts money values to our internal money format. - ''' - data_in = self._get_data_from_book(book) - data_out = [] - - total = 0 - count = 1 - vwap = 0 - vsize = 0 - for x in data_in: - - price = x.price - size = x.volume - - vsize += size - vwap += price * size - - total += size - if vsize > utilities.float2internal(self.GROUP_ORDERS): - vwap = utilities.gox2internal(vwap / vsize, 'USD') - vsize = utilities.gox2internal(vsize, 'BTC') - total = utilities.gox2internal(total, 'BTC') - data_out.append([vwap, vsize, total]) - count = 1 - vwap = 0 - vsize = 0 - else: - count += 1 - - return data_out - @abc.abstractmethod - def _get_data_from_book(self, book): - ''' - This method retrieves the orders relevant to this - specific model from the order book. - ''' - return [] - - def get_price(self, index): - return self.__data[index][0] - - def get_size(self, index): - return self.__data[index][1] - - def get_total(self, index): - return self.__data[index][2] + def _get_header(self): + pass - # START Qt methods + # Qt methods + @abc.abstractmethod def rowCount(self, parent): - return len(self.__data) + pass def columnCount(self, parent): - return len(self.__headerdata) + return len(self._get_header()) def data(self, index, role): @@ -100,35 +46,71 @@ def data(self, index, role): col = index.column() if col == 0: - return QVariant(utilities.internal2str(self.get_price(row), 5)) + return QVariant(str(self.get_price(row))) if col == 1: - return QVariant(utilities.internal2str(self.get_size(row))) - if col == 2: - return QVariant(utilities.internal2str(self.get_total(row))) + return QVariant(str(self.get_size(row))) def headerData(self, col, orientation, role): if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return QVariant(self.__headerdata[col]) + return QVariant(self._get_header()[col]) return QVariant() - # END Qt methods + # public methods + @abc.abstractmethod + def get_price(self, index): + pass + + @abc.abstractmethod + def get_size(self, index): + pass -class ModelAsk(Model): - def __init__(self, parent, gox): - Model.__init__(self, parent, gox, ['Ask $', 'Size ' + utilities.BITCOIN_SYMBOL, - 'Total ' + utilities.BITCOIN_SYMBOL]) +class ModelBid(Model): - def _get_data_from_book(self, book): - return book.asks + def __init__(self, parent, market, preferences): + Model.__init__(self, parent) + self.__market = market + self.__preferences = preferences + self.__market.signal_orderbook_changed.connect(self._slot_changed) + def _get_header(self): + symbolQuote = self.__preferences.get_currency( + Preferences.CURRENCY_INDEX_QUOTE).symbol + symbolBase = self.__preferences.get_currency( + Preferences.CURRENCY_INDEX_BASE).symbol + return ['Bid ' + symbolQuote, 'Size ' + symbolBase] + + def get_price(self, index): + return self.__market.get_order(Market.ORDER_TYPE_BID, index)[0] + + def get_size(self, index): + return self.__market.get_order(Market.ORDER_TYPE_BID, index)[1] + + def rowCount(self, parent): + return self.__market.get_order_count(Market.ORDER_TYPE_BID) -class ModelBid(Model): - def __init__(self, parent, gox): - Model.__init__(self, parent, gox, ['Bid $', 'Size ' + utilities.BITCOIN_SYMBOL, - 'Total ' + utilities.BITCOIN_SYMBOL]) +class ModelAsk(Model): + + def __init__(self, parent, market, preferences): + Model.__init__(self, parent) + self.__market = market + self.__preferences = preferences + self.__market.signal_orderbook_changed.connect(self._slot_changed) - def _get_data_from_book(self, book): - return book.bids + def _get_header(self): + symbolQuote = self.__preferences.get_currency( + Preferences.CURRENCY_INDEX_QUOTE).symbol + symbolBase = self.__preferences.get_currency( + Preferences.CURRENCY_INDEX_BASE).symbol + return ['Ask ' + symbolQuote, 'Size ' + symbolBase] + + def get_price(self, index): + return self.__market.get_order(Market.ORDER_TYPE_ASK, index)[0] + + def get_size(self, index): + return self.__market.get_order(Market.ORDER_TYPE_ASK, index)[1] + + def rowCount(self, parent): + return self.__market.get_order_count(Market.ORDER_TYPE_ASK) diff --git a/goxgui/money.py b/goxgui/money.py new file mode 100644 index 0000000..e192c6a --- /dev/null +++ b/goxgui/money.py @@ -0,0 +1,94 @@ +from currency import Currency + + +class Money(object): + ''' + Represents a money value including various attributes + such as currency type, number of decimals to render, etc. + ''' + __INTERNAL_DECIMALS = 8 + __INTERNAL_FACTOR = pow(10, __INTERNAL_DECIMALS) + + def __init__(self, value=0, currency=Currency('BTC'), shift=True): + + if shift: + self.__value = long(value * Money.__INTERNAL_FACTOR) + else: + self.__value = long(value) + + self.__currency = currency + self.__symbol = '' + + def __str__(self): + ''' + Returns the current money value as a string. + ''' + return '{{:,.{}f}}'.format(self.decimals).format(self.to_float()) + + def to_long_string(self): + ''' + Returns the current money value as a string including currency type + ''' + return '{} {}'.format(self.__str__(), self.__currency) + + def to_float(self): + ''' + Returns the money value as a float. + ''' + return self.value / float(Money.__INTERNAL_FACTOR) + + def __mul__(self, other): + ''' + Multiplies the current money value with the specified other + money value. The result will have the currency of the current value. + ''' + return Money(self.value * other.value / Money.__INTERNAL_FACTOR, + self.currency, False) + + def __div__(self, other): + ''' + Divides the current money value by the specified other money value. + The result will have the currency of the current value. + ''' + return Money(self.value * Money.__INTERNAL_FACTOR / other.value, + self.currency, False) + + def __add__(self, other): + ''' + Adds another money value to this one. + The resulting money value will have the same type as this one. + ''' + return Money(self.value + other.value, self.currency, False) + + def __sub__(self, other): + ''' + Subtracts another money value from this one. + The resulting money value will have the same type as this one. + ''' + return Money(self.value - other.value, self.currency, False) + + def pip(self): + ''' + Returns a money value that is the equivalent of one pip. + ''' + return Money(pow(10, -self.decimals), self.currency) + + def get_currency(self): + return self.__currency + + def set_currency(self, currency): + self.__currency = currency + + def get_value(self): + return self.__value + + def get_decimals(self): + return self.__currency.decimals + + def get_symbol(self): + return self.__currency.symbol + + currency = property(get_currency, set_currency, None, None) + value = property(get_value, None, None, None) + decimals = property(get_decimals, None, None, None) + symbol = property(get_symbol, None, None, None) diff --git a/goxgui/preferences.py b/goxgui/preferences.py index 826a972..9e9e1a0 100644 --- a/goxgui/preferences.py +++ b/goxgui/preferences.py @@ -6,6 +6,7 @@ from PyQt4.QtGui import QDialogButtonBox from ui.preferences_ import Ui_Preferences from PyQt4 import QtGui +from currency import Currency class Preferences(QDialog): @@ -13,9 +14,12 @@ class Preferences(QDialog): Represents the application preferences. ''' - PASSPHRASE = 'fffuuuuuuu' - FILENAME = 'goxgui.ini' - SECTION_GLOBAL = 'Global' + CURRENCY_INDEX_BASE = 1 + CURRENCY_INDEX_QUOTE = 2 + + __PASSPHRASE = 'fffuuuuuuu' + __FILENAME = 'goxgui.ini' + __SECTION_GLOBAL = 'Global' def __init__(self): QDialog.__init__(self) @@ -38,12 +42,14 @@ def __init__(self): self.configparser = RawConfigParser() # __load or (if non-existent) create config file - if path.isfile(self.FILENAME): + if path.isfile(self.__FILENAME): self.__load() else: self.__init_with_defaults() self.__save() + self.set_fiat_currencies([]) + # start slots def __slot_validate_credentials(self): @@ -74,21 +80,38 @@ def __slot_validate_credentials(self): # start private methods + def get_fiat_currency_index(self, other): + ''' + Returns the index of the given currency in the + fiat currency list. + ''' + index = 0 + for currency in self.__fiat_currencies: + if currency == other: + return index + index += 1 + raise Exception('Currency {} not found.'.format(other.symbol)) + def __init_with_defaults(self): - self.configparser.add_section(self.SECTION_GLOBAL) - self.set('currency_a', 'BTC') - self.set('currency_b', 'USD') - self.set('key', '') - self.set('secret', '') + self.configparser.add_section(self.__SECTION_GLOBAL) + self.set_currency(Preferences.CURRENCY_INDEX_BASE, Currency('BTC')) + self.set_currency(Preferences.CURRENCY_INDEX_QUOTE, Currency('USD')) + self.__set_key('') + self.__set_secret('') def __load_to_gui(self): self.__ui.lineEditKey.setText(self.get_key()) self.__ui.lineEditSecret.setText(self.get_secret()) + quoteCurrency = self.get_currency(Preferences.CURRENCY_INDEX_QUOTE) + index = self.get_fiat_currency_index(quoteCurrency) + self.__ui.comboBoxCurrency.setCurrentIndex(index) self.__set_status('') def __save_from_gui(self): self.__set_key(str(self.__ui.lineEditKey.text())) self.__set_secret(str(self.__ui.lineEditSecret.text())) + quoteCurrency = Currency(str(self.__ui.comboBoxCurrency.currentText())) + self.set_currency(Preferences.CURRENCY_INDEX_QUOTE, quoteCurrency) def __disable_ok(self, text): self.__ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) @@ -102,28 +125,28 @@ def __save(self): ''' Saves the config to the .ini file ''' - with open(self.FILENAME, 'wb') as configfile: + with open(self.__FILENAME, 'wb') as configfile: self.configparser.write(configfile) def __load(self): ''' Loads or reloads the config from the .ini file ''' - self.configparser.read(self.FILENAME) + self.configparser.read(self.__FILENAME) def __set_key(self, key): ''' Writes the specified key to the configuration file. ''' - self.set('key', key) + self.__set('key', key) def __set_secret(self, secret): ''' Writes the specified secret to the configuration file (encrypted). ''' if secret != '': - secret = utilities.encrypt(secret, Preferences.PASSPHRASE) - self.set('secret', secret) + secret = utilities.encrypt(secret, Preferences.__PASSPHRASE) + self.__set('secret', secret) def __set_status(self, text): self.__ui.labelStatus.setText(text) @@ -144,37 +167,55 @@ def __adjust_for_mac(self): self.__ui.labelKeySecret.setFont(fontB) self.__ui.labelCurrency.setFont(fontB) - # end private methods - - # start public methods - - def get(self, key): + def __get(self, key): ''' Retrieves a property from the global section ''' - return self.configparser.get(self.SECTION_GLOBAL, key) + return self.configparser.get(self.__SECTION_GLOBAL, key) - def set(self, key, value): + def __set(self, key, value): ''' Stores a property to the global section ''' - self.configparser.set(self.SECTION_GLOBAL, key, value) + self.configparser.set(self.__SECTION_GLOBAL, key, value) + + # end private methods + + # start public methods + + def set_fiat_currencies(self, currencies): + ''' + Sets the fiat currencies available in the preferences dialog. + ''' + self.__fiat_currencies = currencies + + self.__ui.comboBoxCurrency.clear() + index = 0 + for x in self.__fiat_currencies: + self.__ui.comboBoxCurrency.insertItem(index, x.symbol) + index += 1 + + def set_currency(self, index, currency): + return self.__set('currency_{}'.format(index), currency.symbol) + + def get_currency(self, index): + return Currency(self.__get('currency_{}'.format(index))) def get_key(self): ''' Loads the key from the configuration file. ''' - return self.get('key') + return self.__get('key') def get_secret(self): ''' Loads the secret from the configuration file. ''' - secret = self.get('secret') + secret = self.__get('secret') if secret == '': return secret - return utilities.decrypt(secret, Preferences.PASSPHRASE) + return utilities.decrypt(secret, Preferences.__PASSPHRASE) def show(self): ''' diff --git a/goxgui/ui/main_window.ui b/goxgui/ui/main_window.ui index 5e95b26..ce0477c 100644 --- a/goxgui/ui/main_window.ui +++ b/goxgui/ui/main_window.ui @@ -12,12 +12,12 @@ 0 0 - 886 - 790 + 904 + 858 - MtGox Trading UI + goxgui @@ -58,11 +58,14 @@ 0 + + PointingHandCursor + Push to use this value - BTC + - @@ -113,11 +116,14 @@ 0 + + PointingHandCursor + Push to use this value - USD + - @@ -204,6 +210,9 @@ + + PointingHandCursor + Recalculate size @@ -213,15 +222,18 @@ - + 0 0 + + PointingHandCursor + - Size (in BTC) + Size Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -233,12 +245,15 @@ 8 - 999999.000000000000000 + 999999999.000000000000000 + + PointingHandCursor + Suggest price @@ -258,8 +273,11 @@ 0 + + PointingHandCursor + - Price (in USD) + Price Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -271,15 +289,18 @@ 5 - 9999.000000000000000 + 999999999.000000000000000 - 0.010000000000000 + 1.000000000000000 + + PointingHandCursor + Recalculate total trade worth @@ -296,8 +317,11 @@ 0 + + PointingHandCursor + - Total trade worth (in USD) + Total trade worth Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -309,10 +333,10 @@ 5 - 99999999.000000000000000 + 999999999.000000000000000 - 100.000000000000000 + 1.000000000000000 @@ -415,10 +439,10 @@ false - 133 + 200 - 133 + 200 true @@ -482,10 +506,10 @@ false - 133 + 200 - 133 + 200 true @@ -604,8 +628,8 @@ 0 0 - 886 - 18 + 904 + 22 @@ -628,7 +652,7 @@ - doubleSpinBoxBtc + doubleSpinBoxSize doubleSpinBoxPrice doubleSpinBoxTotal lineEditOrder diff --git a/goxgui/ui/main_window_.py b/goxgui/ui/main_window_.py index 6777bb1..c4adb20 100644 --- a/goxgui/ui/main_window_.py +++ b/goxgui/ui/main_window_.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file '/Users/user/git/goxgui/goxgui/ui/main_window.ui' # -# Created: Sat May 4 09:28:34 2013 +# Created: Mon May 6 19:37:18 2013 # by: PyQt4 UI code generator 4.9.4 # # WARNING! All changes made in this file will be lost! @@ -19,7 +19,7 @@ def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.setWindowModality(QtCore.Qt.WindowModal) MainWindow.setEnabled(True) - MainWindow.resize(886, 790) + MainWindow.resize(904, 858) MainWindow.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates)) self.widgetMain = QtGui.QWidget(MainWindow) self.widgetMain.setEnabled(True) @@ -42,6 +42,7 @@ def setupUi(self, MainWindow): self.pushButtonWalletA = QtGui.QPushButton(self.groupBoxAccount) self.pushButtonWalletA.setEnabled(False) self.pushButtonWalletA.setMinimumSize(QtCore.QSize(200, 0)) + self.pushButtonWalletA.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.pushButtonWalletA.setObjectName(_fromUtf8("pushButtonWalletA")) self.horizontalLayout_2.addWidget(self.pushButtonWalletA) spacerItem = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) @@ -55,6 +56,7 @@ def setupUi(self, MainWindow): self.pushButtonWalletB = QtGui.QPushButton(self.groupBoxAccount) self.pushButtonWalletB.setEnabled(False) self.pushButtonWalletB.setMinimumSize(QtCore.QSize(200, 0)) + self.pushButtonWalletB.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.pushButtonWalletB.setObjectName(_fromUtf8("pushButtonWalletB")) self.horizontalLayout_2.addWidget(self.pushButtonWalletB) self.horizontalLayout.addLayout(self.horizontalLayout_2) @@ -95,21 +97,24 @@ def setupUi(self, MainWindow): self.radioButtonSell.setObjectName(_fromUtf8("radioButtonSell")) self.horizontalLayout_4.addWidget(self.radioButtonSell) self.pushButtonSize = QtGui.QPushButton(self.groupBoxTrading) + self.pushButtonSize.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.pushButtonSize.setObjectName(_fromUtf8("pushButtonSize")) self.horizontalLayout_4.addWidget(self.pushButtonSize) - self.doubleSpinBoxBtc = QtGui.QDoubleSpinBox(self.groupBoxTrading) + self.doubleSpinBoxSize = QtGui.QDoubleSpinBox(self.groupBoxTrading) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.doubleSpinBoxBtc.sizePolicy().hasHeightForWidth()) - self.doubleSpinBoxBtc.setSizePolicy(sizePolicy) - self.doubleSpinBoxBtc.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.doubleSpinBoxBtc.setButtonSymbols(QtGui.QAbstractSpinBox.UpDownArrows) - self.doubleSpinBoxBtc.setDecimals(8) - self.doubleSpinBoxBtc.setMaximum(999999.0) - self.doubleSpinBoxBtc.setObjectName(_fromUtf8("doubleSpinBoxBtc")) - self.horizontalLayout_4.addWidget(self.doubleSpinBoxBtc) + sizePolicy.setHeightForWidth(self.doubleSpinBoxSize.sizePolicy().hasHeightForWidth()) + self.doubleSpinBoxSize.setSizePolicy(sizePolicy) + self.doubleSpinBoxSize.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) + self.doubleSpinBoxSize.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.doubleSpinBoxSize.setButtonSymbols(QtGui.QAbstractSpinBox.UpDownArrows) + self.doubleSpinBoxSize.setDecimals(8) + self.doubleSpinBoxSize.setMaximum(999999999.0) + self.doubleSpinBoxSize.setObjectName(_fromUtf8("doubleSpinBoxSize")) + self.horizontalLayout_4.addWidget(self.doubleSpinBoxSize) self.pushButtonPrice = QtGui.QPushButton(self.groupBoxTrading) + self.pushButtonPrice.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.pushButtonPrice.setObjectName(_fromUtf8("pushButtonPrice")) self.horizontalLayout_4.addWidget(self.pushButtonPrice) self.doubleSpinBoxPrice = QtGui.QDoubleSpinBox(self.groupBoxTrading) @@ -119,14 +124,16 @@ def setupUi(self, MainWindow): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.doubleSpinBoxPrice.sizePolicy().hasHeightForWidth()) self.doubleSpinBoxPrice.setSizePolicy(sizePolicy) + self.doubleSpinBoxPrice.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.doubleSpinBoxPrice.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.doubleSpinBoxPrice.setButtonSymbols(QtGui.QAbstractSpinBox.UpDownArrows) self.doubleSpinBoxPrice.setDecimals(5) - self.doubleSpinBoxPrice.setMaximum(9999.0) - self.doubleSpinBoxPrice.setSingleStep(0.01) + self.doubleSpinBoxPrice.setMaximum(999999999.0) + self.doubleSpinBoxPrice.setSingleStep(1.0) self.doubleSpinBoxPrice.setObjectName(_fromUtf8("doubleSpinBoxPrice")) self.horizontalLayout_4.addWidget(self.doubleSpinBoxPrice) self.pushButtonTotal = QtGui.QPushButton(self.groupBoxTrading) + self.pushButtonTotal.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.pushButtonTotal.setObjectName(_fromUtf8("pushButtonTotal")) self.horizontalLayout_4.addWidget(self.pushButtonTotal) self.doubleSpinBoxTotal = QtGui.QDoubleSpinBox(self.groupBoxTrading) @@ -135,11 +142,12 @@ def setupUi(self, MainWindow): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.doubleSpinBoxTotal.sizePolicy().hasHeightForWidth()) self.doubleSpinBoxTotal.setSizePolicy(sizePolicy) + self.doubleSpinBoxTotal.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.doubleSpinBoxTotal.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.doubleSpinBoxTotal.setButtonSymbols(QtGui.QAbstractSpinBox.UpDownArrows) self.doubleSpinBoxTotal.setDecimals(5) - self.doubleSpinBoxTotal.setMaximum(99999999.0) - self.doubleSpinBoxTotal.setSingleStep(100.0) + self.doubleSpinBoxTotal.setMaximum(999999999.0) + self.doubleSpinBoxTotal.setSingleStep(1.0) self.doubleSpinBoxTotal.setObjectName(_fromUtf8("doubleSpinBoxTotal")) self.horizontalLayout_4.addWidget(self.doubleSpinBoxTotal) self.pushButtonGo = QtGui.QPushButton(self.groupBoxTrading) @@ -182,8 +190,8 @@ def setupUi(self, MainWindow): self.tableBid.setWordWrap(False) self.tableBid.setCornerButtonEnabled(False) self.tableBid.setObjectName(_fromUtf8("tableBid")) - self.tableBid.horizontalHeader().setDefaultSectionSize(133) - self.tableBid.horizontalHeader().setMinimumSectionSize(133) + self.tableBid.horizontalHeader().setDefaultSectionSize(200) + self.tableBid.horizontalHeader().setMinimumSectionSize(200) self.tableBid.horizontalHeader().setStretchLastSection(True) self.tableBid.verticalHeader().setVisible(False) self.tableBid.verticalHeader().setDefaultSectionSize(20) @@ -208,8 +216,8 @@ def setupUi(self, MainWindow): self.tableAsk.setWordWrap(False) self.tableAsk.setCornerButtonEnabled(False) self.tableAsk.setObjectName(_fromUtf8("tableAsk")) - self.tableAsk.horizontalHeader().setDefaultSectionSize(133) - self.tableAsk.horizontalHeader().setMinimumSectionSize(133) + self.tableAsk.horizontalHeader().setDefaultSectionSize(200) + self.tableAsk.horizontalHeader().setMinimumSectionSize(200) self.tableAsk.horizontalHeader().setStretchLastSection(True) self.tableAsk.verticalHeader().setVisible(False) self.tableAsk.verticalHeader().setDefaultSectionSize(20) @@ -262,7 +270,7 @@ def setupUi(self, MainWindow): self.statusbar.setObjectName(_fromUtf8("statusbar")) MainWindow.setStatusBar(self.statusbar) self.menuBar = QtGui.QMenuBar(MainWindow) - self.menuBar.setGeometry(QtCore.QRect(0, 0, 886, 18)) + self.menuBar.setGeometry(QtCore.QRect(0, 0, 904, 22)) self.menuBar.setObjectName(_fromUtf8("menuBar")) self.menuOptions = QtGui.QMenu(self.menuBar) self.menuOptions.setObjectName(_fromUtf8("menuOptions")) @@ -276,30 +284,30 @@ def setupUi(self, MainWindow): self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) - MainWindow.setTabOrder(self.doubleSpinBoxBtc, self.doubleSpinBoxPrice) + MainWindow.setTabOrder(self.doubleSpinBoxSize, self.doubleSpinBoxPrice) MainWindow.setTabOrder(self.doubleSpinBoxPrice, self.doubleSpinBoxTotal) MainWindow.setTabOrder(self.doubleSpinBoxTotal, self.lineEditOrder) def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MtGox Trading UI", None, QtGui.QApplication.UnicodeUTF8)) + MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "goxgui", None, QtGui.QApplication.UnicodeUTF8)) self.groupBoxAccount.setTitle(QtGui.QApplication.translate("MainWindow", "Account", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonWalletA.setToolTip(QtGui.QApplication.translate("MainWindow", "Push to use this value", None, QtGui.QApplication.UnicodeUTF8)) - self.pushButtonWalletA.setText(QtGui.QApplication.translate("MainWindow", "BTC", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButtonWalletA.setText(QtGui.QApplication.translate("MainWindow", "-", None, QtGui.QApplication.UnicodeUTF8)) self.labelOrderlag.setToolTip(QtGui.QApplication.translate("MainWindow", "MtGox trading lag (how long it takes for an order to be executed)", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonWalletB.setToolTip(QtGui.QApplication.translate("MainWindow", "Push to use this value", None, QtGui.QApplication.UnicodeUTF8)) - self.pushButtonWalletB.setText(QtGui.QApplication.translate("MainWindow", "USD", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButtonWalletB.setText(QtGui.QApplication.translate("MainWindow", "-", None, QtGui.QApplication.UnicodeUTF8)) self.groupBoxTrading.setTitle(QtGui.QApplication.translate("MainWindow", "Trading", None, QtGui.QApplication.UnicodeUTF8)) self.radioButtonBuy.setText(QtGui.QApplication.translate("MainWindow", "Buy", None, QtGui.QApplication.UnicodeUTF8)) self.radioButtonSell.setText(QtGui.QApplication.translate("MainWindow", "Sell", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonSize.setToolTip(QtGui.QApplication.translate("MainWindow", "Recalculate size", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonSize.setText(QtGui.QApplication.translate("MainWindow", "Size", None, QtGui.QApplication.UnicodeUTF8)) - self.doubleSpinBoxBtc.setToolTip(QtGui.QApplication.translate("MainWindow", "Size (in BTC)", None, QtGui.QApplication.UnicodeUTF8)) + self.doubleSpinBoxSize.setToolTip(QtGui.QApplication.translate("MainWindow", "Size", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonPrice.setToolTip(QtGui.QApplication.translate("MainWindow", "Suggest price", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonPrice.setText(QtGui.QApplication.translate("MainWindow", "Price", None, QtGui.QApplication.UnicodeUTF8)) - self.doubleSpinBoxPrice.setToolTip(QtGui.QApplication.translate("MainWindow", "Price (in USD)", None, QtGui.QApplication.UnicodeUTF8)) + self.doubleSpinBoxPrice.setToolTip(QtGui.QApplication.translate("MainWindow", "Price", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonTotal.setToolTip(QtGui.QApplication.translate("MainWindow", "Recalculate total trade worth", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonTotal.setText(QtGui.QApplication.translate("MainWindow", "Total", None, QtGui.QApplication.UnicodeUTF8)) - self.doubleSpinBoxTotal.setToolTip(QtGui.QApplication.translate("MainWindow", "Total trade worth (in USD)", None, QtGui.QApplication.UnicodeUTF8)) + self.doubleSpinBoxTotal.setToolTip(QtGui.QApplication.translate("MainWindow", "Total trade worth", None, QtGui.QApplication.UnicodeUTF8)) self.pushButtonGo.setText(QtGui.QApplication.translate("MainWindow", "Go!", None, QtGui.QApplication.UnicodeUTF8)) self.lineEditOrder.setToolTip(QtGui.QApplication.translate("MainWindow", "Paste order ID here or click link above", None, QtGui.QApplication.UnicodeUTF8)) self.lineEditOrder.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Order ID", None, QtGui.QApplication.UnicodeUTF8)) diff --git a/goxgui/ui/preferences.ui b/goxgui/ui/preferences.ui index 132d71d..5b8b985 100644 --- a/goxgui/ui/preferences.ui +++ b/goxgui/ui/preferences.ui @@ -205,7 +205,7 @@ - false + true Currency diff --git a/goxgui/ui/preferences_.py b/goxgui/ui/preferences_.py index 079b0c0..27d32cd 100644 --- a/goxgui/ui/preferences_.py +++ b/goxgui/ui/preferences_.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file '/Users/user/git/goxgui/goxgui/ui/preferences.ui' # -# Created: Sat May 4 10:04:18 2013 +# Created: Mon May 6 18:34:36 2013 # by: PyQt4 UI code generator 4.9.4 # # WARNING! All changes made in this file will be lost! @@ -91,7 +91,7 @@ def setupUi(self, Preferences): self.verticalLayout_5 = QtGui.QVBoxLayout(self.tabVarious) self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) self.groupBoxCurrency = QtGui.QGroupBox(self.tabVarious) - self.groupBoxCurrency.setEnabled(False) + self.groupBoxCurrency.setEnabled(True) self.groupBoxCurrency.setFlat(True) self.groupBoxCurrency.setObjectName(_fromUtf8("groupBoxCurrency")) self.gridLayout_3 = QtGui.QGridLayout(self.groupBoxCurrency) @@ -133,7 +133,7 @@ def setupUi(self, Preferences): self.verticalLayout_2.addLayout(self.horizontalLayout) self.retranslateUi(Preferences) - self.tabWidget.setCurrentIndex(0) + self.tabWidget.setCurrentIndex(1) QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), Preferences.accept) QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), Preferences.reject) QtCore.QMetaObject.connectSlotsByName(Preferences) diff --git a/goxgui/utilities.py b/goxgui/utilities.py index d40991e..9ba268d 100644 --- a/goxgui/utilities.py +++ b/goxgui/utilities.py @@ -23,74 +23,6 @@ BITCOIN_SYMBOL = unichr(3647) -def gox2internal(value, currency): - ''' - Converts the given value from gox format - (see https://en.bitcoin.it/wiki/MtGox/API) - into the application's internal format. - ''' - if currency == 'BTC': - return value * FACTOR_GOX_BTC - if currency == 'JPY': - return value * FACTOR_GOX_JPY - else: - return value * FACTOR_GOX_USD - - -def internal2gox(value, currency): - ''' - Converts the specified value from internal format to gox format - ''' - if currency == 'BTC': - return value / FACTOR_GOX_BTC - if currency == 'JPY': - return value / FACTOR_GOX_JPY - else: - return value / FACTOR_GOX_USD - - -def float2str(value, decimals=8): - ''' - Returns currency float formatted as a string. - ''' - return ('{{:,.{0}f}}'.format(decimals).format(value)) - - -def internal2float(value): - ''' - Converts internal value to float. - ''' - return value / float(FACTOR_FLOAT) - - -def float2internal(value): - ''' - Converts float to internal value. - ''' - return int(value * FACTOR_FLOAT) - - -def multiply_internal(valueA, valueB): - ''' - Multiplies two values in internal format. - ''' - return valueA * valueB / FACTOR_FLOAT - - -def divide_internal(valueA, valueB): - ''' - Divides two values in internal format (value a / value b). - ''' - return valueA * FACTOR_FLOAT / valueB - - -def internal2str(value, decimals=8): - ''' - Returns currency float formatted as a string. - ''' - return float2str(internal2float(value), decimals) - - def encrypt(secret, password): ''' Encrypts the specified secret using the specified password. diff --git a/goxgui/view.py b/goxgui/view.py index 992d804..8f9fcac 100644 --- a/goxgui/view.py +++ b/goxgui/view.py @@ -8,6 +8,8 @@ from model import ModelAsk from model import ModelBid from PyQt4 import QtGui +from money import Money +from preferences import Preferences class View(QMainWindow): @@ -15,11 +17,13 @@ class View(QMainWindow): Represents the combined view / control. ''' - # how the application-proposed bid will differ from the selected bid - ADD_TO_BID = 1000 + # how the application-proposed bid + # will differ from the selected bid (in pips) + ADD_TO_BID_PIPS = 1 - # how the application-proposed ask will differ from the selected ask - SUB_FROM_ASK = 1000 + # how the application-proposed ask + # will differ from the selected ask (in pips) + SUB_FROM_ASK_PIPS = 1 def __init__(self, preferences, market): @@ -46,9 +50,9 @@ def __init__(self, preferences, market): self.ui.pushButtonGo.released.connect( self.execute_trade) self.ui.tableAsk.clicked.connect( - self.update_price_from_asks) + self.slot_update_price_from_asks) self.ui.tableBid.clicked.connect( - self.update_price_from_bids) + self.slot_update_price_from_bids) self.ui.pushButtonCancel.released.connect( self.cancel_order) self.ui.textBrowserStatus.anchorClicked.connect( @@ -67,9 +71,9 @@ def __init__(self, preferences, market): self.show_preferences) # initialize and connect bid / ask table models - self.modelAsk = ModelAsk(self, self.market) + self.modelAsk = ModelAsk(self, self.market, preferences) self.ui.tableAsk.setModel(self.modelAsk) - self.modelBid = ModelBid(self, self.market) + self.modelBid = ModelBid(self, self.market, preferences) self.ui.tableBid.setModel(self.modelBid) # associate log channels with their check boxes @@ -79,6 +83,9 @@ def __init__(self, preferences, market): [self.ui.checkBoxLogDepth, 'depth'], ] + # resets dynamic ui elements + self.reset() + # activate market self.market.start() @@ -87,6 +94,24 @@ def __init__(self, preferences, market): self.show() self.raise_() + def reset(self): + + # initialize wallet values + self.set_wallet_a(None) + self.set_wallet_b(None) + + # adjust decimal values to current currencies + self.adjust_decimals() + + def adjust_decimals(self): + currencyQuote = self.preferences.get_currency( + Preferences.CURRENCY_INDEX_QUOTE) + currencyBase = self.preferences.get_currency( + Preferences.CURRENCY_INDEX_BASE) + self.ui.doubleSpinBoxSize.setDecimals(currencyBase.decimals) + self.ui.doubleSpinBoxPrice.setDecimals(currencyQuote.decimals) + self.ui.doubleSpinBoxTotal.setDecimals(currencyQuote.decimals) + def adjust_for_mac(self): ''' Fixes some stuff that looks good on windows but bad on mac. @@ -98,7 +123,7 @@ def adjust_for_mac(self): self.ui.textBrowserLog.setFont(font) self.ui.textBrowserStatus.setFont(font) self.ui.lineEditOrder.setFont(font) - self.ui.doubleSpinBoxBtc.setFont(font) + self.ui.doubleSpinBoxSize.setFont(font) self.ui.doubleSpinBoxPrice.setFont(font) self.ui.doubleSpinBoxTotal.setFont(font) @@ -115,6 +140,7 @@ def show_preferences(self): self.status_message('Preferences changed, restarting market.') self.market.stop() self.preferences.apply() + self.reset() self.market.start() self.status_message('Market restarted successfully.') @@ -159,39 +185,52 @@ def status_message(self, text): self.ui.textBrowserStatus.moveCursor(QTextCursor.End) self.ui.textBrowserStatus.append(text) - def set_wallet_btc(self, value): - self.ui.pushButtonWalletA.setEnabled(value > 0) - self.ui.pushButtonWalletA.setText( - 'BTC: ' + utilities.internal2str(value)) + def set_wallet_a(self, value): + + if value == None: + self.ui.pushButtonWalletA.setEnabled(False) + self.ui.pushButtonWalletA.setText('n/a') + return + + self.ui.pushButtonWalletA.setEnabled(True) + self.ui.pushButtonWalletA.setText(value.to_long_string()) + + def set_wallet_b(self, value): + + if value == None: + self.ui.pushButtonWalletB.setEnabled(False) + self.ui.pushButtonWalletB.setText('n/a') + return - def set_wallet_usd(self, value): - self.ui.pushButtonWalletB.setEnabled(value > 0) - self.ui.pushButtonWalletB.setText( - 'USD: ' + utilities.internal2str(value, 5)) + self.ui.pushButtonWalletB.setEnabled(True) + self.ui.pushButtonWalletB.setText(value.to_long_string()) def get_trade_size(self): - value = self.ui.doubleSpinBoxBtc.value() - return utilities.float2internal(value) + # re-convert trade size float value from gui + # to proper base currency value + return Money(self.ui.doubleSpinBoxSize.value(), + self.preferences.get_currency(Preferences.CURRENCY_INDEX_BASE)) def set_trade_size(self, value): - value_float = utilities.internal2float(value) - self.ui.doubleSpinBoxBtc.setValue(value_float) + self.ui.doubleSpinBoxSize.setValue(value.to_float()) def get_trade_price(self): - value = self.ui.doubleSpinBoxPrice.value() - return utilities.float2internal(value) + # re-convert trade price value from gui + # to proper quote currency value + return Money(self.ui.doubleSpinBoxPrice.value(), + self.preferences.get_currency(Preferences.CURRENCY_INDEX_QUOTE)) def set_trade_price(self, value): - value_float = utilities.internal2float(value) - self.ui.doubleSpinBoxPrice.setValue(value_float) + self.ui.doubleSpinBoxPrice.setValue(value.to_float()) def get_trade_total(self): - value = self.ui.doubleSpinBoxTotal.value() - return utilities.float2internal(value) + # re-convert trade total value from gui + # to proper quote currency value + return Money(self.ui.doubleSpinBoxTotal.value(), + self.preferences.get_currency(Preferences.CURRENCY_INDEX_QUOTE)) def set_trade_total(self, value): - value_float = utilities.internal2float(value) - self.ui.doubleSpinBoxTotal.setValue(value_float) + self.ui.doubleSpinBoxTotal.setValue(value.to_float()) def get_order_id(self): return str(self.ui.lineEditOrder.text()) @@ -204,19 +243,19 @@ def order_selected(self, url): def display_wallet(self): - self.set_wallet_usd( - utilities.gox2internal(self.market.get_balance('USD'), 'USD')) - self.set_wallet_btc( - utilities.gox2internal(self.market.get_balance('BTC'), 'BTC')) + self.set_wallet_a(self.market.get_balance( + Preferences.CURRENCY_INDEX_BASE)) + self.set_wallet_b(self.market.get_balance( + Preferences.CURRENCY_INDEX_QUOTE)) def set_trade_size_from_wallet(self): self.set_trade_size( - utilities.gox2internal(self.market.get_balance('BTC'), 'BTC')) + self.market.get_balance(Preferences.CURRENCY_INDEX_BASE)) self.set_selected_trade_type('SELL') def set_trade_total_from_wallet(self): self.set_trade_total( - utilities.gox2internal(self.market.get_balance('USD'), 'USD')) + self.market.get_balance(Preferences.CURRENCY_INDEX_QUOTE)) self.set_selected_trade_type('BUY') def display_orderlag(self, ms, text): @@ -228,15 +267,15 @@ def execute_trade(self): size = self.get_trade_size() price = self.get_trade_price() - total = self.get_trade_total() + total = price * size trade_name = 'BID' if trade_type == 'BUY' else 'ASK' - self.status_message('Placing order: {0} {1} BTC at {2} USD (total {3} USD)...'.format(# @IgnorePep8 + self.status_message('Placing order: {0} {1} at {2} (total {3})...'.format(# @IgnorePep8 trade_name, - utilities.internal2str(size), - utilities.internal2str(price, 5), - utilities.internal2str(total, 5))) + size.to_long_string(), + price.to_long_string(), + total.to_long_string())) if trade_type == 'BUY': self.market.buy(price, size) @@ -246,28 +285,31 @@ def execute_trade(self): def recalculate_size(self): price = self.get_trade_price() - if price == 0: + + if price.value == 0: return total = self.get_trade_total() - size = utilities.divide_internal(total, price) + size = total / price + + # we multiply quote currency values but the resulting + # size must be expressed in base currency + size.currency = self.preferences.get_currency( + Preferences.CURRENCY_INDEX_BASE) + self.set_trade_size(size) def recalculate_total(self): price = self.get_trade_price() size = self.get_trade_size() - total = utilities.multiply_internal(price, size) + total = price * size + + # ToDo: set currency type self.set_trade_total(total) def display_userorder(self, price, size, order_type, oid, status): - size = utilities.gox2internal(size, 'BTC') - price = utilities.gox2internal(price, 'USD') - - size = utilities.internal2str(size) - price = utilities.internal2str(price) - if order_type == '': self.status_message("Order {0} {1}.".format( oid, status)) @@ -275,17 +317,29 @@ def display_userorder(self, price, size, order_type, oid, status): self.set_order_id('') else: self.status_message("{0} size: {1}, price: {2}, oid: {3} - {4}".format(# @IgnorePep8 - str.upper(str(order_type)), size, price, oid, status)) + str.upper(str(order_type)), + size.to_long_string(), + price.to_long_string(), + oid, + status)) if status == 'post-pending': self.set_order_id(oid) - def update_price_from_asks(self, index): - self.set_trade_price(self.modelAsk.get_price(index.row()) - - self.SUB_FROM_ASK) + def slot_update_price_from_asks(self, index): + self.update_price_from_asks(index.row()) + + def update_price_from_asks(self, row): + value = self.modelAsk.get_price(row) + pip = value.pip() * Money(View.SUB_FROM_ASK_PIPS) + self.set_trade_price(value - pip) + + def slot_update_price_from_bids(self, index): + self.update_price_from_bids(index.row()) - def update_price_from_bids(self, index): - self.set_trade_price(self.modelBid.get_price(index.row()) - + self.ADD_TO_BID) + def update_price_from_bids(self, row): + value = self.modelBid.get_price(row) + pip = value.pip() * Money(View.ADD_TO_BID_PIPS) + self.set_trade_price(value + pip) def cancel_order(self): order_id = self.get_order_id() @@ -297,13 +351,9 @@ def update_price_best(self): trade_type = self.get_selected_trade_type() if trade_type == 'BUY': - price = self.modelBid.get_price(0) - price += self.ADD_TO_BID - self.set_trade_price(price) + self.update_price_from_bids(0) elif trade_type == 'SELL': - price = self.modelAsk.get_price(0) - price -= self.SUB_FROM_ASK - self.set_trade_price(price) + self.update_price_from_asks(0) def stop(self): self.market.stop() diff --git a/unittest/currency_test.py b/unittest/currency_test.py new file mode 100644 index 0000000..12b86ea --- /dev/null +++ b/unittest/currency_test.py @@ -0,0 +1,15 @@ +import unittest +from currency import Currency + + +class Test(unittest.TestCase): + + def test_construct(self): + currency = Currency('EUR') + self.assertEquals('EUR', currency.symbol) + self.assertEquals(5, currency.decimals) + + def test_equals(self): + currencyA = Currency('EUR') + currencyB = Currency('EUR') + self.assertEquals(currencyA, currencyB) diff --git a/unittest/money_test.py b/unittest/money_test.py new file mode 100644 index 0000000..7b31585 --- /dev/null +++ b/unittest/money_test.py @@ -0,0 +1,99 @@ +import unittest +from money import Money +from currency import Currency + + +class Test(unittest.TestCase): + + def test_construct(self): + money = Money(1.0, Currency('USD')) + self.assertEquals(100000000, money.value) + self.assertEquals(Currency('USD'), money.currency) + self.assertEquals('USD', money.symbol) + self.assertEquals(5, money.decimals) + + def test_construct_noshift(self): + money = Money(100000000, Currency('USD'), False) + self.assertEquals(100000000, money.value) + self.assertEquals(Currency('USD'), money.currency) + self.assertEquals('USD', money.symbol) + self.assertEquals(5, money.decimals) + + def test_multiply(self): + + moneyA = Money(10.0, Currency('USD')) + moneyB = Money(5.0) + moneyC = moneyA * moneyB + + # the result should have money A's properties + self.assertEquals(5000000000, moneyC.value) + self.assertEquals(Currency('USD'), moneyC.currency) + self.assertEquals(5, moneyC.decimals) + + moneyA = Money(10.0) + moneyB = Money(5.0, Currency('EUR')) + moneyC = moneyA * moneyB + + # the result should have money A's properties + self.assertEquals(5000000000, moneyC.value) + self.assertEquals(Currency('BTC'), moneyC.currency) + self.assertEquals(8, moneyC.decimals) + + def test_divide(self): + + moneyA = Money(10.0, Currency('USD')) + moneyB = Money(5.0) + moneyC = moneyA / moneyB + + self.assertEquals(200000000, moneyC.value) + self.assertEquals(Currency('USD'), moneyC.currency) + + def test_add(self): + + moneyA = Money(10.0, Currency('USD')) + moneyB = Money(5.0) + moneyC = moneyA + moneyB + + self.assertEquals(1500000000, moneyC.value) + self.assertEquals(Currency('USD'), moneyC.currency) + + def test_sub(self): + + moneyA = Money(10.0, Currency('USD')) + moneyB = Money(5.0) + moneyC = moneyA - moneyB + + self.assertEquals(500000000, moneyC.value) + self.assertEquals(Currency('USD'), moneyC.currency) + + def test_to_string(self): + money = Money(1000, Currency('USD')) + self.assertEquals('1,000.00000', str(money)) + + def test_to_float(self): + money = Money(6, Currency('USD')) + self.assertEquals(6.0, money.to_float()) + + def test_to_long_string(self): + money = Money(1000, Currency('EUR')) + self.assertEquals('1,000.00000 EUR', money.to_long_string()) + + def test_pip(self): + money = Money(1, Currency('USD')) + self.assertEquals(1000, money.pip().value) + + def test_change_type(self): + + money = Money(6666.6, Currency('USD')) + + self.assertEquals(6666.6, money.to_float()) + self.assertEquals('6,666.60000', str(money)) + self.assertEquals(Currency('USD'), money.currency) + self.assertEquals(5, money.decimals) + + money.currency = Currency('JPY') + + self.assertEquals(6666.6, money.to_float()) + self.assertEquals('6,666.600', str(money)) + self.assertEquals(Currency('JPY'), money.currency) + self.assertEquals(3, money.decimals)