Exemple #1
0
 def _create_fee_handler(self, grid_fee_type, grid_fees):
     if not grid_fees:
         grid_fees = GridFee(grid_fee_percentage=0.0, grid_fee_const=0.0)
     if grid_fee_type == 1:
         if grid_fees.grid_fee_const is None or \
                 grid_fees.grid_fee_const <= 0.0:
             self.fee_class = ConstantGridFees(0.0)
         else:
             self.fee_class = ConstantGridFees(grid_fees.grid_fee_const)
         self.const_fee_rate = self.fee_class.grid_fee_rate
     else:
         if grid_fees.grid_fee_percentage is None or \
                 grid_fees.grid_fee_percentage <= 0.0:
             self.fee_class = GridFees(0.0)
         else:
             self.fee_class = GridFees(grid_fees.grid_fee_percentage / 100)
Exemple #2
0
    def event_bid_traded(self, *, bid_trade):
        bid_info = self.forwarded_bids.get(bid_trade.offer.id)
        if not bid_info:
            return

        # Bid was traded in target market, buy in source
        if bid_trade.offer.id == bid_info.target_bid.id:
            market_bid = self.markets.source.bids[bid_info.source_bid.id]
            assert bid_trade.offer.energy <= market_bid.energy, \
                f"Traded bid on target market has more energy than the market bid."

            source_rate = bid_info.source_bid.price / bid_info.source_bid.energy
            target_rate = bid_info.target_bid.price / bid_info.target_bid.energy
            assert source_rate >= target_rate, \
                f"bid: source_rate ({source_rate}) is not lower than target_rate ({target_rate})"

            trade_rate = (bid_trade.offer.price/bid_trade.offer.energy)

            if bid_trade.offer_bid_trade_info is not None:
                # Adapt trade_offer_info received by the trade to include source market grid fees,
                # which was skipped when accepting the bid during the trade operation.
                updated_trade_offer_info = GridFees.propagate_original_offer_info_on_bid_trade(
                    [None, None, *bid_trade.offer_bid_trade_info],
                    self.markets.source.transfer_fee_ratio
                )
            else:
                updated_trade_offer_info = bid_trade.offer_bid_trade_info

            source_trade = self.markets.source.accept_bid(
                market_bid,
                energy=bid_trade.offer.energy,
                seller=self.owner.name,
                already_tracked=False,
                trade_rate=trade_rate,
                trade_offer_info=GridFees.update_forwarded_bid_trade_original_info(
                    updated_trade_offer_info, market_bid
                ), seller_origin=bid_trade.seller_origin
            )

            self.after_successful_trade_event(source_trade, bid_info)
        # Bid was traded in the source market by someone else
        elif bid_trade.offer.id == bid_info.source_bid.id:
            self.delete_forwarded_bids(bid_info)
        else:
            raise Exception(f"Invalid bid state for IAA {self.owner.name}: "
                            f"traded bid {bid_trade} was not in offered bids tuple {bid_info}")
Exemple #3
0
 def _create_fee_handler(self, grid_fee_type, transfer_fees):
     if not transfer_fees:
         transfer_fees = TransferFees(grid_fee_percentage=0.0,
                                      transfer_fee_const=0.0)
     if grid_fee_type == 1:
         if transfer_fees.transfer_fee_const is None or \
                 transfer_fees.transfer_fee_const <= 0.0:
             self.fee_class = ConstantGridFees(0.0)
         else:
             self.fee_class = ConstantGridFees(
                 transfer_fees.transfer_fee_const)
     else:
         if transfer_fees.grid_fee_percentage is None or \
                 transfer_fees.grid_fee_percentage <= 0.0:
             self.fee_class = GridFees(0.0)
         else:
             self.fee_class = GridFees(transfer_fees.grid_fee_percentage /
                                       100)
 def __init__(self, offers, bids=[], m_id=123, transfer_fees=transfer_fees, name=None):
     self.name = name
     self.id = m_id
     self.offers = {o.id: o for o in offers}
     self._bids = bids
     self.bids = {bid.id: bid for bid in self._bids}
     self.offer_call_count = 0
     self.bid_call_count = 0
     self.forwarded_offer_id = 'fwd'
     self.forwarded_bid_id = 'fwd_bid_id'
     self.calls_energy = []
     self.calls_energy_bids = []
     self.calls_offers = []
     self.calls_bids = []
     self.calls_bids_price = []
     self.time_slot = pendulum.now(tz=TIME_ZONE)
     self.time_slot_str = self.time_slot.format(TIME_FORMAT)
     self.state = MarketClearingState()
     self.fee_class = GridFees(transfer_fees.grid_fee_percentage)
