from typing import TypeVar, Generic, Callable

T = TypeVar('T')
U = TypeVar('U')

class Box(Generic[T]):

    def __init__(self, value: T) -> None:
        """
        :param value: value to store in Box
        :type value: Any
        """
        self.value = value

    def __eq__(self, other: object) -> bool:
        return isinstance(other, Box) and self.value == other.value


from typing import TypeVar, Generic, Callable

T = TypeVar('T')
U = TypeVar('U')

class Box(Generic[T]):

    def __init__(self, value: T) -> None:
        """
        :param value: value to store in Box
        :type value: Any
        """
        self.value = value

    def to_lazy(self):
        """
        Transform Box into Lazy with returning value function.

        :returns: not folded Lazy monad with function returning previous value
        :rtype: Lazy[Function(() -> A)]
        """
        from pymonet.lazy import Lazy

        return Lazy(lambda: self.value)


from typing import TypeVar, Generic, Callable, Any

T = TypeVar('T')
U = TypeVar('U')

class Either(Generic[T]):

    def __init__(self, value: T) -> None:
        self.value = value

    def __eq__(self, other: object) -> bool:
        return isinstance(other, Either) and\
            self.value == other.value and\
            self.is_right() == other.is_right()


from typing import TypeVar, Generic, Callable, Any

T = TypeVar('T')
U = TypeVar('U')

class Either(Generic[T]):

    def __init__(self, value: T) -> None:
        self.value = value

    def case(self, error: Callable[[T], U], success: Callable[[T], U]) -> U:
        """
        Take 2 functions call only one of then with either value and return her result.

        :params error: function to call when Either is Left
        :type error: Function(A) -> B
        :params success: function to call when Either is Right
        :type success: Function(A) -> B
        :returns: result of success handler when Eihter is Right, result of error handler when Eihter is Left
        :rtpye: B
        """
        if self.is_right():
            return success(self.value)
        return error(self.value)


from typing import TypeVar, Generic, Callable, Any

T = TypeVar('T')
U = TypeVar('U')

class Either(Generic[T]):

    def __init__(self, value: T) -> None:
        self.value = value

    def to_lazy(self):
        """
        Transform Either to Try.

        :returns: Lazy monad with function returning previous value
        :rtype: Lazy[Function() -> A]
        """
        from pymonet.lazy import Lazy

        return Lazy(lambda: self.value)


from typing import TypeVar, Generic, Callable, Optional

T = TypeVar('T')
U = TypeVar('U')

class ImmutableList(Generic[T]):

    def __init__(self, head: T = None, tail: 'ImmutableList[T]' = None, is_empty: bool = False) -> None:
        self.head = head
        self.tail = tail
        self.is_empty = is_empty

    def __eq__(self, other: object) -> bool:
        return isinstance(other, ImmutableList) \
            and self.head == other.head\
            and self.tail == other.tail\
            and self.is_empty == other.is_empty


from typing import TypeVar, Generic, Callable, Optional

T = TypeVar('T')
U = TypeVar('U')

class ImmutableList(Generic[T]):

    def __init__(self, head: T = None, tail: 'ImmutableList[T]' = None, is_empty: bool = False) -> None:
        self.head = head
        self.tail = tail
        self.is_empty = is_empty

    def __len__(self):
        if self.head is None:
            return 0

        if self.tail is None:
            return 1

        return len(self.tail) + 1


from typing import TypeVar, Generic, Callable, Optional

T = TypeVar('T')
U = TypeVar('U')

class ImmutableList(Generic[T]):

    def __init__(self, head: T = None, tail: 'ImmutableList[T]' = None, is_empty: bool = False) -> None:
        self.head = head
        self.tail = tail
        self.is_empty = is_empty

    def filter(self, fn: Callable[[Optional[T]], bool]) -> 'ImmutableList[T]':
        """
        Returns new ImmutableList with only this elements that passed
        info argument returns True

        :param fn: function to call with ImmutableList value
        :type fn: Function(A) -> bool
        :returns: ImmutableList[A]
        """
        if self.tail is None:
            if fn(self.head):
                return ImmutableList(self.head)
            return ImmutableList(is_empty=True)

        if fn(self.head):
            return ImmutableList(self.head, self.tail.filter(fn))

        return self.tail.filter(fn)


from typing import TypeVar, Generic, Callable, Optional

T = TypeVar('T')
U = TypeVar('U')

class ImmutableList(Generic[T]):

    def __init__(self, head: T = None, tail: 'ImmutableList[T]' = None, is_empty: bool = False) -> None:
        self.head = head
        self.tail = tail
        self.is_empty = is_empty

    def find(self, fn: Callable[[Optional[T]], bool]) -> Optional[T]:
        """
        Returns first element of ImmutableList that passed
        info argument returns True

        :param fn: function to call with ImmutableList value
        :type fn: Function(A) -> bool
        :returns: A
        """
        if self.head is None:
            return None

        if self.tail is None:
            return self.head if fn(self.head) else None

        if fn(self.head):
            return self.head

        return self.tail.find(fn)


from typing import TypeVar, Generic, Callable, Optional

T = TypeVar('T')
U = TypeVar('U')

class ImmutableList(Generic[T]):

    def __init__(self, head: T = None, tail: 'ImmutableList[T]' = None, is_empty: bool = False) -> None:
        self.head = head
        self.tail = tail
        self.is_empty = is_empty

    def reduce(self, fn: Callable[[U, T], U], acc: U) -> U:
        """
        Method executes a reducer function
        on each element of the array, resulting in a single output value.

        :param fn: function to call with ImmutableList value
        :type fn: Function(A, B) -> A
        :returns: A
        """
        if self.head is None:
            return acc

        if self.tail is None:
            return fn(self.head, acc)

        return self.tail.reduce(fn, fn(acc, self.head))


from typing import TypeVar, Generic, Callable

T = TypeVar('T')
U = TypeVar('U')
W = TypeVar('W')

class Lazy(Generic[T, U]):

    def __init__(self, constructor_fn: Callable[[T], U]) -> None:
        """
        :param constructor_fn: function to call during fold method call
        :type constructor_fn: Function() -> A
        """
        self.constructor_fn = constructor_fn
        self.is_evaluated = False
        self.value = None

    def __eq__(self, other: object) -> bool:
        """
        Two Lazy are equals where both are evaluated both have the same value and constructor functions.
        """
        return (
            isinstance(other, Lazy)
            and self.is_evaluated == other.is_evaluated
            and self.value == other.value
            and self.constructor_fn == other.constructor_fn
        )


