Exemple #1
0
    def offer(self, price: float, energy: float, seller: str, seller_origin,
              offer_id=None, original_offer_price=None, dispatch_event=True,
              adapt_price_with_fees=True, add_to_history=True) -> Offer:
        if self.readonly:
            raise MarketReadOnlyException()
        if energy <= 0:
            raise InvalidOffer()
        if original_offer_price is None:
            original_offer_price = price

        if adapt_price_with_fees:
            price = self._update_new_offer_price_with_fee(price, original_offer_price, energy)

        if offer_id is None:
            offer_id = self.bc_interface.create_new_offer(energy, price, seller)
        offer = Offer(offer_id, self.now, price, energy, seller, original_offer_price,
                      seller_origin=seller_origin)

        self.offers[offer.id] = offer
        if add_to_history is True:
            self.offer_history.append(offer)
            self._update_min_max_avg_offer_prices()

        log.debug(f"[OFFER][NEW][{self.name}][{self.time_slot_str}] {offer}")
        if dispatch_event is True:
            self.dispatch_market_offer_event(offer)
        return offer
Exemple #2
0
    def balancing_offer(self,
                        price: float,
                        energy: float,
                        seller: str,
                        from_agent: bool = False,
                        iaa_fee: bool = False,
                        seller_origin=None) -> BalancingOffer:
        if seller not in DeviceRegistry.REGISTRY.keys() and not from_agent:
            raise DeviceNotInRegistryError(
                f"Device {seller} "
                f"not in registry ({DeviceRegistry.REGISTRY}).")
        if self.readonly:
            raise MarketReadOnlyException()
        if energy == 0:
            raise InvalidOffer()
        if iaa_fee:
            price = price * (
                1 + self.transfer_fee_ratio) + self.transfer_fee_const * energy

        offer = BalancingOffer(str(uuid.uuid4()),
                               price,
                               energy,
                               seller,
                               seller_origin=seller_origin)
        self.offers[offer.id] = offer
        self._sorted_offers = \
            sorted(self.offers.values(), key=lambda o: o.price / o.energy)
        self.offer_history.append(offer)
        log.debug(f"[BALANCING_OFFER][NEW][{self.time_slot_str}] {offer}")
        self._notify_listeners(MarketEvent.BALANCING_OFFER, offer=offer)
        return offer
Exemple #3
0
    def balancing_offer(self, price: float, energy: float, seller: str,
                        original_offer_price=None, offer_id=None, from_agent: bool = False,
                        adapt_price_with_fees: bool = False, dispatch_event=True,
                        seller_origin=None, attributes: Dict = None,
                        requirements: List[Dict] = None) -> BalancingOffer:
        if seller not in DeviceRegistry.REGISTRY.keys() and not from_agent:
            raise DeviceNotInRegistryError(f"Device {seller} "
                                           f"not in registry ({DeviceRegistry.REGISTRY}).")
        if self.readonly:
            raise MarketReadOnlyException()
        if energy == 0:
            raise InvalidOffer()
        if adapt_price_with_fees:
            if self._is_constant_fees:
                price = price + self.fee_class.grid_fee_rate * energy
            else:
                price = price * (1 + self.fee_class.grid_fee_rate)

        if offer_id is None:
            offer_id = str(uuid.uuid4())

        offer = BalancingOffer(
            offer_id, self.now, price, energy, seller,
            seller_origin=seller_origin, attributes=attributes,
            requirements=requirements)
        self.offers[offer.id] = offer

        self.offer_history.append(offer)
        log.debug(f"[BALANCING_OFFER][NEW][{self.time_slot_str}] {offer}")
        if dispatch_event is True:
            self._notify_listeners(MarketEvent.BALANCING_OFFER, offer=offer)
        return offer
Exemple #4
0
    def delete_balancing_offer(self, offer_or_id: Union[str, BalancingOffer]):
        if self.readonly:
            raise MarketReadOnlyException()
        if isinstance(offer_or_id, Offer):
            offer_or_id = offer_or_id.id
        offer = self.offers.pop(offer_or_id, None)

        self._update_min_max_avg_offer_prices()
        if not offer:
            raise OfferNotFoundException()
        log.debug(f"[BALANCING_OFFER][DEL][{self.time_slot_str}] {offer}")
        self._notify_listeners(MarketEvent.BALANCING_OFFER_DELETED, offer=offer)