Exemple #5
0
    def event_trade(self, *, trade):
        offer_info = self.forwarded_offers.get(trade.offer.id)
        if not offer_info:
            # Trade doesn't concern us
            return

        if trade.offer.id == offer_info.target_offer.id:
            # Offer was accepted in target market - buy in source
            source_rate = offer_info.source_offer.price / offer_info.source_offer.energy
            target_rate = offer_info.target_offer.price / offer_info.target_offer.energy
            assert abs(source_rate) <= abs(target_rate) + FLOATING_POINT_TOLERANCE, \
                f"offer: source_rate ({source_rate}) is not lower than target_rate ({target_rate})"

            try:
                trade_offer_rate = trade.offer.price / trade.offer.energy
                updated_trade_bid_info = GridFees.update_forwarded_offer_trade_original_info(
                    trade.offer_bid_trade_info, offer_info.source_offer)
                trade_source = self.owner.accept_offer(
                    market_or_id=self.markets.source,
                    offer=offer_info.source_offer,
                    energy=trade.offer.energy,
                    buyer=self.owner.name,
                    trade_rate=trade_offer_rate,
                    trade_bid_info=updated_trade_bid_info,
                    buyer_origin=trade.buyer_origin)

            except OfferNotFoundException:
                raise OfferNotFoundException()
            self.owner.log.debug(
                f"[{self.markets.source.time_slot_str}] Offer accepted {trade_source}"
            )

            self._delete_forwarded_offer_entries(offer_info.source_offer)
            self.offer_age.pop(offer_info.source_offer.id, None)

        elif trade.offer.id == offer_info.source_offer.id:
            # Offer was bought in source market by another party
            try:
                self.owner.delete_offer(self.markets.target,
                                        offer_info.target_offer)
            except OfferNotFoundException:
                pass
            except MarketException as ex:
                self.owner.log.error(
                    "Error deleting InterAreaAgent offer: {}".format(ex))

            self._delete_forwarded_offer_entries(offer_info.source_offer)
            self.offer_age.pop(offer_info.source_offer.id, None)
        else:
            raise RuntimeError("Unknown state. Can't happen")

        assert offer_info.source_offer.id not in self.forwarded_offers
        assert offer_info.target_offer.id not in self.forwarded_offers
 def _update_new_offer_price_with_fee(self, offer_price, original_offer_price, energy):
     """
     Override one sided market private method to abstract away the grid fee calculation
     when placing an offer to a market.
     :param offer_price: Price of the offer coming from the source market, in cents
     :param original_offer_price: Price of the original offer from the device
     :param energy: Not required here, added to comply with the one-sided market implementation
     :return: Updated price for the forwarded offer on this market
     """
     return GridFees.update_incoming_offer_with_fee(
         offer_price, original_offer_price, self.transfer_fee_ratio
     )
Exemple #7
0
 def determine_offer_price(self, energy_portion, energy, trade_rate,
                           trade_bid_info, orig_offer_price, original_offer,
                           offer):
     if ConstSettings.IAASettings.MARKET_TYPE == 1:
         return self._update_offer_fee_and_calculate_final_price(
             energy, trade_rate, energy_portion, orig_offer_price)
     else:
         revenue, grid_fee_rate, trade_rate_incl_fees = \
             GridFees.calculate_trade_price_and_fees(
                 trade_bid_info, self.transfer_fee_ratio
             )
         grid_fee_price = grid_fee_rate * energy
         return grid_fee_price, energy * (trade_rate_incl_fees - grid_fee_rate) \
             if original_offer.seller_origin == offer.seller else \
             energy * trade_rate_incl_fees
