lbry-sdk/lbrynet/core/Strategy.py

131 lines
5.1 KiB
Python
Raw Normal View History

import logging
from decimal import Decimal
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE
from lbrynet.core.Offer import Offer
from lbrynet.core.PriceModel import MeanAvailabilityWeightedPrice
log = logging.getLogger(__name__)
def get_default_strategy(blob_tracker, **kwargs):
return BasicAvailabilityWeightedStrategy(blob_tracker, **kwargs)
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)
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})
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)
def respond_to_offer(self, offer, peer, blobs):
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:
# give blobs away for free by default on the first request
offer.accept()
self.accepted_offers.update({peer: offer})
elif offer.rate >= bounded_price:
log.debug("Accept: %f", offer.rate)
offer.accept()
self.accepted_offers.update({peer: offer})
else:
log.debug("Reject: %f", offer.rate)
offer.reject()
if peer in self.accepted_offers:
del self.accepted_offers[peer]
return offer
def _make_offer(self, rates, offer_count):
return NotImplementedError()
def _respond_to_offer(self, rates, offer_count):
return NotImplementedError()
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
def _premium(self, rate, turn):
return rate * (self._acceleration ** Decimal(turn))
def _discount(self, rate, turn):
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)