from typing import TypeVar, Generic, Callable

T = TypeVar('T')
U = TypeVar('U')
W = TypeVar('W')

class Lazy(Generic[T, U]):

    def __init__(self, constructor_fn: Callable[[T], U]) -> None:
        """
        :param constructor_fn: function to call during fold method call
        :type constructor_fn: Function() -> A
        """
        self.constructor_fn = constructor_fn
        self.is_evaluated = False
        self.value = None

    def map(self, mapper: Callable[[U], W]) -> 'Lazy[T, W]':
        """
        Take function Function(A) -> B and returns new Lazy with mapped result of Lazy constructor function.
        Both mapper end constructor will be called only during calling fold method.

        :param mapper: mapper function
        :type mapper: Function(A) -> B
        :returns: Lazy with mapped value
        :rtype: Lazy[Function() -> B)]
        """
        return Lazy(lambda *args: mapper(self.constructor_fn(*args)))


from typing import TypeVar, Generic, Callable

T = TypeVar('T')
U = TypeVar('U')
W = TypeVar('W')

class Lazy(Generic[T, U]):

    def __init__(self, constructor_fn: Callable[[T], U]) -> None:
        """
        :param constructor_fn: function to call during fold method call
        :type constructor_fn: Function() -> A
        """
        self.constructor_fn = constructor_fn
        self.is_evaluated = False
        self.value = None

    def ap(self, applicative):
        """
        Applies the function inside the Lazy[A] structure to another applicative type for notempty Lazy.
        For empty returns copy of itself

        :param applicative: applicative contains function
        :type applicative: Lazy[Function(A) -> B]
        :returns: new Lazy with result of contains function
        :rtype: Lazy[B]
        """
        return Lazy(lambda *args: self.constructor_fn(applicative.get(*args)))


from typing import TypeVar, Generic, Callable

T = TypeVar('T')
U = TypeVar('U')
W = TypeVar('W')

class Lazy(Generic[T, U]):

    def __init__(self, constructor_fn: Callable[[T], U]) -> None:
        """
        :param constructor_fn: function to call during fold method call
        :type constructor_fn: Function() -> A
        """
        self.constructor_fn = constructor_fn
        self.is_evaluated = False
        self.value = None

    def bind(self, fn: 'Callable[[U], Lazy[U, W]]') -> 'Lazy[T, W]':
        """
        Take function and call constructor function passing returned value to fn function.

        It's only way to call function store in Lazy
        :param fn: Function(constructor_fn) -> B
        :returns: result od folder function
        :rtype: B
        """
        def lambda_fn(*args):
            computed_value = self._compute_value(*args)
            return fn(computed_value).constructor_fn

        return Lazy(lambda_fn)


from typing import TypeVar, Generic, Callable

T = TypeVar('T')
U = TypeVar('U')
W = TypeVar('W')

class Lazy(Generic[T, U]):

    def __init__(self, constructor_fn: Callable[[T], U]) -> None:
        """
        :param constructor_fn: function to call during fold method call
        :type constructor_fn: Function() -> A
        """
        self.constructor_fn = constructor_fn
        self.is_evaluated = False
        self.value = None

    def get(self, *args):
        """
        Evaluate function and memoize her output or return memoized value when function was evaluated.

        :returns: result of function in Lazy
        :rtype: A
        """
        if self.is_evaluated:
            return self.value
        return self._compute_value(*args)


from typing import TypeVar, Generic, Callable, Union

T = TypeVar('T')
U = TypeVar('U')

class Maybe(Generic[T]):

    def __init__(self, value: T, is_nothing: bool) -> None:
        self.is_nothing = is_nothing
        if not is_nothing:
            self.value = value

    def __eq__(self, other: object) -> bool:
        return isinstance(other, Maybe) and \
            self.is_nothing == other.is_nothing and \
            (self.is_nothing or self.value == other.value)


from typing import TypeVar, Generic, Callable, Union

T = TypeVar('T')
U = TypeVar('U')

class Maybe(Generic[T]):

    def __init__(self, value: T, is_nothing: bool) -> None:
        self.is_nothing = is_nothing
        if not is_nothing:
            self.value = value

    def filter(self, filterer: Callable[[T], bool]) -> Union['Maybe[T]', 'Maybe[None]']:
        """
        If Maybe is empty or filterer returns False return default_value, in other case
        return new instance of Maybe with the same value.

        :param filterer:
        :type filterer: Function(A) -> Boolean
        :returns: copy of self when filterer returns True, in other case empty Maybe
        :rtype: Maybe[A] | Maybe[None]
        """
        if self.is_nothing or not filterer(self.value):
            return Maybe.nothing()
        return Maybe.just(self.value)


from typing import TypeVar, Generic, Callable, Union

T = TypeVar('T')
U = TypeVar('U')

class Maybe(Generic[T]):

    def __init__(self, value: T, is_nothing: bool) -> None:
        self.is_nothing = is_nothing
        if not is_nothing:
            self.value = value

    def to_lazy(self):
        """
        Transform Maybe to Try.

        :returns: Lazy monad with function returning previous value in other case Left with None
        :rtype: Lazy[Function() -> (A | None)]
        """
        from pymonet.lazy import Lazy

        if self.is_nothing:
            return Lazy(lambda: None)
        return Lazy(lambda: self.value)


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def __eq__(self, other) -> bool:
        return isinstance(other, type(self))\
            and self.value == other.value\
            and self.is_success == other.is_success


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def __str__(self) -> str:  # pragma: no cover
        return 'Try[value={}, is_success={}]'.format(self.value, self.is_success)


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def map(self, mapper):
        """
        Take function and applied this function with monad value and returns new monad with mapped value.

        :params mapper: function to apply on monad value
        :type mapper: Function(A) -> B
        :returns: for successfully new Try with mapped value, othercase copy of self
        :rtype: Try[B]
        """
        if self.is_success:
            return Try(mapper(self.value), True)
        return Try(self.value, False)


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def bind(self, binder):
        """
        Take function and applied this function with monad value and returns function result.

        :params binder: function to apply on monad value
        :type binder: Function(A) -> Try[B]
        :returns: for successfully result of binder, othercase copy of self
        :rtype: Try[B]
        """
        if self.is_success:
            return binder(self.value)
        return self


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def on_success(self, success_callback):
        """
        Call success_callback function with monad value when monad is successfully.

        :params success_callback: function to apply with monad value.
        :type success_callback: Function(A)
        :returns: self
        :rtype: Try[A]
        """
        if self.is_success:
            success_callback(self.value)
        return self


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def on_fail(self, fail_callback):
        """
        Call success_callback function with monad value when monad is not successfully.

        :params fail_callback: function to apply with monad value.
        :type fail_callback: Function(A)
        :returns: self
        :rtype: Try[A]
        """
        if not self.is_success:
            fail_callback(self.value)
        return self


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def filter(self, filterer):
        """
        Take filterer function, when monad is successfully call filterer with monad value.
        When filterer returns True method returns copy of monad, othercase
        not successfully Try with previous value.

        :params filterer: function to apply on monad value
        :type filterer: Function(A) -> Boolean
        :returns: Try with previous value
        :rtype: Try[A]
        """
        if self.is_success and filterer(self.value):
            return Try(self.value, True)
        return Try(self.value, False)


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def get(self):
        """
        Return monad value.

        :returns: monad value
        :rtype: A
        """
        return self.value