Exemple #8
0
    def match_offers_bids(self):
        if not (self.current_tick + 1) % int(self.mcp_update_point) == 0:
            return

        clearing = self._perform_pay_as_clear_matching()

        if clearing is None:
            return

        clearing_rate, clearing_energy = clearing
        if clearing_energy > 0:
            log.info(f"Market Clearing Rate: {clearing_rate} "
                     f"||| Clearing Energy: {clearing_energy} "
                     f"||| Clearing Market {self.name}")
            self.state.clearing[self.now] = (clearing_rate, clearing_energy)

        matchings = self._create_bid_offer_matchings(clearing_energy,
                                                     self.sorted_offers,
                                                     self.sorted_bids)

        for index, match in enumerate(matchings):
            offer = match.offer
            bid = match.bid

            assert math.isclose(match.offer_energy, match.bid_energy)

            selected_energy = match.offer_energy
            original_bid_rate = bid.original_bid_price / bid.energy
            propagated_bid_rate = bid.price / bid.energy
            offer_original_rate = offer.original_offer_price / offer.energy
            offer_propagated_rate = offer.price / offer.energy

            trade_rate_original = GridFees.calculate_original_trade_rate_from_clearing_rate(
                original_bid_rate, propagated_bid_rate, clearing_rate)

            trade_bid_info = TradeBidInfo(
                original_bid_rate=original_bid_rate,
                propagated_bid_rate=propagated_bid_rate,
                original_offer_rate=offer_original_rate,
                propagated_offer_rate=offer_propagated_rate,
                trade_rate=trade_rate_original)

            bid_trade, trade = self.accept_bid_offer_pair(
                bid, offer, clearing_rate, trade_bid_info, selected_energy)

            if trade.residual is not None or bid_trade.residual is not None:
                matchings = self._replace_offers_bids_with_residual_in_matching_list(
                    matchings, index + 1, trade, bid_trade)
Exemple #9
0
    def _forward_bid(self, bid):
        if bid.buyer == self.markets.target.name and \
           bid.seller == self.markets.source.name:
            return
        if self.owner.name == self.markets.target.name:
            return

        forwarded_bid = self.markets.target.bid(
            price=GridFees.update_forwarded_bid_with_fee(
                bid.price, bid.original_bid_price,
                self.markets.source.transfer_fee_ratio),
            energy=bid.energy,
            buyer=self.owner.name,
            seller=self.markets.target.name,
            original_bid_price=bid.original_bid_price,
            buyer_origin=bid.buyer_origin)

        self._add_to_forward_bids(bid, forwarded_bid)
        self.owner.log.trace(f"Forwarding bid {bid} to {forwarded_bid}")
        return forwarded_bid
Exemple #10
0
    def _forward_bid(self, bid):
        if bid.buyer == self.markets.target.name and \
           bid.seller == self.markets.source.name:
            return
        if self.owner.name == self.markets.target.name:
            return

        forwarded_bid = self.markets.target.bid(
            GridFees.update_forwarded_bid_with_fee(
                bid.price, bid.original_bid_price, self.markets.source.transfer_fee_ratio),
            bid.energy,
            self.owner.name,
            self.markets.target.name,
            original_bid_price=bid.original_bid_price,
            buyer_origin=bid.buyer_origin
        )
        bid_coupling = BidInfo(bid, forwarded_bid)
        self.forwarded_bids[forwarded_bid.id] = bid_coupling
        self.forwarded_bids[bid.id] = bid_coupling
        self.owner.log.trace(f"Forwarding bid {bid} to {forwarded_bid}")
        return forwarded_bid
Exemple #11
0
    def _forward_offer(self, offer, offer_id):
        if offer.price == 0:
            self.owner.log.debug("Offer is not forwarded because price=0")
            return

        forwarded_offer = self.markets.target.offer(
            GridFees.update_forwarded_offer_with_fee(
                offer.price, offer.original_offer_price,
                self.markets.target.transfer_fee_ratio),
            offer.energy,
            self.owner.name,
            offer.original_offer_price,
            dispatch_event=False,
            seller_origin=offer.seller_origin)
        offer_info = OfferInfo(deepcopy(offer), deepcopy(forwarded_offer))
        self.forwarded_offers[forwarded_offer.id] = offer_info
        self.forwarded_offers[offer_id] = offer_info
        self.owner.log.trace(f"Forwarding offer {offer} to {forwarded_offer}")
        # TODO: Ugly solution, required in order to decouple offer placement from
        # new offer event triggering
        self.markets.target.dispatch_market_offer_event(offer)
        return forwarded_offer
Exemple #12
0
    def _offer_in_market(self, offer):

        kwargs = {
            "price":
            GridFees.update_forwarded_offer_with_fee(
                offer.price, offer.original_offer_price,
                self.markets.target.transfer_fee_ratio),
            "energy":
            offer.energy,
            "seller":
            self.owner.name,
            "original_offer_price":
            offer.original_offer_price,
            "dispatch_event":
            False,
            "seller_origin":
            offer.seller_origin
        }

        if ConstSettings.GeneralSettings.EVENT_DISPATCHING_VIA_REDIS:
            return self.owner.offer(market_id=self.markets.target,
                                    offer_args=kwargs)
        else:
            return self.markets.target.offer(**kwargs)
    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