Exemple #5
0
    def delete_offer(self, offer_or_id: Union[str, Offer]):
        if self.readonly:
            raise MarketReadOnlyException()
        if isinstance(offer_or_id, Offer):
            offer_or_id = offer_or_id.id
        offer = self.offers.pop(offer_or_id, None)
        self.bc_interface.cancel_offer(offer)

        self._update_min_max_avg_offer_prices()
        if not offer:
            raise OfferNotFoundException()
        log.debug(f"[OFFER][DEL][{self.name}][{self.time_slot_str}] {offer}")
        # TODO: Once we add event-driven blockchain, this should be asynchronous
        self._notify_listeners(MarketEvent.OFFER_DELETED, offer=offer)
Exemple #6
0
 def balancing_offer(self,
                     price: float,
                     energy: float,
                     seller: str,
                     from_agent: bool = False) -> BalancingOffer:
     if seller not in DeviceRegistry.REGISTRY.keys() and not from_agent:
         raise DeviceNotInRegistryError(
             f"Device {seller} "
             f"not in registry ({DeviceRegistry.REGISTRY}).")
     if self.readonly:
         raise MarketReadOnlyException()
     if energy == 0:
         raise InvalidOffer()
     offer = BalancingOffer(str(uuid.uuid4()), price, energy, seller, self)
     self.offers[offer.id] = offer
     self._sorted_offers = \
         sorted(self.offers.values(), key=lambda o: o.price / o.energy)
     log.info(f"[BALANCING_OFFER][NEW][{self.time_slot_str}] {offer}")
     self._notify_listeners(MarketEvent.BALANCING_OFFER, offer=offer)
     return offer
Exemple #7
0
    def accept_offer(self,
                     offer_or_id: Union[str, BalancingOffer],
                     buyer: str,
                     *,
                     energy: int = None,
                     time: DateTime = None,
                     already_tracked: bool = False,
                     trade_rate: float = None,
                     trade_bid_info: float = None,
                     buyer_origin=None) -> BalancingTrade:
        if self.readonly:
            raise MarketReadOnlyException()

        if isinstance(offer_or_id, Offer):
            offer_or_id = offer_or_id.id
        offer = self.offers.pop(offer_or_id, None)
        if offer is None:
            raise OfferNotFoundException()

        if (offer.energy > 0 and energy < 0) or (offer.energy < 0
                                                 and energy > 0):
            raise InvalidBalancingTradeException("BalancingOffer and energy "
                                                 "are not compatible")
        if energy is None:
            energy = offer.energy

        original_offer = offer
        residual_offer = None

        if trade_rate is None:
            trade_rate = offer.energy_rate

        orig_offer_price = self._calculate_original_prices(offer)

        try:
            if time is None:
                time = self.now
            if energy == 0:
                raise InvalidBalancingTradeException("Energy can not be zero.")
            elif abs(energy) < abs(offer.energy):
                # partial energy is requested
                assert trade_rate + FLOATING_POINT_TOLERANCE >= (offer.price /
                                                                 offer.energy)

                original_offer = offer

                accepted_offer, residual_offer = self.split_offer(
                    offer, energy, orig_offer_price)

                fees, trade_price = self.determine_offer_price(
                    energy / offer.energy, energy, trade_rate, trade_bid_info,
                    orig_offer_price)
                offer = accepted_offer
                offer.update_price(trade_price)

            elif abs(energy) > abs(offer.energy):
                raise InvalidBalancingTradeException(
                    "Energy can't be greater than offered energy")
            else:
                # Requested energy is equal to offer's energy - just proceed normally
                fees, trade_price = self._update_offer_fee_and_calculate_final_price(
                    energy, trade_rate, 1, orig_offer_price
                ) if already_tracked is False else energy * trade_rate
                offer.update_price(trade_price)

        except Exception:
            # Exception happened - restore offer
            self.offers[offer.id] = offer
            raise

        # Delete the accepted offer from self.offers:
        self.offers.pop(offer.id, None)

        trade_id, residual_offer = \
            self.bc_interface.handle_blockchain_trade_event(
                offer, buyer, original_offer, residual_offer
            )
        trade = BalancingTrade(trade_id,
                               time,
                               offer,
                               offer.seller,
                               buyer,
                               residual_offer,
                               seller_origin=offer.seller_origin,
                               buyer_origin=buyer_origin,
                               fee_price=fees)
        self.bc_interface.track_trade_event(trade)

        if already_tracked is False:
            self._update_stats_after_trade(trade, offer, buyer)
            log.info(f"[BALANCING_TRADE] [{self.time_slot_str}] {trade}")

        # TODO: Use non-blockchain non-event-driven version for now for both blockchain and
        # normal runs.
        self._notify_listeners(MarketEvent.BALANCING_TRADE, trade=trade)
        return trade