from typing import Callable

class Try:

    def __init__(self, value, is_success: bool) -> None:
        self.value = value
        self.is_success = is_success

    def get_or_else(self, default_value):
        """
        Return monad value when is successfully.
        Othercase return default_value argument.

        :params default_value: value to return when monad is not successfully.
        :type default_value: B
        :returns: monad value
        :rtype: A | B
        """
        if self.is_success:
            return self.value
        return default_value


class Semigroup:

    def __init__(self, value):
        self.value = value


class Semigroup:

    def __init__(self, value):
        self.value = value

    def __eq__(self, other) -> bool:
        return self.value == other.value


class Semigroup:

    def __init__(self, value):
        self.value = value

    def fold(self, fn):
        return fn(self.value)


class Sum(Semigroup):

    neutral_element = 0

    def __str__(self) -> str:  # pragma: no cover
        return 'Sum[value={}]'.format(self.value)


class Sum(Semigroup):

    neutral_element = 0

    def concat(self, semigroup: 'Sum') -> 'Sum':
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Sum[B]
        :returns: new Sum with sum of concat semigroups values
        :rtype: Sum[A]
        """
        return Sum(self.value + semigroup.value)


class All(Semigroup):

    neutral_element = True

    def __str__(self) -> str:  # pragma: no cover
        return 'All[value={}]'.format(self.value)


class All(Semigroup):

    neutral_element = True

    def concat(self, semigroup: 'All') -> 'All':
        """
        :param semigroup: other semigroup to concat
        :type semigroup: All[B]
        :returns: new All with last truly value or first falsy
        :rtype: All[A | B]
        """
        return All(self.value and semigroup.value)


class One(Semigroup):

    neutral_element = False

    def __str__(self) -> str:  # pragma: no cover
        return 'One[value={}]'.format(self.value)


class One(Semigroup):

    neutral_element = False

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: One[B]
        :returns: new One with first truly value or last falsy
        :rtype: One[A | B]
        """
        return One(self.value or semigroup.value)


class First(Semigroup):

    def __str__(self) -> str:  # pragma: no cover
        return 'Fist[value={}]'.format(self.value)


class First(Semigroup):

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: First[B]
        :returns: new First with first value
        :rtype: First[A]
        """
        return First(self.value)


class Last(Semigroup):

    def __str__(self) -> str:  # pragma: no cover
        return 'Last[value={}]'.format(self.value)


class Last(Semigroup):

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Last[B]
        :returns: new Last with last value
        :rtype: Last[A]
        """
        return Last(semigroup.value)


class Map(Semigroup):

    def __str__(self) -> str:  # pragma: no cover
        return 'Map[value={}]'.format(self.value)


class Map(Semigroup):

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Map[B]
        :returns: new Map with concated all values
        :rtype: Map[A]
        """
        return Map(
            {key: value.concat(semigroup.value[key]) for key, value in self.value.items()}
        )


class Max(Semigroup):

    neutral_element = -float("inf")

    def __str__(self) -> str:  # pragma: no cover
        return 'Max[value={}]'.format(self.value)


class Max(Semigroup):

    neutral_element = -float("inf")

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Max[B]
        :returns: new Max with largest value
        :rtype: Max[A | B]
        """
        return Max(self.value if self.value > semigroup.value else semigroup.value)


class Min(Semigroup):

    neutral_element = float("inf")

    def __str__(self) -> str:  # pragma: no cover
        return 'Min[value={}]'.format(self.value)


class Min(Semigroup):

    neutral_element = float("inf")

    def concat(self, semigroup):
        """
        :param semigroup: other semigroup to concat
        :type semigroup: Min[B]
        :returns: new Min with smallest value
        :rtype: Min[A | B]
        """
        return Min(self.value if self.value <= semigroup.value else semigroup.value)


class Task:

    def __init__(self, fork):
        """
        :param fork: function to call during fork
        :type fork: Function(reject, resolve) -> Any
        """
        self.fork = fork

    def map(self, fn):
        """
        Take function, store it and call with Task value during calling fork function.
        Return new Task with result of called.

        :param fn: mapper function
        :type fn: Function(value) -> B
        :returns: new Task with mapped resolve attribute
        :rtype: Task[Function(resolve, reject -> A | B]
        """
        def result(reject, resolve):
            return self.fork(
                lambda arg: reject(arg),
                lambda arg: resolve(fn(arg))
            )

        return Task(result)


class Task:

    def __init__(self, fork):
        """
        :param fork: function to call during fork
        :type fork: Function(reject, resolve) -> Any
        """
        self.fork = fork

    def bind(self, fn):
        """
        Take function, store it and call with Task value during calling fork function.
        Return result of called.

        :param fn: mapper function
        :type fn: Function(value) -> Task[reject, mapped_value]
        :returns:  new Task with mapper resolve attribute
        :rtype: Task[reject, mapped_value]
        """
        def result(reject, resolve):
            return self.fork(
                lambda arg: reject(arg),
                lambda arg: fn(arg).fork(reject, resolve)
            )

        return Task(result)


from functools import reduce
from typing import TypeVar, Callable, List, Tuple, Any

T = TypeVar('T')

def curry(x, args_count=None):
    """
    In mathematics and computer science, currying is the technique of translating the evaluation of a function.
    It that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions.
    each with a single argument.
    """
    if args_count is None:
        args_count = x.__code__.co_argcount

    def fn(*args):
        if len(args) == args_count:
            return x(*args)
        return curry(lambda *args1: x(*(args + args1)), args_count - len(args))
    return fn


from functools import reduce
from typing import TypeVar, Callable, List, Tuple, Any

T = TypeVar('T')

