예제 #1
0
def time_correct_verlet(state, t, accn, mass=1):
    """
    This low-level function implements a single step of
    Time-corrected Verlet position integration. See Jonathan Dummer's
    `article on TCV`_ for a full description of the algorithm.

    :param state: a 2-tuple of
                :py:class:`Impulses <turberfield.utils.travel.Impulse>`
                . Element 0 is the most recent in time.
    :param t: a time quantity.
    :param accn: an acceleration quantity.
    :returns: a new state 2-tuple.
    :requires: `acceleration` type to support multiplication over
            `time` type.

    .. _article on TCV: http://lonesock.net/article/verlet.html
    """
    imp0, imp_1 = state
    dt0, dt_1 = (Dl(imp0.tBegin - imp0.tEnd), Dl(imp_1.tBegin - imp_1.tEnd))
    leap = imp0.accn * dt0 * dt0
    try:
        pos = imp0.pos + (imp0.pos - imp_1.pos) * dt0 / dt_1 + leap
    except (ZeroDivisionError, decimal.InvalidOperation):
        pos = imp0.pos + (imp0.pos - imp_1.pos) + leap
    rv = Impulse(imp0.tEnd, t, accn, pos)
    return (rv, imp0)
예제 #2
0
 def add_column(self, ref, role, *, label="{}", currency=None):
     assert role is not Role.trading
     crncy = currency or self.ref
     rv = Column(ref, crncy, role, label)
     self._tally[rv] = Dl(0)
     self._rates[rv] = Exchange({})
     if crncy not in self._tradingAccounts:
         tA = Column(crncy.name, crncy, Role.trading, "{} trading account")
         self._tradingAccounts[crncy] = tA
         self._tally[tA] = Dl(0)
     return rv
예제 #3
0
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
예제 #4
0
    def test_output_sequential_journals(self):
        ldgr = Ledger(
            Column("Canadian cash", Cy.CAD, Role.asset, "{}"),
            Column("US cash", Cy.USD, Role.asset, "{}"),
            Column("Capital", Cy.CAD, Role.capital, "{}"),
            Column("Expense", Cy.CAD, Role.expense, "{}"),
            ref=Cy.CAD)
        cols = ldgr.columns

        for amount, col in zip(
            (Dl(200), Dl(0), Dl(200), Dl(0)), ldgr.columns.values()
        ):
            ldgr.commit(amount, col)

        out = io.StringIO()
        print(metadata(ldgr), file=out)
        print(journal(
            ldgr,
            ts=datetime.date(2013, 1, 1), note="Opening balance"),
            file=out)

        exchange = Exchange({(Cy.USD, Cy.CAD): Dl("1.2")})
        for args in ldgr.adjustments(exchange):
            ldgr.commit(*args)

        print(journal(
            ldgr,
            ts=datetime.date(2013, 1, 2), note="1 USD -> 1.20 CAD"),
            file=out)

        ldgr.commit(-20, cols["Canadian cash"])
        ldgr.commit(20, cols["Expense"])

        print(journal(
            ldgr,
            ts=datetime.date(2013, 1, 3), note="Buy food"),
            file=out)

        objs = rson.loads(out.getvalue())
        self.assertEqual(8, len(objs))
        self.assertEqual([180., 0., 200., 20., 0., 0.], objs[-1])
예제 #5
0
    def equation(self):
        """
        The `Fundamental Accounting Equation`_ is this::

            Assets - Liabilities = Capital + Income - Expenses - Dividends

        Currency trading gains are counted as income.
        For practical purposes, the `FAE` is often rearranged to be::

            Assets + Expenses + Dividends = Capital + Income + Liabilities

        This property evaluates both sides of this second equation,
        and determines if they are equal or not.

        :returns: A tuple of `lhs`, `rhs`, `status`

        .. _Fundamental Accounting Equation: \
http://en.wikipedia.org/wiki/Accounting_equation
        """
        st = Status.failed
        lhCols = set(i for i in self._tally
                     if i.role in (Role.asset, Role.expense, Role.dividend))
        trCols = set(i for i in self._tally if i.role is Role.trading)
        rhCols = set(self._tally.keys()) - lhCols - trCols
        try:
            lhs = sum(self._rates[col].convert(
                self._tally[col], TradePath(col.currency, self.ref, self.ref))
                      for col in lhCols)
            rhs = sum(self._rates[col].convert(
                self._tally.get(col, Dl(0)),
                TradePath(col.currency, self.ref, self.ref))
                      for col in rhCols) + sum(
                          self._tally.get(col, Dl(0)) for col in trCols)
        except KeyError:
            lhs = None
            rhs = None
        else:
            if lhs.quantize(Dl("0.01")) == rhs.quantize(Dl("0.01")):
                st = Status.ok

        return FAE(lhs, rhs, st)