Exemple #8
0
    def accept_offer(self,
                     offer_or_id: Union[str, Offer],
                     buyer: str,
                     *,
                     energy: int = None,
                     time: DateTime = None,
                     already_tracked: bool = False,
                     trade_rate: float = None,
                     trade_bid_info=None,
                     buyer_origin=None) -> Trade:
        if self.readonly:
            raise MarketReadOnlyException()

        if isinstance(offer_or_id, Offer):
            offer_or_id = offer_or_id.id
        offer = self.offers.pop(offer_or_id, None)
        if offer is None:
            raise OfferNotFoundException()

        if energy is None:
            energy = offer.energy

        original_offer = offer
        residual_offer = None

        if trade_rate is None:
            trade_rate = offer.energy_rate

        orig_offer_price = self._calculate_original_prices(offer)

        try:
            if time is None:
                time = self.now

            if energy == 0:
                raise InvalidTrade("Energy can not be zero.")
            elif energy < offer.energy:
                # partial energy is requested

                accepted_offer, residual_offer = self.split_offer(
                    offer, energy, orig_offer_price)

                fee_price, trade_price = self.determine_offer_price(
                    energy_portion=energy / accepted_offer.energy,
                    energy=energy,
                    trade_rate=trade_rate,
                    trade_bid_info=trade_bid_info,
                    orig_offer_price=orig_offer_price)

                offer = accepted_offer
                offer.update_price(trade_price)

            elif energy > offer.energy:
                raise InvalidTrade(
                    "Energy can't be greater than offered energy")
            else:
                # Requested energy is equal to offer's energy - just proceed normally
                fee_price, trade_price = self.determine_offer_price(
                    1, energy, trade_rate, trade_bid_info, orig_offer_price)
                offer.update_price(trade_price)
        except Exception:
            # Exception happened - restore offer
            self.offers[offer.id] = offer
            raise

        trade_id, residual_offer = \
            self.bc_interface.handle_blockchain_trade_event(
                offer, buyer, original_offer, residual_offer
            )

        # Delete the accepted offer from self.offers:
        self.offers.pop(offer.id, None)
        offer_bid_trade_info = self.fee_class.propagate_original_bid_info_on_offer_trade(
            trade_original_info=trade_bid_info)
        trade = Trade(trade_id,
                      time,
                      offer,
                      offer.seller,
                      buyer,
                      residual_offer,
                      offer_bid_trade_info=offer_bid_trade_info,
                      seller_origin=offer.seller_origin,
                      buyer_origin=buyer_origin,
                      fee_price=fee_price)
        self.bc_interface.track_trade_event(trade)

        if already_tracked is False:
            self._update_stats_after_trade(trade, offer, buyer)
            log.info(f"[TRADE] [{self.name}] [{self.time_slot_str}] {trade}")

        # TODO: Use non-blockchain non-event-driven version for now for both blockchain and
        # normal runs.
        self._notify_listeners(MarketEvent.TRADE, trade=trade)
        return trade