Exemple #14
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
Exemple #15
0
 def _update_new_bid_price_with_fee(self, bid_price, original_bid_price):
     return GridFees.update_incoming_bid_with_fee(bid_price,
                                                  original_bid_price,
                                                  self.transfer_fee_ratio)
Exemple #16
0
    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_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 = GridFees.propagate_original_offer_info_on_bid_trade(
            trade_offer_info, 0.0)

        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.time_slot_str}] {trade}")

        self._notify_listeners(MarketEvent.BID_TRADED, bid_trade=trade)
        return trade
Exemple #17
0
 def determine_bid_price(self, trade_offer_info, energy):
     revenue, grid_fee_rate, final_trade_rate = GridFees.calculate_trade_price_and_fees(
         trade_offer_info, self.transfer_fee_ratio)
     return grid_fee_rate * energy, energy * final_trade_rate
class FakeMarket:
    def __init__(self, offers, bids=[], m_id=123, transfer_fees=transfer_fees, name=None):
        self.name = name
        self.id = m_id
        self.offers = {o.id: o for o in offers}
        self._bids = bids
        self.bids = {bid.id: bid for bid in self._bids}
        self.offer_call_count = 0
        self.bid_call_count = 0
        self.forwarded_offer_id = 'fwd'
        self.forwarded_bid_id = 'fwd_bid_id'
        self.calls_energy = []
        self.calls_energy_bids = []
        self.calls_offers = []
        self.calls_bids = []
        self.calls_bids_price = []
        self.time_slot = pendulum.now(tz=TIME_ZONE)
        self.time_slot_str = self.time_slot.format(TIME_FORMAT)
        self.state = MarketClearingState()
        self.fee_class = GridFees(transfer_fees.grid_fee_percentage)

    @property
    def sorted_offers(self):
        return list(sorted(self.offers.values(), key=lambda b: b.price / b.energy))

    def get_bids(self):
        return self.bids

    def set_time_slot(self, timeslot):
        self.time_slot = timeslot

    def accept_offer(self, offer_or_id, buyer, *, energy=None, time=None, already_tracked=False,
                     trade_rate: float = None, trade_bid_info=None, buyer_origin=None):
        offer = offer_or_id
        self.calls_energy.append(energy)
        self.calls_offers.append(offer)
        if energy < offer.energy:
            residual_energy = offer.energy - energy
            residual = Offer('res', offer.time, offer.price, residual_energy, offer.seller,
                             seller_origin='res')
            traded = Offer(offer.id, offer.time, offer.price, energy,
                           offer.seller, seller_origin='res')
            return Trade('trade_id', time, traded, traded.seller, buyer, residual,
                         seller_origin=offer.seller_origin, buyer_origin=buyer_origin)
        else:
            return Trade('trade_id', time, offer, offer.seller, buyer,
                         seller_origin=offer.seller_origin, buyer_origin=buyer_origin)

    def accept_bid(self, bid, energy, seller, buyer=None, *, time=None, trade_rate: float = None,
                   trade_offer_info=None, already_tracked=False, seller_origin=None):
        self.calls_energy_bids.append(energy)
        self.calls_bids.append(bid)
        self.calls_bids_price.append(bid.price)
        if trade_rate is None:
            trade_rate = bid.price / bid.energy
        else:
            assert trade_rate <= (bid.price / bid.energy)

        market_bid = [b for b in self._bids if b.id == bid.id][0]
        if energy < market_bid.energy:
            residual_energy = bid.energy - energy
            residual = Bid('res', bid.time, bid.price, residual_energy, bid.buyer, seller,
                           buyer_origin='res')
            traded = Bid(bid.id, bid.time, (trade_rate * energy), energy, bid.buyer, seller,
                         buyer_origin='res')
            return Trade('trade_id', time, traded, traded.seller, bid.buyer, residual,
                         buyer_origin=bid.buyer_origin, seller_origin=seller_origin)
        else:
            traded = Bid(bid.id, bid.time, (trade_rate * energy), energy, bid.buyer, seller,
                         buyer_origin=bid.id)
            return Trade('trade_id', time, traded, traded.seller, bid.buyer,
                         buyer_origin=bid.buyer_origin, seller_origin=seller_origin)

    def delete_offer(self, *args):
        pass

    def delete_bid(self, *args):
        pass

    def _update_new_offer_price_with_fee(self, offer_price, original_offer_price, energy):
        return offer_price + self.fee_class.grid_fee_rate * original_offer_price

    def _update_new_bid_price_with_fee(self, bid_price, original_bid_price):
        return self.fee_class.update_incoming_bid_with_fee(
            bid_price, original_bid_price)

    def offer(self, price: float, energy: float, seller: str, offer_id=None,
              original_offer_price=None, dispatch_event=True, seller_origin=None,
              adapt_price_with_fees=True) -> Offer:
        self.offer_call_count += 1

        if original_offer_price is None:
            original_offer_price = price
        if offer_id is None:
            offer_id = "uuid"
        if adapt_price_with_fees:
            price = self._update_new_offer_price_with_fee(price, original_offer_price, energy)
        offer = Offer(offer_id, pendulum.now(), price, energy, seller, original_offer_price,
                      seller_origin=seller_origin)
        self.offers[offer.id] = deepcopy(offer)
        self.forwarded_offer = deepcopy(offer)

        return offer

    def dispatch_market_offer_event(self, offer):
        pass

    def bid(self, price: float, energy: float, buyer: str, seller: str,
            bid_id: str = None, original_bid_price=None, buyer_origin=None,
            adapt_price_with_fees=True):
        self.bid_call_count += 1

        if original_bid_price is None:
            original_bid_price = price

        if bid_id is None:
            bid_id = "uuid"

        if adapt_price_with_fees:
            price = self._update_new_bid_price_with_fee(price, original_bid_price)

        bid = Bid(bid_id, pendulum.now(), price, energy, buyer, seller,
                  original_bid_price=original_bid_price,
                  buyer_origin=buyer_origin)
        self._bids.append(bid)
        self.forwarded_bid = bid

        return bid

    def split_offer(self, original_offer, energy, orig_offer_price):
        self.offers.pop(original_offer.id, None)
        # same offer id is used for the new accepted_offer
        accepted_offer = self.offer(offer_id=original_offer.id,
                                    price=original_offer.price * (energy / original_offer.energy),
                                    energy=energy,
                                    seller=original_offer.seller,
                                    dispatch_event=False,
                                    seller_origin=original_offer.seller_origin)

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

        residual_offer = self.offer(price=residual_price,
                                    energy=residual_energy,
                                    seller=original_offer.seller,
                                    original_offer_price=original_residual_price,
                                    dispatch_event=False,
                                    seller_origin=original_offer.seller_origin,
                                    adapt_price_with_fees=False)

        return accepted_offer, residual_offer

    def split_bid(self, original_bid, energy, orig_bid_price):
        self.offers.pop(original_bid.id, None)
        # same offer id is used for the new accepted_offer
        accepted_bid = self.bid(bid_id=original_bid.id,
                                buyer=original_bid.buyer,
                                price=original_bid.price * (energy / original_bid.energy),
                                energy=energy,
                                seller=original_bid.seller,
                                buyer_origin=original_bid.buyer_origin)
        residual_price = (1 - energy / original_bid.energy) * original_bid.price
        residual_energy = original_bid.energy - energy
        original_residual_price = \
            ((original_bid.energy - energy) / original_bid.energy) * orig_bid_price

        residual_bid = self.bid(price=residual_price,
                                buyer=original_bid.buyer,
                                energy=residual_energy,
                                seller=original_bid.seller,
                                original_bid_price=original_residual_price,
                                buyer_origin=original_bid.buyer_origin,
                                adapt_price_with_fees=False)
        return accepted_bid, residual_bid
Exemple #19
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

            if energy == 0:
                raise InvalidTrade("Energy can not be zero.")
            elif energy < offer.energy:
                # partial energy is requested
                assert trade_rate + FLOATING_POINT_TOLERANCE >= (offer.price /
                                                                 offer.energy)

                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,
                    original_offer=original_offer,
                    offer=accepted_offer)

                offer = accepted_offer
                offer.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, offer.price = self.determine_offer_price(
                    1, energy, trade_rate, trade_bid_info, orig_offer_price,
                    original_offer, offer)
        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 = GridFees.propagate_original_bid_info_on_offer_trade(
            trade_original_info=trade_bid_info,
            tax_ratio=self.transfer_fee_ratio)
        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.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