def cond(condition_list: List[Tuple[
    Callable[[T], bool],
    Callable,
]]):
    """
    Function for return function depended on first function argument
    cond get list of two-item tuples,
    first is condition_function, second is execute_function.
    Returns this execute_function witch first condition_function return truly value.

    :param condition_list: list of two-item tuples (condition_function, execute_function)
    :type condition_list: List[(Function, Function)]
    :returns: Returns this execute_function witch first condition_function return truly value
    :rtype: Function
    """
    def result(*args):
        for (condition_function, execute_function) in condition_list:
            if condition_function(*args):
                return execute_function(*args)

    return result


from functools import reduce
from typing import TypeVar, Callable, List, Tuple, Any

T = TypeVar('T')

def memoize(fn: Callable, key=eq) -> Callable:
    """
    Create a new function that, when invoked,
    caches the result of calling fn for a given argument set and returns the result.
    Subsequent calls to the memoized fn with the same argument set will not result in an additional call to fn;
    instead, the cached result for that set of arguments will be returned.

    :param fn: function to invoke
    :type fn: Function(A) -> B
    :param key: function to decide if result should be taken from cache
    :type key: Function(A, A) -> Boolean
    :returns: new function invoking old one
    :rtype: Function(A) -> B
    """
    cache: List[Any] = []

    def memoized_fn(argument):
        cached_result = find(cache, lambda cacheItem: key(cacheItem[0], argument))
        if cached_result is not None:
            return cached_result[1]
        fn_result = fn(argument)
        cache.append((argument, fn_result))

        return fn_result

    return memoized_fn


class Validation:

    def __init__(self, value, errors):
        self.value = value
        self.errors = errors

    def __eq__(self, other):
        """
        Two Validations are equals when values and errors lists are equal.
        """
        return (isinstance(other, Validation) and
                self.errors == other.errors and
                self.value == other.value)


class Validation:

    def __init__(self, value, errors):
        self.value = value
        self.errors = errors

    def __str__(self):  # pragma: no cover
        if self.is_success():
            return 'Validation.success[{}]'.format(self.value)
        return 'Validation.fail[{}, {}]'.format(self.value, self.errors)


class Validation:

    def __init__(self, value, errors):
        self.value = value
        self.errors = errors

    def to_maybe(self):
        """
        Transform Validation to Maybe.

        :returns: Maybe with Validation Value when Validation has no errors, in other case empty Maybe
        :rtype: Maybe[A | None]
        """
        from pymonet.maybe import Maybe

        if self.is_success():
            return Maybe.just(self.value)
        return Maybe.nothing()


class Validation:

    def __init__(self, value, errors):
        self.value = value
        self.errors = errors

    def to_lazy(self):
        """
        Transform Validation to Try.

        :returns: Lazy monad with function returning Validation value
        :rtype: Lazy[Function() -> (A | None)]
        """
        from pymonet.lazy import Lazy

        return Lazy(lambda: self.value)


import datetime
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, Generic, Iterable, List, Protocol, Set, TypeVar
from ..commons.numbers import Amount, Quantity, isum
from ..commons.others import Guid, makeguid
from ..commons.zeitgeist import DateRange
from .accounts import Account, AccountType

__all__ = [
    "Direction",
    "JournalEntry",
    "Posting",
    "ReadJournalEntries",
]
_T = TypeVar("_T")
_debit_mapping: Dict[Direction, Set[AccountType]] = {
    Direction.INC: {AccountType.ASSETS, AccountType.EQUITIES, AccountType.LIABILITIES},
    Direction.DEC: {AccountType.REVENUES, AccountType.EXPENSES},
}

class ReadJournalEntries(Protocol[_T]):

    def __call__(self, period: DateRange) -> Iterable[JournalEntry[_T]]:
        pass


import datetime
from dataclasses import dataclass, field
from decimal import Decimal
from typing import Dict, Generic, Iterable, List, Optional, Protocol, TypeVar
from ..commons.numbers import Amount, Quantity
from ..commons.zeitgeist import DateRange
from .accounts import Account
from .generic import Balance
from .journaling import JournalEntry, Posting, ReadJournalEntries

__all__ = [
    "GeneralLedger",
    "GeneralLedgerProgram",
    "InitialBalances",
    "Ledger",
    "LedgerEntry",
    "ReadInitialBalances",
    "build_general_ledger",
    "compile_general_ledger_program",
]
_T = TypeVar("_T")
InitialBalances = Dict[Account, Balance]

def build_general_ledger(
    period: DateRange, journal: Iterable[JournalEntry[_T]], initial: InitialBalances
) -> GeneralLedger[_T]:
    """
    Builds a general ledger.

    :param period: Accounting period.
    :param journal: All available journal entries.
    :param initial: Opening balances for terminal accounts, if any.
    :return: A :py:class:`GeneralLedger` instance.
    """
    ## Initialize ledgers buffer as per available initial balances:
    ledgers: Dict[Account, Ledger[_T]] = {a: Ledger(a, b) for a, b in initial.items()}

    ## Iterate over journal postings and populate ledgers:
    for posting in (p for j in journal for p in j.postings if period.since <= j.date <= period.until):
        ## Check if we have the ledger yet, and create if not:
        if posting.account not in ledgers:
            ledgers[posting.account] = Ledger(posting.account, Balance(period.since, Quantity(Decimal(0))))

        ## Add the posting to the ledger:
        ledgers[posting.account].add(posting)

    ## Done, return general ledger.
    return GeneralLedger(period, ledgers)


import datetime
from dataclasses import dataclass, field
from decimal import Decimal
from typing import Dict, Generic, Iterable, List, Optional, Protocol, TypeVar
from ..commons.numbers import Amount, Quantity
from ..commons.zeitgeist import DateRange
from .accounts import Account
from .generic import Balance
from .journaling import JournalEntry, Posting, ReadJournalEntries

__all__ = [
    "GeneralLedger",
    "GeneralLedgerProgram",
    "InitialBalances",
    "Ledger",
    "LedgerEntry",
    "ReadInitialBalances",
    "build_general_ledger",
    "compile_general_ledger_program",
]
_T = TypeVar("_T")
InitialBalances = Dict[Account, Balance]