Exemple #9
0
    def accept_offer(self,
                     offer_or_id: Union[str, BalancingOffer],
                     buyer: str,
                     *,
                     energy: int = None,
                     time: DateTime = None,
                     already_tracked: bool = False,
                     trade_rate: float = None,
                     trade_bid_info: float = None,
                     buyer_origin=None) -> BalancingTrade:
        if self.readonly:
            raise MarketReadOnlyException()
        if isinstance(offer_or_id, Offer):
            offer_or_id = offer_or_id.id
        offer = self.offers.pop(offer_or_id, None)
        if offer is None:
            raise OfferNotFoundException()
        if (offer.energy > 0 and energy < 0) or (offer.energy < 0
                                                 and energy > 0):
            raise InvalidBalancingTradeException("BalancingOffer and energy "
                                                 "are not compatible")
        if energy is None:
            energy = offer.energy

        original_offer = offer
        residual_offer = None

        if trade_rate is None:
            trade_rate = offer.price / offer.energy

        orig_offer_price = self._calculate_original_prices(offer)

        self._sorted_offers = sorted(self.offers.values(),
                                     key=lambda o: o.price / o.energy)
        try:
            if time is None:
                time = self._now

            energy_portion = energy / offer.energy
            if energy == 0:
                raise InvalidBalancingTradeException("Energy can not be zero.")
            # partial energy is requested
            elif abs(energy) < abs(offer.energy):
                original_offer = offer
                accepted_offer_id = offer.id \
                    if self.area is None or self.area.bc is None \
                    else offer.real_id

                assert trade_rate + FLOATING_POINT_TOLERANCE >= (offer.price /
                                                                 offer.energy)

                final_price = self._update_offer_fee_and_calculate_final_price(
                    energy, trade_rate, energy_portion, orig_offer_price
                ) if already_tracked is False else energy * trade_rate

                accepted_offer = Offer(accepted_offer_id,
                                       abs(final_price),
                                       energy,
                                       offer.seller,
                                       seller_origin=offer.seller_origin)

                residual_price = (1 - energy_portion) * offer.price
                residual_energy = offer.energy - energy
                original_residual_price = \
                    ((offer.energy - energy) / offer.energy) * orig_offer_price

                residual_offer = Offer(
                    str(uuid.uuid4()),
                    abs(residual_price),
                    residual_energy,
                    offer.seller,
                    original_offer_price=original_residual_price,
                    seller_origin=offer.seller_origin)
                self.offers[residual_offer.id] = residual_offer
                log.debug(f"[BALANCING_OFFER][CHANGED][{self.time_slot_str}] "
                          f"{original_offer} -> {residual_offer}")
                offer = accepted_offer

                self.bc_interface.change_offer(offer, original_offer,
                                               residual_offer)
                self._sorted_offers = sorted(self.offers.values(),
                                             key=lambda o: o.price / o.energy)
                self._notify_listeners(MarketEvent.BALANCING_OFFER_CHANGED,
                                       existing_offer=original_offer,
                                       new_offer=residual_offer)
            elif abs(energy) > abs(offer.energy):
                raise InvalidBalancingTradeException(
                    "Energy can't be greater than offered energy")
            else:
                # Requested energy is equal to offer's energy - just proceed normally
                offer.price = self._update_offer_fee_and_calculate_final_price(
                    energy, trade_rate, 1, orig_offer_price
                ) if already_tracked is False else energy * trade_rate

        except Exception:
            # Exception happened - restore offer
            self.offers[offer.id] = offer
            self._sorted_offers = sorted(self.offers.values(),
                                         key=lambda o: o.price / o.energy)
            raise

        trade_id, residual_offer = \
            self.bc_interface.handle_blockchain_trade_event(
                offer, buyer, original_offer, residual_offer
            )
        trade = BalancingTrade(trade_id,
                               time,
                               offer,
                               offer.seller,
                               buyer,
                               residual_offer,
                               seller_origin=offer.seller_origin,
                               buyer_origin=buyer_origin)
        self.bc_interface.track_trade_event(trade)

        if already_tracked is False:
            self._update_stats_after_trade(trade, offer, buyer)
            log.info(f"[BALANCING_TRADE] [{self.time_slot_str}] {trade}")

        # TODO: Use non-blockchain non-event-driven version for now for both blockchain and
        # normal runs.
        self._notify_listeners(MarketEvent.BALANCING_TRADE, trade=trade)
        return trade
