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
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
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
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)
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)
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
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
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
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
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
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