def compile_general_ledger_program(
    read_initial_balances: ReadInitialBalances,
    read_journal_entries: ReadJournalEntries[_T],
) -> GeneralLedgerProgram[_T]:
    """
    Consumes implementations of the algebra and returns a program which consumes opening and closing dates and produces
    a general ledger.

    :param read_initial_balances: Algebra implementation which reads initial balances.
    :param read_journal_entries: Algebra implementation which reads journal entries.
    :return: A function which consumes opening and closing dates and produces a general ledger
    """

    def _program(period: DateRange) -> GeneralLedger[_T]:
        """
        Consumes the opening and closing dates and produces a general ledger.

        :param period: Accounting period.
        :return: A general ledger.
        """
        ## Get initial balances as of the end of previous financial period:
        initial_balances = read_initial_balances(period)

        ## Read journal entries and post each of them:
        journal_entries = read_journal_entries(period)

        ## Build the general ledger and return:
        return build_general_ledger(period, journal_entries, initial_balances)

    ## Return the compiled program.
    return _program


import datetime
from dataclasses import dataclass, field
from decimal import Decimal
from typing import Dict, Generic, Iterable, List, Optional, Protocol, TypeVar
from ..commons.numbers import Amount, Quantity
from ..commons.zeitgeist import DateRange
from .accounts import Account
from .generic import Balance
from .journaling import JournalEntry, Posting, ReadJournalEntries

__all__ = [
    "GeneralLedger",
    "GeneralLedgerProgram",
    "InitialBalances",
    "Ledger",
    "LedgerEntry",
    "ReadInitialBalances",
    "build_general_ledger",
    "compile_general_ledger_program",
]
_T = TypeVar("_T")
InitialBalances = Dict[Account, Balance]

class ReadInitialBalances(Protocol):

    def __call__(self, period: DateRange) -> InitialBalances:
        pass


import datetime
from dataclasses import dataclass, field
from decimal import Decimal
from typing import Dict, Generic, Iterable, List, Optional, Protocol, TypeVar
from ..commons.numbers import Amount, Quantity
from ..commons.zeitgeist import DateRange
from .accounts import Account
from .generic import Balance
from .journaling import JournalEntry, Posting, ReadJournalEntries

__all__ = [
    "GeneralLedger",
    "GeneralLedgerProgram",
    "InitialBalances",
    "Ledger",
    "LedgerEntry",
    "ReadInitialBalances",
    "build_general_ledger",
    "compile_general_ledger_program",
]
_T = TypeVar("_T")
InitialBalances = Dict[Account, Balance]

class GeneralLedgerProgram(Protocol[_T]):

    def __call__(self, period: DateRange) -> GeneralLedger[_T]:
        pass


import calendar
import datetime
from decimal import Decimal
from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Set, Union
from dateutil.relativedelta import relativedelta
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currencies, Currency
from .monetary import Money

__all__ = ["DCC", "DCCRegistry"]
DCFC = Callable[[Date, Date, Date, Optional[Decimal]], Decimal]
DCCRegistry = DCCRegistryMachinery()

class DCC(NamedTuple):

    name: str

    altnames: Set[str]

    currencies: Set[Currency]

    calculate_fraction_method: DCFC

    def calculate_fraction(self, start: Date, asof: Date, end: Date, freq: Optional[Decimal] = None) -> Decimal:
        """
        Calculates the day count fraction based on the underlying methodology after performing some general checks.
        """
        ## Checks if dates are provided properly:
        if not start <= asof <= end:
            ## Nope, return 0:
            return ZERO

        ## Cool, we can proceed with calculation based on the methodology:
        return self[3](start, asof, end, freq)


import calendar
import datetime
from decimal import Decimal
from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Set, Union
from dateutil.relativedelta import relativedelta
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currencies, Currency
from .monetary import Money

__all__ = ["DCC", "DCCRegistry"]
DCFC = Callable[[Date, Date, Date, Optional[Decimal]], Decimal]
DCCRegistry = DCCRegistryMachinery()

class DCC(NamedTuple):

    name: str

    altnames: Set[str]

    currencies: Set[Currency]

    calculate_fraction_method: DCFC

    def calculate_daily_fraction(self, start: Date, asof: Date, end: Date, freq: Optional[Decimal] = None) -> Decimal:
        """
        Calculates daily fraction.
        """
        ## Get t-1 for asof:
        asof_minus_1 = asof - datetime.timedelta(days=1)

        ## Get the yesterday's factor:
        if asof_minus_1 < start:
            yfact = ZERO
        else:
            yfact = self.calculate_fraction_method(start, asof_minus_1, end, freq)

        ## Get today's factor:
        tfact = self.calculate_fraction_method(start, asof, end, freq)

        ## Get the factor and return:
        return tfact - yfact


import calendar
import datetime
from decimal import Decimal
from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Set, Union
from dateutil.relativedelta import relativedelta
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currencies, Currency
from .monetary import Money

__all__ = ["DCC", "DCCRegistry"]
DCFC = Callable[[Date, Date, Date, Optional[Decimal]], Decimal]
DCCRegistry = DCCRegistryMachinery()

class DCC(NamedTuple):

    name: str

    altnames: Set[str]

    currencies: Set[Currency]

    calculate_fraction_method: DCFC

    def interest(
        self,
        principal: Money,
        rate: Decimal,
        start: Date,
        asof: Date,
        end: Optional[Date] = None,
        freq: Optional[Decimal] = None,
    ) -> Money:
        """
        Calculates the accrued interest.
        """
        return principal * rate * self.calculate_fraction(start, asof, end or asof, freq)


import calendar
import datetime
from decimal import Decimal
from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Set, Union
from dateutil.relativedelta import relativedelta
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currencies, Currency
from .monetary import Money

__all__ = ["DCC", "DCCRegistry"]
DCFC = Callable[[Date, Date, Date, Optional[Decimal]], Decimal]
DCCRegistry = DCCRegistryMachinery()

class DCC(NamedTuple):

    name: str

    altnames: Set[str]

    currencies: Set[Currency]

    calculate_fraction_method: DCFC

    def coupon(
        self,
        principal: Money,
        rate: Decimal,
        start: Date,
        asof: Date,
        end: Date,
        freq: Union[int, Decimal],
        eom: Optional[int] = None,
    ) -> Money:
        """
        Calculates the accrued interest for the coupon payment.

        This method is primarily used for bond coupon accruals which assumes the start date to be the first of regular
        payment schedules.
        """
        ## Find the previous and next payment dates:
        prevdate = _last_payment_date(start, asof, freq, eom)
        nextdate = _next_payment_date(prevdate, freq, eom)

        ## Calculate the interest and return:
        return self.interest(principal, rate, prevdate, asof, nextdate, Decimal(freq))


import calendar
import datetime
from decimal import Decimal
from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Set, Union
from dateutil.relativedelta import relativedelta
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currencies, Currency
from .monetary import Money