Exemple #10
0
 def accept_offer(self,
                  offer_or_id: Union[str, BalancingOffer],
                  buyer: str,
                  *,
                  energy: int = None,
                  time: DateTime = None,
                  price_drop: bool = False) -> BalancingTrade:
     if self.readonly:
         raise MarketReadOnlyException()
     if isinstance(offer_or_id, Offer):
         offer_or_id = offer_or_id.id
     residual_offer = None
     offer = self.offers.pop(offer_or_id, None)
     self._sorted_offers = sorted(self.offers.values(),
                                  key=lambda o: o.price / o.energy)
     if offer is None:
         raise OfferNotFoundException()
     if (offer.energy > 0 and energy < 0) or (offer.energy < 0
                                              and energy > 0):
         raise InvalidBalancingTradeException("BalancingOffer and energy "
                                              "are not compatible")
     try:
         if time is None:
             time = self._now
         if energy is not None:
             # Partial trade
             if energy == 0:
                 raise InvalidBalancingTradeException(
                     "Energy can not be zero.")
             elif abs(energy) < abs(offer.energy):
                 original_offer = offer
                 accepted_offer = Offer(
                     offer.id, abs((offer.price / offer.energy) * energy),
                     energy, offer.seller, offer.market)
                 residual_energy = (offer.energy - energy)
                 residual_offer = Offer(
                     str(uuid.uuid4()),
                     abs((offer.price / offer.energy) * residual_energy),
                     residual_energy, offer.seller, offer.market)
                 self.offers[residual_offer.id] = residual_offer
                 log.info(
                     f"[BALANCING_OFFER][CHANGED][{self.time_slot_str}] "
                     f"{original_offer} -> {residual_offer}")
                 offer = accepted_offer
                 self._sorted_offers = sorted(
                     self.offers.values(), key=lambda o: o.price / o.energy)
                 self._notify_listeners(MarketEvent.BALANCING_OFFER_CHANGED,
                                        existing_offer=original_offer,
                                        new_offer=residual_offer)
             elif abs(energy) > abs(offer.energy):
                 raise InvalidBalancingTradeException(
                     f"Energy {energy} can't be greater than offered energy {offer}"
                 )
             else:
                 # Requested partial is equal to offered energy - just proceed normally
                 pass
     except Exception:
         # Exception happened - restore offer
         self.offers[offer.id] = offer
         self._sorted_offers = sorted(self.offers.values(),
                                      key=lambda o: o.price / o.energy)
         raise
     trade = BalancingTrade(id=str(uuid.uuid4()),
                            time=time,
                            offer=offer,
                            seller=offer.seller,
                            buyer=buyer,
                            residual=residual_offer,
                            price_drop=price_drop)
     self.trades.append(trade)
     self._update_accumulated_trade_price_energy(trade)
     log.warning(f"[BALANCING_TRADE][{self.time_slot_str}] {trade}")
     self.traded_energy[offer.seller] += offer.energy
     self.traded_energy[buyer] -= offer.energy
     self.ious[buyer][offer.seller] += offer.price
     self._update_min_max_avg_trade_prices(offer.price / offer.energy)
     # Recalculate offer min/max price since offer was removed
     self._update_min_max_avg_offer_prices()
     offer._traded(trade, self)
     self._notify_listeners(MarketEvent.BALANCING_TRADE, trade=trade)
     return trade
