Source code for tallywallet.common.exchange

#!/usr/bin/env python3.4
#   encoding: UTF-8

# This file is part of tallywallet.
#
# Tallywallet is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Tallywallet is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with tallywallet.  If not, see <http://www.gnu.org/licenses/>.

from decimal import Decimal as Dl

from tallywallet.common.trade import TradeFees
from tallywallet.common.trade import TradeGain

__doc__ = """
The exchange module contains functionality to enable conversion between
currencies.
"""


def convert(self, val, path, fees=TradeFees(0, 0)):
    """
    Return the calculated outcome of converting the amount `val`
    via the TradePath `path`.
    """
    work = (val - fees.rcv) * self.get((path.rcv, path.work))
    rv = work * self.get((path.work, path.out)) - fees.out
    return rv


def gain(self, val, path, prior=None, fees=TradeFees(0, 0)):
    """
    Calculate the gain related to a change in exchange rates.
    The `self` object contains the latest rates, and the historic
    ones are passed as an argument to this method.

    :param prior: An Exchange_ object which contains the rates
                  existing prior to those in the `self` object.
    :rtype: TradeGain
    """
    prior = prior or self
    this = self.convert(val, path, fees)
    that = prior.convert(val, path, fees)
    return TradeGain(val, this - that, this)


def infer_rate(self, key):
    try:
        return self[key]
    except KeyError:
        key = tuple(reversed(key))
        try:
            return Dl(1) / self[key]
        except KeyError as err:
            if key[0] == key[1]:
                return Dl(1)
            else:
                raise err

Exchange = type("Exchange", (dict,),
                {"convert": convert, "gain": gain, "get": infer_rate})
Exchange.__doc__ = """
An exchange is a lookup container for currency exchange rates.

It behaves just like a Python dictionary, but has some extra methods.

The approach Tallywallet uses is to associate each rate against a key
which is a 2-tuple of Currency_.
By convention, the first element of this key is the source currency,
and the second is the destination. The values of the exchange mapping can
therefore be considered as `gain` from one currency to the next.

.. py:method:: get(key)

   Return the value stored against `key`.

   This method overrides the standard behaviour of `dict.get`. It infers the
   values of missing keys as follows:

    * The rate of a currency against itself is unity.
    * The rate of one currency against another is the reciprocal of the
      reverse rate (if defined).
"""