예제 #6
0
    def test_track_exchange_gain_with_fixed_assets(self):
        """
        From Selinger table 4.1
        date                             asset  asset   capital gain
        Jan 1 Balance (1 USD = 1.20 CAD) CAD 60 USD 100 CAD 180 CAD 0
        Jan 2 Balance (1 USD = 1.30 CAD) CAD 60 USD 100 CAD 180 CAD 10
        Jan 3 Balance (1 USD = 1.25 CAD) CAD 60 USD 100 CAD 180 CAD 5
        Jan 4 Balance (1 USD = 1.15 CAD) CAD 60 USD 100 CAD 180 – CAD 5
        """
        ldgr = Ledger(Column("Canadian cash", Cy.CAD, Role.asset, "{}"),
                      Column("US cash", Cy.USD, Role.asset, "{}"),
                      Column("Capital", Cy.CAD, Role.capital, "{}"),
                      ref=Cy.CAD)
        usC = ldgr.columns["US cash"]
        for args in ldgr.adjustments(Exchange({(Cy.USD, Cy.CAD): Dl("1.2")})):
            ldgr.commit(*args,
                        ts=datetime.date(2013, 1, 1),
                        note="1 USD = 1.20 CAD")

        lhs, rhs, st = ldgr.equation
        self.assertEqual(lhs, rhs)
        self.assertIs(st, Status.ok)

        for deposit, col in zip((Dl(60), Dl(100), Dl(180)),
                                ldgr.columns.values()):
            ldgr.commit(deposit,
                        col,
                        ts=datetime.date(2013, 1, 1),
                        note="Initial balance")

        lhs, rhs, st = ldgr.equation
        self.assertEqual(lhs, rhs)
        self.assertIs(st, Status.ok)

        trade, col, exchange = next(
            ldgr.adjustments(Exchange({(Cy.USD, Cy.CAD): Dl("1.3")}), [usC]))
        self.assertIs(col, usC)
        self.assertEqual(10, trade.gain)

        trade, col, exchange = next(
            ldgr.adjustments(Exchange({(Cy.USD, Cy.CAD): Dl("1.25")}), [usC]))
        self.assertIs(col, usC)
        self.assertEqual(5, trade.gain)

        trade, col, exchange = next(
            ldgr.adjustments(Exchange({(Cy.USD, Cy.CAD): Dl("1.15")}), [usC]))
        self.assertIs(col, usC)
        self.assertEqual(-5, trade.gain)

        lhs, rhs, st = ldgr.equation
        self.assertEqual(lhs, rhs)
        self.assertIs(st, Status.ok)
예제 #7
0
    def test_output_journal(self):
        ldgr = Ledger(
            Column("Canadian cash", Cy.CAD, Role.asset, "{}"),
            Column("US cash", Cy.USD, Role.asset, "{}"),
            Column("Capital", Cy.CAD, Role.capital, "{}"),
            Column("Expense", Cy.CAD, Role.expense, "{}"),
            ref=Cy.CAD)

        for amount, col in zip(
            (Dl(200), Dl(0), Dl(200), Dl(0)), ldgr.columns.values()
        ):
            (_, _, _, _, st) = ldgr.commit(amount, col)

        self.assertIs(Status.ok, st)
        t = journal(
            ldgr,
            ts=datetime.date(2013, 1, 1), note="Opening balance")
        out = rson.loads(t)
        self.assertEqual(2, len(out))
        self.assertEqual(
            {"ts": "2013-01-01", "note": "Opening balance"},
            out[0])
        self.assertEqual(6, len(out[1]))
        self.assertEqual(400, sum(out[1]))
예제 #8
0
 def __init__(self, *args, ref=Currency.XTW):
     """
     :param ref: (optional) the base Currency_ type for the Ledger
     :param args: One or more Column objects
     """
     self.ref = ref
     cols = list(args)
     cols.extend(
         Column(c.name, c, Role.trading, "{} trading account")
         for c in set(i.currency for i in args))
     self._tradingAccounts = {
         i.currency: i
         for i in cols if i.role is Role.trading
     }
     self._rates = {i: Exchange({}) for i in args}
     self._tally = OrderedDict((i, Dl(0)) for i in cols)
     self.transaction = singledispatch(transaction)