__all__ = ["DCC", "DCCRegistry"]
DCFC = Callable[[Date, Date, Date, Optional[Decimal]], Decimal]
DCCRegistry = DCCRegistryMachinery()

class DCCRegistryMachinery:

    def __init__(self) -> None:
        """
        Initializes the registry.
        """
        ## Define the main registry buffer:
        self._buffer_main: Dict[str, DCC] = {}

        ## Defines the registry buffer for alternative DCC names:
        self._buffer_altn: Dict[str, DCC] = {}

    def register(self, dcc: DCC) -> None:
        """
        Attempts to register the given day count convention.
        """
        ## Check if the main name is ever registered before:
        if self._is_registered(dcc.name):
            ## Yep, raise a TypeError:
            raise TypeError(f"Day count convention '{dcc.name}' is already registered")

        ## Add to the main buffer:
        self._buffer_main[dcc.name] = dcc

        ## Check if there is any registry conflict:
        for name in dcc.altnames:
            ## Check if the name is ever registered:
            if self._is_registered(name):
                ## Yep, raise a TypeError:
                raise TypeError(f"Day count convention '{dcc.name}' is already registered")

            ## Register to the alternative buffer:
            self._buffer_altn[name] = dcc


import calendar
import datetime
from decimal import Decimal
from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Set, Union
from dateutil.relativedelta import relativedelta
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currencies, Currency
from .monetary import Money

__all__ = ["DCC", "DCCRegistry"]
DCFC = Callable[[Date, Date, Date, Optional[Decimal]], Decimal]
DCCRegistry = DCCRegistryMachinery()

class DCCRegistryMachinery:

    def __init__(self) -> None:
        """
        Initializes the registry.
        """
        ## Define the main registry buffer:
        self._buffer_main: Dict[str, DCC] = {}

        ## Defines the registry buffer for alternative DCC names:
        self._buffer_altn: Dict[str, DCC] = {}

    def find(self, name: str) -> Optional[DCC]:
        """
        Attempts to find the day count convention by the given name.

        Note that all day count conventions are registered under stripped, uppercased names. Therefore,
        the implementation will first attempt to find by given name as is. If it can not find it, it will
        strip and uppercase the name and try to find it as such as a last resort.
        """
        return self._find_strict(name) or self._find_strict(name.strip().upper())


from abc import ABCMeta, abstractmethod
from decimal import Decimal
from typing import Iterable, NamedTuple, Optional, Tuple
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currency

__all__ = ["FXRate", "FXRateLookupError", "FXRateService"]

class FXRateLookupError(LookupError):

    def __init__(self, ccy1: Currency, ccy2: Currency, asof: Date) -> None:
        """
        Initializes the foreign exchange rate lookup error.
        """
        ## Keep the slots:
        self.ccy1 = ccy1
        self.ccy2 = ccy2
        self.asof = asof

        ## Set the message:
        super().__init__(f"Foreign exchange rate for {ccy1}/{ccy2} not found as of {asof}")


from abc import ABCMeta, abstractmethod
from decimal import Decimal
from typing import Iterable, NamedTuple, Optional, Tuple
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currency

__all__ = ["FXRate", "FXRateLookupError", "FXRateService"]

class FXRate(NamedTuple):

    ccy1: Currency

    ccy2: Currency

    date: Date

    value: Decimal

    def __invert__(self) -> "FXRate":
        """
        Returns the inverted foreign exchange rate.

        >>> import datetime
        >>> from decimal import Decimal
        >>> from pypara.currencies import Currencies
        >>> nrate = FXRate(Currencies["EUR"], Currencies["USD"], datetime.date.today(), Decimal("2"))
        >>> rrate = FXRate(Currencies["USD"], Currencies["EUR"], datetime.date.today(), Decimal("0.5"))
        >>> ~nrate == rrate
        True
        """
        return FXRate(self[1], self[0], self[2], self[3] ** -1)


from abc import ABCMeta, abstractmethod
from decimal import Decimal
from typing import Iterable, NamedTuple, Optional, Tuple
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currency

__all__ = ["FXRate", "FXRateLookupError", "FXRateService"]

class FXRateService(metaclass=ABCMeta):

    default: Optional["FXRateService"] = None

    TQuery = Tuple[Currency, Currency, Date]

    @abstractmethod
    def query(self, ccy1: Currency, ccy2: Currency, asof: Date, strict: bool = False) -> Optional[FXRate]:
        """
        Returns the foreign exchange rate of a given currency pair as of a given date.

        :param ccy1: The first currency of foreign exchange rate.
        :param ccy2: The second currency of foreign exchange rate.
        :param asof: Temporal dimension the foreign exchange rate is effective as of.
        :param strict: Indicates if we should raise a lookup error if that the foreign exchange rate can not be found.
        :return: The foreign exhange rate as a :class:`Decimal` instance or None.
        """
        pass


from abc import ABCMeta, abstractmethod
from decimal import Decimal
from typing import Iterable, NamedTuple, Optional, Tuple
from .commons.numbers import ONE, ZERO
from .commons.zeitgeist import Date
from .currencies import Currency

__all__ = ["FXRate", "FXRateLookupError", "FXRateService"]

