<commit_before>#!/usr/bin/env python
#   -*- encoding: UTF-8 -*-

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

from collections import deque
from collections import namedtuple
import datetime
import random
import statistics
import warnings

from tallywallet.common.finance import Note
from tallywallet.common.finance import value_series

from inventory import Commodity

fields = ["ts", "value", "currency"]
Ask = namedtuple("Ask", fields)
Bid = namedtuple("Bid", fields)
Valuation = namedtuple("Valuation", fields)


class ValueBook(dict):

    @staticmethod
    def approve(series, offer:set([Ask, Bid]), **kwargs):
        sd = statistics.pstdev(i.value for i in series)
        estimate = ValueBook.estimate(series, **kwargs)
        if isinstance(offer, Ask):
            return (offer.value < estimate.value
                    or offer.value - estimate.value < sd / 2)
        elif isinstance(offer, Bid):
            return (offer.value > estimate.value
                    or estimate.value - offer.value < sd / 2)
        else:
            raise NotImplementedError

    @staticmethod
    def estimate(series, ts=None):
        currencies = {i.currency for i in series}
        if len(currencies) > 1:
            warnings.warn("Mixed currencies ({})".format(currencies))
            return None
        else:
            return Valuation(
                None,
                statistics.median(i.value for i in series),
                currencies.pop()
            )

    def __setitem__(self, key, value):
        raise NotImplementedError

    def commit(self, commodity:Commodity, obj:set([Ask, Bid, Note])):
        if isinstance(obj, Note):
            series = super().setdefault(
                commodity,
                deque([Valuation(*i, currency=obj.currency)
                       for i in value_series(**vars(obj))],
                      maxlen=obj.term // obj.period)
            )
        elif isinstance(obj, (Ask, Bid)):
            series = self[commodity]
            series.append(obj)
        else:
            raise NotImplementedError

        return self.estimate(series)

    def consider(self, commodity:Commodity, offer:set([Ask, Bid]), constraint=1.0):
        estimate = self.estimate(self[commodity])
        if isinstance(offer, Ask):
            if offer.value < estimate.value or random.random() >= constraint:
                return self.commit(commodity, offer)
            else:
                return estimate
        elif isinstance(offer, Bid):
            if offer.value > estimate.value or random.random() <= constraint:
                return self.commit(commodity, offer)
            else:
                return estimate
        else:
            raise NotImplementedError

    def setdefault(self, key, default=None):
        raise NotImplementedError

    def update(self, other):
        raise NotImplementedError<commit_msg>Separated population of value book series from creation.<commit_after>#!/usr/bin/env python
#   -*- encoding: UTF-8 -*-

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

from collections import deque
from collections import namedtuple
import datetime
import random
import statistics
import warnings

from tallywallet.common.finance import Note
from tallywallet.common.finance import value_series

from inventory import Commodity

fields = ["ts", "value", "currency"]
Ask = namedtuple("Ask", fields)
Bid = namedtuple("Bid", fields)
Valuation = namedtuple("Valuation", fields)


class ValueBook(dict):

    @staticmethod
    def approve(series, offer:set([Ask, Bid]), **kwargs):
        sd = statistics.pstdev(i.value for i in series)
        estimate = ValueBook.estimate(series, **kwargs)
        if isinstance(offer, Ask):
            return (offer.value < estimate.value
                    or offer.value - estimate.value < sd / 2)
        elif isinstance(offer, Bid):
            return (offer.value > estimate.value
                    or estimate.value - offer.value < sd / 2)
        else:
            raise NotImplementedError

    @staticmethod
    def estimate(series, ts=None):
        currencies = {i.currency for i in series}
        if len(currencies) > 1:
            warnings.warn("Mixed currencies ({})".format(currencies))
            return None
        else:
            return Valuation(
                None,
                statistics.median(i.value for i in series),
                currencies.pop()
            )

    def __setitem__(self, key, value):
        raise NotImplementedError

    def commit(self, commodity:Commodity, obj:set([Ask, Bid, Note])):
        if isinstance(obj, Note):
            series = super().setdefault(
                commodity,
                deque([], maxlen=obj.term // obj.period)
            )
            series.extend(
                [Valuation(*i, currency=obj.currency)
                 for i in value_series(**vars(obj))]
            )
        elif isinstance(obj, (Ask, Bid)):
            series = self[commodity]
            series.append(obj)
        else:
            raise NotImplementedError

        return self.estimate(series)

    def consider(self, commodity:Commodity, offer:set([Ask, Bid]), constraint=1.0):
        estimate = self.estimate(self[commodity])
        if isinstance(offer, Ask):
            if offer.value < estimate.value or random.random() >= constraint:
                return self.commit(commodity, offer)
            else:
                return estimate
        elif isinstance(offer, Bid):
            if offer.value > estimate.value or random.random() <= constraint:
                return self.commit(commodity, offer)
            else:
                return estimate
        else:
            raise NotImplementedError

    def setdefault(self, key, default=None):
        raise NotImplementedError

    def update(self, other):
        raise NotImplementedError
{'fraction_non_alphanumeric': 0.09011019668015065, 'fraction_numerical': 0.0019528525596317478, 'mean_word_length': 2.3426573426573425, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6045335', 'n_tokens_mistral': 1944, 'n_tokens_neox': 1849, 'n_words': 636}