Exemple #11
0
    def accept_offer(self,
                     offer_or_id: Union[str, Offer],
                     buyer: str,
                     *,
                     energy: int = None,
                     time: DateTime = None,
                     already_tracked: bool = False,
                     trade_rate: float = None,
                     trade_bid_info=None,
                     buyer_origin=None) -> Trade:
        if self.readonly:
            raise MarketReadOnlyException()

        if isinstance(offer_or_id, Offer):
            offer_or_id = offer_or_id.id
        offer = self.offers.pop(offer_or_id, None)
        if offer is None:
            raise OfferNotFoundException()

        if energy is None:
            energy = offer.energy

        original_offer = offer
        residual_offer = None

        if trade_rate is None:
            trade_rate = offer.price / offer.energy

        orig_offer_price = self._calculate_original_prices(offer)

        try:
            if time is None:
                time = self._now

            energy_portion = energy / offer.energy
            if energy == 0:
                raise InvalidTrade("Energy can not be zero.")
            # partial energy is requested
            elif energy < offer.energy:
                original_offer = offer
                accepted_offer_id = offer.id \
                    if self.area is None or self.area.bc is None \
                    else offer.real_id

                assert trade_rate + FLOATING_POINT_TOLERANCE >= (offer.price /
                                                                 offer.energy)

                if ConstSettings.IAASettings.MARKET_TYPE == 1:
                    final_price = self._update_offer_fee_and_calculate_final_price(
                        energy, trade_rate, energy_portion, orig_offer_price
                    ) if already_tracked is False else energy * trade_rate
                else:
                    revenue, fees, trade_rate_incl_fees = \
                        GridFees.calculate_trade_price_and_fees(
                            trade_bid_info, self.transfer_fee_ratio
                        )
                    self.market_fee += fees
                    final_price = energy * trade_rate_incl_fees

                accepted_offer = Offer(accepted_offer_id,
                                       final_price,
                                       energy,
                                       offer.seller,
                                       seller_origin=offer.seller_origin)

                residual_price = (1 - energy_portion) * offer.price
                residual_energy = offer.energy - energy
                original_residual_price = \
                    ((offer.energy - energy) / offer.energy) * orig_offer_price

                residual_offer = Offer(
                    str(uuid.uuid4()),
                    residual_price,
                    residual_energy,
                    offer.seller,
                    original_offer_price=original_residual_price,
                    seller_origin=offer.seller_origin)
                self.offers[residual_offer.id] = residual_offer
                log.debug(f"[OFFER][CHANGED][{self.time_slot_str}] "
                          f"{original_offer} -> {residual_offer}")
                offer = accepted_offer

                self.bc_interface.change_offer(offer, original_offer,
                                               residual_offer)
                self._notify_listeners(MarketEvent.OFFER_CHANGED,
                                       existing_offer=original_offer,
                                       new_offer=residual_offer)
            elif energy > offer.energy:
                raise InvalidTrade(
                    "Energy can't be greater than offered energy")
            else:
                # Requested energy is equal to offer's energy - just proceed normally
                if ConstSettings.IAASettings.MARKET_TYPE == 1:
                    offer.price = self._update_offer_fee_and_calculate_final_price(
                        energy, trade_rate, 1, orig_offer_price
                    ) if already_tracked is False else energy * trade_rate
                else:
                    revenue, fees, trade_price = GridFees.calculate_trade_price_and_fees(
                        trade_bid_info, self.transfer_fee_ratio)
                    self.market_fee += fees
                    offer.price = energy * trade_price

        except Exception:
            # Exception happened - restore offer
            self.offers[offer.id] = offer
            raise

        trade_id, residual_offer = \
            self.bc_interface.handle_blockchain_trade_event(
                offer, buyer, original_offer, residual_offer
            )

        trade = Trade(trade_id,
                      time,
                      offer,
                      offer.seller,
                      buyer,
                      residual_offer,
                      offer_bid_trade_info=GridFees.
                      propagate_original_bid_info_on_offer_trade(
                          trade_bid_info, self.transfer_fee_ratio),
                      seller_origin=offer.seller_origin,
                      buyer_origin=buyer_origin)
        self.bc_interface.track_trade_event(trade)

        if already_tracked is False:
            self._update_stats_after_trade(trade, offer, buyer)
            log.info(f"[TRADE] [{self.time_slot_str}] {trade}")

        # TODO: Use non-blockchain non-event-driven version for now for both blockchain and
        # normal runs.
        self._notify_listeners(MarketEvent.TRADE, trade=trade)
        return trade