예제 #9
0
    def test_point_calculation(self):
        expected = [point(i, 0, 0) for i in [
            Dl("0"), Dl("29.41800004"), Dl("56.38450008"),
            Dl("80.89950012"), Dl("102.96300016"), Dl("122.5750002"),
            Dl("139.73550024"), Dl("154.44450028"), Dl("166.70200032"),
            Dl("176.50800036"), Dl("183.8625004"), Dl("188.76550044"),
            Dl("191.21700048"), Dl("191.21700052"), Dl("188.76550056"),
            Dl("183.8625006"), Dl("176.50800064"), Dl("166.70200068"),
            Dl("154.44450072"), Dl("139.73550076"), Dl("122.5750008"),
            Dl("102.96300084"), Dl("80.89950088"), Dl("56.38450092"),
            Dl("29.41800096"), Dl("0.000001")]
        ]

        dt = Dl("0.5")
        vel = vector(Dl("61.28750008"), 0, 0)
        accn = vector(Dl("-9.806"), 0, 0)
        proc = trajectory()
        proc.send(None)
        for n, x in enumerate(expected):
            if n == 0:
                imp = proc.send(Impulse(
                    Dl(0), Dl("0.5"), accn, point(0, 0, 0)))
            elif n == 1:
                p = (
                    point(0, 0, 0) + vel * dt +
                    Dl("0.5") * accn * dt * dt
                )
                imp = proc.send(Impulse(
                    imp.tEnd, imp.tEnd + dt, accn, p))
            else:
                imp = proc.send(Impulse(
                    imp.tEnd, imp.tEnd + dt, accn, imp.pos))

            with self.subTest(n=n):
                self.assertEqual(x, imp.pos)
예제 #10
0
    def test_point_calculation(self):
        expected = [point(i, 0, 0) for i in [
            Dl("0"), Dl("0.502656"), Dl("0.824448"), Dl("0.986112"),
            Dl("1.008384"), Dl("0.912"), Dl("0.717696"),
            Dl("0.446208"), Dl("0.118272"), Dl("-0.245376"),
            Dl("-0.624"), Dl("-0.996864"), Dl("-1.343232"),
            Dl("-1.642368"), Dl("-1.873536"), Dl("-2.016"),
            Dl("-2.049024"), Dl("-1.951872"), Dl("-1.703808"),
            Dl("-1.284096"), Dl("-0.672"), Dl("0.153216"),
            Dl("1.212288"), Dl("2.525952"), Dl("4.114944"), Dl("6")]
        ]

        accns = [vector(i, 0, 0) for i in [
            Dl("-14"), Dl("-12.56"), Dl("-11.12"), Dl("-9.68"),
            Dl("-8.24"), Dl("-6.8"), Dl("-5.36"), Dl("-3.92"),
            Dl("-2.48"), Dl("-1.04"), Dl("0.4"), Dl("1.84"),
            Dl("3.28"), Dl("4.72"), Dl("6.16"), Dl("7.6"), Dl("9.04"),
            Dl("10.48"), Dl("11.92"), Dl("13.36"), Dl("14.8"),
            Dl("16.24"), Dl("17.68"), Dl("19.12"), Dl("20.56"),
            Dl("22")]
        ]

        dt = Dl("0.12")
        proc = trajectory()
        proc.send(None)
        for n, x in enumerate(expected):
            accn = accns[n]
            if n == 0:
                p = point(Dl(0), Dl(0), Dl(0))
                imp = proc.send(Impulse(Dl(0), dt, accn, p))
            elif n == 1:
                p = point(Dl("0.502656"), Dl(0), Dl(0))
                imp = proc.send(Impulse(
                    imp.tEnd, imp.tEnd + dt, accn, p))
            else:
                imp = proc.send(Impulse(
                    imp.tEnd, imp.tEnd + dt, accn, imp.pos))

            with self.subTest(n=n):
                self.assertEqual(x, imp.pos)
