def accept_bid(self, bid: Bid, energy: float = None, seller: str = None, buyer: str = None, already_tracked: bool = False, price_drop: bool = True): market_bid = self.bids.pop(bid.id, None) if market_bid is None: raise BidNotFound("During accept bid: " + str(bid)) seller = market_bid.seller if seller is None else seller buyer = market_bid.buyer if buyer is None else buyer energy = market_bid.energy if energy is None else energy if energy <= 0: raise InvalidTrade("Energy cannot be zero.") elif energy > bid.energy: raise InvalidTrade( "Traded energy cannot be more than the bid energy.") elif energy is None or energy <= market_bid.energy: residual = False if energy < market_bid.energy: # Partial bidding residual = True # For the residual bid we use the market rate, in order to not affect # rate increase algorithm. energy_rate = market_bid.price / market_bid.energy residual_energy = market_bid.energy - energy residual_price = residual_energy * energy_rate self.bid(residual_price, residual_energy, buyer, seller, bid.id) # For the accepted bid we use the 'clearing' rate from the bid # input argument. final_price = energy * (bid.price / bid.energy) bid = Bid(bid.id, final_price, energy, buyer, seller, self) trade = Trade(str(uuid.uuid4()), self._now, bid, seller, buyer, residual, price_drop=price_drop, already_tracked=already_tracked) if not already_tracked: self._update_stats_after_trade(trade, bid, bid.buyer, already_tracked) log.warning(f"[TRADE][BID][{self.time_slot_str}] {trade}") self._notify_listeners(MarketEvent.BID_TRADED, bid_trade=trade) if not trade.residual: self._notify_listeners(MarketEvent.BID_DELETED, bid=market_bid) return trade else: raise Exception( "Undefined state or conditions. Should never reach this place." )
def accept_bid(self, bid: Bid, energy: float = None, seller: str = None, buyer: str = None, already_tracked: bool = False, trade_rate: float = None, trade_offer_info=None, seller_origin=None): market_bid = self.bids.pop(bid.id, None) if market_bid is None: raise BidNotFound("During accept bid: " + str(bid)) seller = market_bid.seller if seller is None else seller buyer = market_bid.buyer if buyer is None else buyer energy = market_bid.energy if energy is None else energy orig_price = bid.original_bid_price if bid.original_bid_price is not None else bid.price residual_bid = None if energy <= 0: raise InvalidTrade("Energy cannot be negative or zero.") elif energy > market_bid.energy: raise InvalidTrade("Traded energy cannot be more than the bid energy.") elif energy < market_bid.energy: # partial bid trade accepted_bid, residual_bid = self.split_bid(market_bid, energy, orig_price) bid = accepted_bid # Delete the accepted bid from self.bids: try: self.bids.pop(accepted_bid.id) except KeyError: raise BidNotFound(f"Bid {accepted_bid.id} not found in self.bids ({self.name}).") else: # full bid trade, nothing further to do here pass fee_price, trade_price = self.determine_bid_price(trade_offer_info, energy) bid = bid._replace(price=trade_price) # Do not adapt grid fees when creating the bid_trade_info structure, to mimic # the behavior of the forwarded bids which use the source market fee. updated_bid_trade_info = self.fee_class.propagate_original_offer_info_on_bid_trade( trade_offer_info, ignore_fees=True ) trade = Trade(str(uuid.uuid4()), self.now, bid, seller, buyer, residual_bid, already_tracked=already_tracked, offer_bid_trade_info=updated_bid_trade_info, buyer_origin=bid.buyer_origin, seller_origin=seller_origin, fee_price=fee_price ) if already_tracked is False: self._update_stats_after_trade(trade, bid, bid.buyer, already_tracked) log.info(f"[TRADE][BID] [{self.name}] [{self.time_slot_str}] {trade}") self._notify_listeners(MarketEvent.BID_TRADED, bid_trade=trade) return trade
def handle_blockchain_trade_event(self, offer, buyer, original_offer, residual_offer): trade_id, new_offer_id = trade_offer(self.bc_interface, self.bc_contract, offer.real_id, offer.energy, buyer) if residual_offer is not None: if new_offer_id is None: raise InvalidTrade( "Blockchain and local residual offers are out of sync") residual_offer.id = str(new_offer_id) residual_offer.real_id = new_offer_id return trade_id, residual_offer
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_bid(self, bid: Bid, energy: float = None, seller: str = None, buyer: str = None, already_tracked: bool = False, trade_rate: float = None, trade_offer_info=None, seller_origin=None): market_bid = self.bids.pop(bid.id, None) if market_bid is None: raise BidNotFound("During accept bid: " + str(bid)) seller = market_bid.seller if seller is None else seller buyer = market_bid.buyer if buyer is None else buyer energy = market_bid.energy if energy is None else energy if trade_rate is None: trade_rate = market_bid.price / market_bid.energy assert trade_rate <= (market_bid.price / market_bid.energy) + FLOATING_POINT_TOLERANCE, \ f"trade rate: {trade_rate} market {market_bid.price / market_bid.energy}" orig_price = bid.original_bid_price if bid.original_bid_price is not None else bid.price residual = None if energy <= 0: raise InvalidTrade("Energy cannot be negative or zero.") elif energy > market_bid.energy: raise InvalidTrade("Traded energy cannot be more than the bid energy.") elif energy < market_bid.energy: # Partial bidding energy_portion = energy / market_bid.energy residual_price = (1 - energy_portion) * market_bid.price residual_energy = market_bid.energy - energy # Manually creating the bid in order to not double-charge the fee of the # residual bid changed_bid = Bid( str(uuid.uuid4()), residual_price, residual_energy, buyer, seller, original_bid_price=(1 - energy_portion) * orig_price, buyer_origin=market_bid.buyer_origin ) self.bids[changed_bid.id] = changed_bid self.bid_history.append(changed_bid) self._notify_listeners(MarketEvent.BID_CHANGED, existing_bid=bid, new_bid=changed_bid) residual = changed_bid revenue, fees, final_trade_rate = GridFees.calculate_trade_price_and_fees( trade_offer_info, self.transfer_fee_ratio ) self.market_fee += fees final_price = energy * final_trade_rate bid = Bid(bid.id, final_price, energy, buyer, seller, original_bid_price=energy_portion * orig_price, buyer_origin=bid.buyer_origin) else: revenue, fees, final_trade_rate = GridFees.calculate_trade_price_and_fees( trade_offer_info, self.transfer_fee_ratio ) self.market_fee += fees final_price = energy * final_trade_rate bid = bid._replace(price=final_price) trade = Trade(str(uuid.uuid4()), self._now, bid, seller, buyer, residual, already_tracked=already_tracked, offer_bid_trade_info=GridFees.propagate_original_offer_info_on_bid_trade( trade_offer_info, self.transfer_fee_ratio), buyer_origin=bid.buyer_origin, seller_origin=seller_origin ) if already_tracked is False: self._update_stats_after_trade(trade, bid, bid.buyer, already_tracked) log.info(f"[TRADE][BID] [{self.time_slot_str}] {trade}") final_bid = bid._replace(price=final_price) trade = trade._replace(offer=final_bid) self._notify_listeners(MarketEvent.BID_TRADED, bid_trade=trade) if not trade.residual: self._notify_listeners(MarketEvent.BID_DELETED, bid=market_bid) 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