class FXRateService(metaclass=ABCMeta):

    default: Optional["FXRateService"] = None

    TQuery = Tuple[Currency, Currency, Date]

    @abstractmethod
    def queries(self, queries: Iterable[TQuery], strict: bool = False) -> Iterable[Optional[FXRate]]:
        """
        Returns foreign exchange rates for a given collection of currency pairs and dates.

        :param queries: An iterable of :class:`Currency`, :class:`Currency` and :class:`Temporal` tuples.
        :param strict: Indicates if we should raise a lookup error if that the foreign exchange rate can not be found.
        :return: An iterable of rates.
        """
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def is_equal(self, other: Any) -> bool:
        """
        Checks the equality of two money objects.

        In particular:

        1. ``True`` if ``other`` is a money object **and** all slots are same.
        2. ``False`` otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def as_boolean(self) -> bool:
        """
        Returns the logical representation of the money object.

        In particular:

        1. ``False`` if money is *undefined* **or** money quantity is ``zero``.
        2. ``True`` otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def as_float(self) -> float:
        """
        Returns the quantity as a ``float`` if *defined*, raises class:`MonetaryOperationException` otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def as_integer(self) -> int:
        """
        Returns the quantity as an ``int`` if *defined*, raises class:`MonetaryOperationException` otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def abs(self) -> "Money":
        """
        Returns the absolute money if *defined*, itself otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def negative(self) -> "Money":
        """
        Negates the quantity of the monetary value if *defined*, itself otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def positive(self) -> "Money":
        """
        Returns same monetary value if *defined*, itself otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def round(self, ndigits: int = 0) -> "Money":
        """
        Rounds the quantity of the monetary value to ``ndigits`` by using ``HALF_EVEN`` method if *defined*, itself
        otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class IncompatibleCurrencyError(ValueError):

    def __init__(self, ccy1: Currency, ccy2: Currency, operation: str = "<Unspecified>") -> None:
        """
        Initializes an incompatible currency error message.
        """
        ## Keep sloys:
        self.ccy1 = ccy1
        self.ccy2 = ccy2
        self.operation = operation

        ## Call super:
        super().__init__(f"{ccy1.code} vs {ccy2.code} are incompatible for operation '{operation}'.")

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def add(self, other: "Money") -> "Money":
        """
        Performs monetary addition on the money object and the given ``other`` money object.

        Note that::

        1. Raises :class:`IncompatibleCurrencyError` if currencies do not match.
        2. If any of the operands are undefined, returns the other one conveniently.
        3. Dates are carried forward as a result of addition of two defined money objects.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def scalar_add(self, other: Numeric) -> "Money":
        """
        Performs scalar addition on the quantity of the money.

        Note that undefined money object is returned as is.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class IncompatibleCurrencyError(ValueError):

    def __init__(self, ccy1: Currency, ccy2: Currency, operation: str = "<Unspecified>") -> None:
        """
        Initializes an incompatible currency error message.
        """
        ## Keep sloys:
        self.ccy1 = ccy1
        self.ccy2 = ccy2
        self.operation = operation

        ## Call super:
        super().__init__(f"{ccy1.code} vs {ccy2.code} are incompatible for operation '{operation}'.")

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def subtract(self, other: "Money") -> "Money":
        """
        Performs monetary subtraction on the money object and the given ``other`` money object.

        Note that::

        1. Raises :class:`IncompatibleCurrencyError` if currencies do not match.
        2. If any of the operands are undefined, returns the other one conveniently.
        3. Dates are carried forward as a result of addition of two defined money objects.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def scalar_subtract(self, other: Numeric) -> "Money":
        """
        Performs scalar subtraction on the quantity of the money.

        Note that undefined money object is returned as is.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def multiply(self, other: Numeric) -> "Money":
        """
        Performs scalar multiplication.

        Note that undefined money object is returned as is.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def divide(self, other: Numeric) -> "Money":
        """
        Performs ordinary division on the money object if *defined*, itself otherwise.

        Note that division by zero yields an undefined money object.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def floor_divide(self, other: Numeric) -> "Money":
        """
        Performs floor division on the money object if *defined*, itself otherwise.

        Note that division by zero yields an undefined money object.

        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class IncompatibleCurrencyError(ValueError):

    def __init__(self, ccy1: Currency, ccy2: Currency, operation: str = "<Unspecified>") -> None:
        """
        Initializes an incompatible currency error message.
        """
        ## Keep sloys:
        self.ccy1 = ccy1
        self.ccy2 = ccy2
        self.operation = operation

        ## Call super:
        super().__init__(f"{ccy1.code} vs {ccy2.code} are incompatible for operation '{operation}'.")

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def lt(self, other: "Money") -> bool:
        """
        Applies "less than" comparison against ``other`` money.

        Note that::

        1. Undefined money objects are always less than ``other`` if ``other`` is not undefined, and
        2. :class:`IncompatibleCurrencyError` is raised when comparing two defined money objects with different
        currencies.
        """
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class IncompatibleCurrencyError(ValueError):

    def __init__(self, ccy1: Currency, ccy2: Currency, operation: str = "<Unspecified>") -> None:
        """
        Initializes an incompatible currency error message.
        """
        ## Keep sloys:
        self.ccy1 = ccy1
        self.ccy2 = ccy2
        self.operation = operation

        ## Call super:
        super().__init__(f"{ccy1.code} vs {ccy2.code} are incompatible for operation '{operation}'.")

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def lte(self, other: "Money") -> bool:
        """
        Applies "less than or equal to" comparison against ``other`` money.

        Note that::

        1. Undefined money objects are always less than or equal to ``other``, and
        2. :class:`IncompatibleCurrencyError` is raised when comparing two defined money objects with different
        currencies.
        """
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class IncompatibleCurrencyError(ValueError):

    def __init__(self, ccy1: Currency, ccy2: Currency, operation: str = "<Unspecified>") -> None:
        """
        Initializes an incompatible currency error message.
        """
        ## Keep sloys:
        self.ccy1 = ccy1
        self.ccy2 = ccy2
        self.operation = operation

        ## Call super:
        super().__init__(f"{ccy1.code} vs {ccy2.code} are incompatible for operation '{operation}'.")

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def gt(self, other: "Money") -> bool:
        """
        Applies "greater than" comparison against ``other`` money.

        Note that::

        1. Undefined money objects are never greater than ``other``,
        2. Defined money objects are always greater than ``other`` if other is undefined, and
        3. :class:`IncompatibleCurrencyError` is raised when comparing two defined money objects with different
        currencies.
        """
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class IncompatibleCurrencyError(ValueError):

    def __init__(self, ccy1: Currency, ccy2: Currency, operation: str = "<Unspecified>") -> None:
        """
        Initializes an incompatible currency error message.
        """
        ## Keep sloys:
        self.ccy1 = ccy1
        self.ccy2 = ccy2
        self.operation = operation

        ## Call super:
        super().__init__(f"{ccy1.code} vs {ccy2.code} are incompatible for operation '{operation}'.")

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def gte(self, other: "Money") -> bool:
        """
        Applies "greater than or equal to" comparison against ``other`` money.

        Note that::

        1. Undefined money objects are never greater than or equal to ``other`` if ``other`` is defined,
        2. Undefined money objects are greater than or equal to ``other`` if ``other is undefined, and
        3. :class:`IncompatibleCurrencyError` is raised when comparing two defined money objects with different
        currencies.
        """
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def with_ccy(self, ccy: Currency) -> "Money":
        """
        Creates a new money object with the given currency if money is *defined*, returns itself otherwise.
        """
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def with_qty(self, qty: Decimal) -> "Money":
        """
        Creates a new money object with the given quantity if money is *defined*, returns itself otherwise.
        """
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def with_dov(self, dov: Date) -> "Money":
        """
        Creates a new money object with the given value date if money is *defined*, returns itself otherwise.
        """
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def convert(self, to: Currency, asof: Optional[Date] = None, strict: bool = False) -> "Money":
        """
        Converts the monetary value from one currency to another.

        Raises :class:`FXRateLookupError` if no foreign exchange rate can be found for conversion.

        Note that we will carry the date forward as per ``asof`` date.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __bool__(self) -> bool:
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __eq__(self, other: Any) -> bool:
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __abs__(self) -> "Money":
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __float__(self) -> float:
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __int__(self) -> int:
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __neg__(self) -> "Money":
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __pos__(self) -> "Money":
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __add__(self, other: "Money") -> "Money":
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __sub__(self, other: "Money") -> "Money":
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __mul__(self, other: Numeric) -> "Money":
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __truediv__(self, other: Numeric) -> "Money":
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __floordiv__(self, other: Numeric) -> "Money":
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __lt__(self, other: "Money") -> bool:
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __le__(self, other: "Money") -> bool:
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __gt__(self, other: "Money") -> bool:
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Money:

    __slots__ = ()

    NA: "Money"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def __ge__(self, other: "Money") -> bool:
        pass


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class SomeMoney(Money, NamedTuple("SomeMoney", [("ccy", Currency), ("qty", Decimal), ("dov", Date)])):

    __slots__ = ()

    defined = True

    undefined = False

    __bool__ = as_boolean

    __eq__ = is_equal

    __abs__ = abs

    __float__ = as_float

    __int__ = as_integer

    __neg__ = negative

    __pos__ = positive

    __add__ = add

    __sub__ = subtract

    __mul__ = multiply

    __truediv__ = divide

    __floordiv__ = floor_divide

    __lt__ = lt

    __le__ = lte

    __gt__ = gt

    __ge__ = gte

    def round(self, ndigits: int = 0) -> "Money":
        c, q, d = self
        dec = c.decimals
        return SomeMoney(c, q.__round__(ndigits if ndigits < dec else dec), d)


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class SomeMoney(Money, NamedTuple("SomeMoney", [("ccy", Currency), ("qty", Decimal), ("dov", Date)])):

    __slots__ = ()

    defined = True

    undefined = False

    __bool__ = as_boolean

    __eq__ = is_equal

    __abs__ = abs

    __float__ = as_float

    __int__ = as_integer

    __neg__ = negative

    __pos__ = positive

    __add__ = add

    __sub__ = subtract

    __mul__ = multiply

    __truediv__ = divide

    __floordiv__ = floor_divide

    __lt__ = lt

    __le__ = lte

    __gt__ = gt

    __ge__ = gte

    def with_dov(self, dov: Date) -> "Money":
        return SomeMoney(self[0], self[1], dov)


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class SomeMoney(Money, NamedTuple("SomeMoney", [("ccy", Currency), ("qty", Decimal), ("dov", Date)])):

    __slots__ = ()

    defined = True

    undefined = False

    __bool__ = as_boolean

    __eq__ = is_equal

    __abs__ = abs

    __float__ = as_float

    __int__ = as_integer

    __neg__ = negative

    __pos__ = positive

    __add__ = add

    __sub__ = subtract

    __mul__ = multiply

    __truediv__ = divide

    __floordiv__ = floor_divide

    __lt__ = lt

    __le__ = lte

    __gt__ = gt

    __ge__ = gte

    def convert(self, to: Currency, asof: Optional[Date] = None, strict: bool = False) -> "Money":
        ## Get slots:
        ccy, qty, dov = self

        ## Get date of conversion:
        asof = asof or dov

        ## Attempt to get the FX rate:
        try:
            rate = FXRateService.default.query(ccy, to, asof, strict)  # type: ignore
        except AttributeError as exc:
            if FXRateService.default is None:
                raise ProgrammingError("Did you implement and set the default FX rate service?")
            else:
                raise exc

        ## Do we have a rate?
        if rate is None:
            ## Nope, shall we raise exception?
            if strict:
                ## Yep:
                raise FXRateLookupError(ccy, to, asof)
            else:
                ## Just return NA:
                return NoMoney

        ## Compute and return:
        return SomeMoney(to, (qty * rate.value).quantize(to.quantizer), asof)


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Price:

    __slots__ = ()

    NA: "Price"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def is_equal(self, other: Any) -> bool:
        """
        Checks the equality of two price objects.

        In particular:

        1. ``True`` if ``other`` is a price object **and** all slots are same.
        2. ``False`` otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Price:

    __slots__ = ()

    NA: "Price"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def as_boolean(self) -> bool:
        """
        Returns the logical representation of the price object.

        In particular:

        1. ``False`` if price is *undefined* **or** price quantity is ``zero``.
        2. ``True`` otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Price:

    __slots__ = ()

    NA: "Price"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def as_float(self) -> float:
        """
        Returns the quantity as a ``float`` if *defined*, raises class:`MonetaryOperationException` otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Price:

    __slots__ = ()

    NA: "Price"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def as_integer(self) -> int:
        """
        Returns the quantity as an ``int`` if *defined*, raises class:`MonetaryOperationException` otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Price:

    __slots__ = ()

    NA: "Price"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def abs(self) -> "Price":
        """
        Returns the absolute price if *defined*, itself otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Price:

    __slots__ = ()

    NA: "Price"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def negative(self) -> "Price":
        """
        Negates the quantity of the monetary value if *defined*, itself otherwise.
        """
        raise NotImplementedError


from abc import abstractmethod
from decimal import Decimal, DivisionByZero, InvalidOperation
from typing import Any, NamedTuple, Optional, Union, overload
from .commons.errors import ProgrammingError
from .commons.numbers import Numeric
from .commons.zeitgeist import Date
from .currencies import Currency
from .exchange import FXRateLookupError, FXRateService

__all__ = [
    "IncompatibleCurrencyError",
    "MonetaryOperationException",
    "Money",
    "NoMoney",
    "NoPrice",
    "NoneMoney",
    "NonePrice",
    "Price",
    "SomeMoney",
    "SomePrice",
]
Money.NA = NoMoney = NoneMoney()
Price.NA = NoPrice = NonePrice()
Price.NA = NoPrice = NonePrice()

class Price:

    __slots__ = ()

    NA: "Price"

    ccy: Currency

    qty: Decimal

    dov: Date

    defined: bool

    undefined: bool

    @abstractmethod
    def positive(self) -> "Price":
        """
        Returns same monetary value if *defined*, itself otherwise.
        """
        raise NotImplementedError