예제 #11
0
    def test_commit_exchange_gain_via_expenses(self):
        """
        From Selinger table 4.4
        date                    asset   asset   capital expense trading
        Jan 1   Opening balance CAD 200 USD 0   CAD 200 CAD 0   USD 0 CAD 0
        Jan 2   1 USD==1.20CAD  CAD-120 USD 100                 USD 100 CAD 120
                Balance         CAD 80  USD 100 CAD 200 CAD 0   USD 100 CAD 120
        Jan 3   1 USD==1.30CAD          USD-40          CAD 52  USD-40 CAD 52
                Balance         CAD 80  USD 60  CAD 200 CAD 52  USD 60 CAD-68
        Jan 5   1 USD==1.25CAD  CAD 75  USD-60                  USD-60 CAD 75
                Balance         CAD 155 USD 0   CAD 200 CAD 52  USD 0  CAD 07
        Jan 7   Buy food        CAD-20                  CAD 20
                Balance         CAD 135 USD 0   CAD 200 CAD 72  USD 0  CAD 07
        """
        ldgr = Ledger(Column("Canadian cash", Cy.CAD, Role.asset, "{}"),
                      Column("US cash", Cy.USD, Role.asset, "{}"),
                      Column("Capital", Cy.CAD, Role.capital, "{}"),
                      Column("Expense", Cy.CAD, Role.expense, "{}"),
                      ref=Cy.CAD)

        cols = ldgr.columns
        self.assertIs(Status.failed, ldgr.equation.status)

        # row one
        for amount, col in zip((Dl(200), Dl(0), Dl(200), Dl(0)),
                               ldgr.columns.values()):
            ldgr.commit(amount,
                        col,
                        ts=datetime.date(2013, 1, 1),
                        note="Opening balance")
        self.assertEqual(200, ldgr.value("Canadian cash"))
        self.assertEqual(200, ldgr.value("Capital"))
        self.assertEqual(0, ldgr.value("USD trading account"))  # whitebox test

        self.assertIs(Status.failed, ldgr.equation.status)

        # row two
        exchange = Exchange({(Cy.USD, Cy.CAD): Dl("1.2")})
        for args in ldgr.adjustments(exchange):
            ldgr.commit(*args,
                        ts=datetime.date(2013, 1, 2),
                        note="1 USD = 1.20 CAD")

        lhs, rhs, st = ldgr.equation
        self.assertEqual(lhs, rhs)
        self.assertIs(st, Status.ok)

        usd = exchange.convert(120, TradePath(Cy.CAD, Cy.CAD, Cy.USD))
        self.assertEqual(100, usd)
        ldgr.commit(-120, cols["Canadian cash"])
        self.assertIs(ldgr.equation.status, Status.failed)
        ldgr.commit(usd, cols["US cash"])
        self.assertIs(ldgr.equation.status, Status.ok)

        # row three
        exchange = Exchange({(Cy.USD, Cy.CAD): Dl("1.3")})
        for args in ldgr.adjustments(exchange):
            ldgr.commit(*args,
                        ts=datetime.date(2013, 1, 3),
                        note="1 USD = 1.30 CAD")
        cad = exchange.convert(40, TradePath(Cy.USD, Cy.CAD, Cy.CAD))
        self.assertEqual(52, cad)

        self.assertIs(ldgr.equation.status, Status.ok)
        ldgr.commit(-40, cols["US cash"])
        self.assertIs(ldgr.equation.status, Status.failed)
        ldgr.commit(cad, cols["Expense"])
        self.assertIs(ldgr.equation.status, Status.ok)

        # row four
        exchange = Exchange({(Cy.USD, Cy.CAD): Dl("1.25")})
        for args in ldgr.adjustments(exchange):
            ldgr.commit(*args,
                        ts=datetime.date(2013, 1, 5),
                        note="1 USD = 1.25 CAD")
        cad = exchange.convert(60, TradePath(Cy.USD, Cy.CAD, Cy.CAD))
        self.assertEqual(75, cad)

        self.assertIs(ldgr.equation.status, Status.ok)
        ldgr.commit(-60, cols["US cash"])
        self.assertIs(ldgr.equation.status, Status.failed)
        ldgr.commit(cad, cols["Canadian cash"])
        self.assertIs(ldgr.equation.status, Status.ok)

        self.assertEqual(155, ldgr.value("Canadian cash"))

        # row five
        ldgr.commit(-20, cols["Canadian cash"], note="Buy food")
        self.assertIs(ldgr.equation.status, Status.failed)
        ldgr.commit(20, cols["Expense"], note="Buy food")
        self.assertIs(ldgr.equation.status, Status.ok)

        # final balance
        self.assertEqual(135, ldgr.value("Canadian cash"))
        self.assertEqual(0, ldgr.value("US cash"))
        self.assertEqual(200, ldgr.value("Capital"))
        self.assertEqual(72, ldgr.value("Expense"))
        self.assertEqual(7, ldgr.value("USD trading account"))