2016-09-27 19:52:44 +02:00
|
|
|
import logging
|
2016-10-06 04:58:34 +02:00
|
|
|
from decimal import Decimal
|
|
|
|
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE
|
2016-09-27 19:52:44 +02:00
|
|
|
from lbrynet.core.Offer import Offer
|
2016-10-06 04:58:34 +02:00
|
|
|
from lbrynet.core.PriceModel import MeanAvailabilityWeightedPrice
|
2016-09-27 19:52:44 +02:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
def get_default_strategy(blob_tracker, **kwargs):
|
|
|
|
return BasicAvailabilityWeightedStrategy(blob_tracker, **kwargs)
|
|
|
|
|
|
|
|
|
2016-10-06 04:58:34 +02:00
|
|
|
class BaseStrategy(object):
|
|
|
|
def __init__(self, price_model, max_rate, min_rate, is_generous=True):
|
|
|
|
self.price_model = price_model
|
|
|
|
self.is_generous = is_generous
|
|
|
|
self.accepted_offers = {}
|
|
|
|
self.offers_sent = {}
|
|
|
|
self.offers_received = {}
|
|
|
|
self.max_rate = max_rate or Decimal(self.price_model.base_price * 100)
|
|
|
|
self.min_rate = Decimal(min_rate)
|
2016-09-27 19:52:44 +02:00
|
|
|
|
2016-10-06 04:58:34 +02:00
|
|
|
def add_offer_sent(self, peer):
|
|
|
|
turn = self.offers_sent.get(peer, 0) + 1
|
|
|
|
self.offers_sent.update({peer: turn})
|
|
|
|
|
|
|
|
def add_offer_received(self, peer):
|
|
|
|
turn = self.offers_received.get(peer, 0) + 1
|
|
|
|
self.offers_received.update({peer: turn})
|
2016-09-27 19:52:44 +02:00
|
|
|
|
2016-10-06 04:58:34 +02:00
|
|
|
def calculate_price_target(self, *args):
|
|
|
|
return self.price_model.calculate_price(*args)
|
|
|
|
|
|
|
|
def bounded_price(self, price):
|
|
|
|
price_for_return = Decimal(min(self.max_rate, max(price, self.min_rate)))
|
|
|
|
return price_for_return
|
|
|
|
|
|
|
|
def make_offer(self, peer, blobs):
|
|
|
|
offer_count = self.offers_sent.get(peer, 0)
|
|
|
|
self.add_offer_sent(peer)
|
|
|
|
if peer in self.accepted_offers:
|
|
|
|
# if there was a previous accepted offer, use that
|
|
|
|
offer = self.accepted_offers[peer]
|
|
|
|
elif offer_count == 0 and self.is_generous:
|
|
|
|
# Try asking for it for free
|
|
|
|
offer = Offer(Decimal(0.0))
|
|
|
|
else:
|
|
|
|
rates = [self.calculate_price_target(blob) for blob in blobs]
|
|
|
|
price = self._make_offer(rates, offer_count)
|
|
|
|
bounded_price = self.bounded_price(price)
|
|
|
|
offer = Offer(bounded_price)
|
|
|
|
log.debug("Offering: %s", offer.rate)
|
|
|
|
return offer
|
|
|
|
|
|
|
|
def offer_accepted(self, peer, offer):
|
|
|
|
if not offer.accepted and peer in self.accepted_offers:
|
|
|
|
del self.accepted_offers[peer]
|
|
|
|
log.debug("Throwing out old accepted offer")
|
|
|
|
if offer.accepted:
|
|
|
|
self.accepted_offers.update({peer: offer})
|
|
|
|
log.debug("Updated accepted offer %f", offer.rate)
|
2016-09-27 19:52:44 +02:00
|
|
|
|
|
|
|
def respond_to_offer(self, offer, peer, blobs):
|
2016-10-06 04:58:34 +02:00
|
|
|
offer_count = self.offers_received.get(peer, 0)
|
|
|
|
self.add_offer_received(peer)
|
|
|
|
rates = [self.calculate_price_target(blob) for blob in blobs]
|
|
|
|
price = self._respond_to_offer(rates, offer_count)
|
|
|
|
bounded_price = self.bounded_price(price)
|
|
|
|
log.debug("Price target: %f", price)
|
|
|
|
|
|
|
|
if peer in self.accepted_offers:
|
|
|
|
offer = self.accepted_offers[peer]
|
|
|
|
log.debug("Already accepted %f", offer.rate)
|
|
|
|
elif offer.rate == 0.0 and offer_count == 0 and self.is_generous:
|
2016-09-28 05:56:08 +02:00
|
|
|
# give blobs away for free by default on the first request
|
|
|
|
offer.accept()
|
2016-10-06 04:58:34 +02:00
|
|
|
self.accepted_offers.update({peer: offer})
|
|
|
|
elif offer.rate >= bounded_price:
|
|
|
|
log.debug("Accept: %f", offer.rate)
|
2016-09-27 19:52:44 +02:00
|
|
|
offer.accept()
|
2016-10-06 04:58:34 +02:00
|
|
|
self.accepted_offers.update({peer: offer})
|
2016-09-27 19:52:44 +02:00
|
|
|
else:
|
2016-10-06 04:58:34 +02:00
|
|
|
log.debug("Reject: %f", offer.rate)
|
2016-09-27 19:52:44 +02:00
|
|
|
offer.reject()
|
2016-10-06 04:58:34 +02:00
|
|
|
if peer in self.accepted_offers:
|
|
|
|
del self.accepted_offers[peer]
|
|
|
|
return offer
|
2016-09-27 19:52:44 +02:00
|
|
|
|
2016-10-06 04:58:34 +02:00
|
|
|
def _make_offer(self, rates, offer_count):
|
|
|
|
return NotImplementedError()
|
2016-09-28 05:56:08 +02:00
|
|
|
|
2016-10-06 04:58:34 +02:00
|
|
|
def _respond_to_offer(self, rates, offer_count):
|
|
|
|
return NotImplementedError()
|
2016-09-27 19:52:44 +02:00
|
|
|
|
|
|
|
|
2016-10-06 04:58:34 +02:00
|
|
|
class BasicAvailabilityWeightedStrategy(BaseStrategy):
|
|
|
|
"""
|
|
|
|
Basic strategy to target blob prices based on supply relative to mean supply
|
|
|
|
|
|
|
|
Discount price target with each incoming request, and raise it with each outgoing from the modeled price
|
|
|
|
until the rate is accepted or a threshold is reached
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, blob_tracker, acceleration=1.25, deceleration=0.9, max_rate=None, min_rate=0.0,
|
|
|
|
is_generous=True, base_price=MIN_BLOB_DATA_PAYMENT_RATE, alpha=1.0):
|
|
|
|
price_model = MeanAvailabilityWeightedPrice(blob_tracker, base_price=base_price, alpha=alpha)
|
|
|
|
BaseStrategy.__init__(self, price_model, max_rate, min_rate, is_generous)
|
|
|
|
self._acceleration = Decimal(acceleration) # rate of how quickly to ramp offer
|
|
|
|
self._deceleration = Decimal(deceleration)
|
|
|
|
|
|
|
|
def _get_mean_rate(self, rates):
|
|
|
|
mean_rate = Decimal(sum(rates)) / Decimal(max(len(rates), 1))
|
|
|
|
return mean_rate
|
2016-09-27 19:52:44 +02:00
|
|
|
|
|
|
|
def _premium(self, rate, turn):
|
2016-10-06 04:58:34 +02:00
|
|
|
return rate * (self._acceleration ** Decimal(turn))
|
2016-09-27 19:52:44 +02:00
|
|
|
|
|
|
|
def _discount(self, rate, turn):
|
2016-10-06 04:58:34 +02:00
|
|
|
return rate * (self._deceleration ** Decimal(turn))
|
|
|
|
|
|
|
|
def _respond_to_offer(self, rates, offer_count):
|
|
|
|
rate = self._get_mean_rate(rates)
|
|
|
|
discounted = self._discount(rate, offer_count)
|
|
|
|
return round(discounted, 5)
|
|
|
|
|
|
|
|
def _make_offer(self, rates, offer_count):
|
|
|
|
rate = self._get_mean_rate(rates)
|
|
|
|
with_premium = self._premium(rate, offer_count)
|
|
|
|
return round(with_premium, 5)
|