def bid(self, price, energy, buyer, seller, original_bid_price=None, buyer_origin=None): return Bid(123, price, energy, buyer, seller, original_bid_price, buyer_origin=buyer_origin)
def test_bid_calls_market_method_and_publishes_response(self): payload = { "data": json.dumps({ "buyer": "mykonos", "energy": 12, "price": 32, "transaction_uuid": "trans_id" }) } bid = Bid("b_id", 32, 12, "b_buyer", "b_seller") self.market.bid = MagicMock(return_value=bid) self.subscriber._bid(payload) self.subscriber.market.bid.assert_called_once_with(buyer="mykonos", energy=12, price=32) self.subscriber.redis_db.publish.assert_called_once_with( "id/BID/RESPONSE", json.dumps({ "status": "ready", "bid": bid.to_JSON_string(), "transaction_uuid": "trans_id" }))
def test_matching_list_affects_only_matches_after_start_index(self): matchings = [ BidOfferMatch(offer=Offer('offer_id', pendulum.now(), 1, 1, 'S'), offer_energy=1, bid=Bid('bid_id', pendulum.now(), 1, 1, 'B', 'S'), bid_energy=1), BidOfferMatch(offer=Offer('offer_id2', pendulum.now(), 2, 2, 'S'), offer_energy=2, bid=Bid('bid_id2', pendulum.now(), 2, 2, 'B', 'S'), bid_energy=2), BidOfferMatch(offer=Offer('offer_id', pendulum.now(), 1, 1, 'S'), offer_energy=1, bid=Bid('bid_id', pendulum.now(), 1, 1, 'B', 'S'), bid_energy=1) ] offer_trade = Trade('trade', 1, Offer('offer_id', pendulum.now(), 1, 1, 'S'), 'S', 'B', residual=Offer('residual_offer', pendulum.now(), 0.5, 0.5, 'S')) bid_trade = Trade('bid_trade', 1, Bid('bid_id2', pendulum.now(), 1, 1, 'S', 'B'), 'S', 'B', residual=Bid('residual_bid_2', pendulum.now(), 1, 1, 'S', 'B')) matchings = TwoSidedPayAsClear._replace_offers_bids_with_residual_in_matching_list( matchings, 1, offer_trade, bid_trade) assert len(matchings) == 3 assert matchings[0].offer.id == 'offer_id' assert matchings[1].bid.id == 'residual_bid_2' assert matchings[2].offer.id == 'residual_offer'
def test_iaa_event_trade_bid_updates_forwarded_bids_on_partial( iaa_bid, called, partial): iaa_bid.lower_market.delete_bid = called low_to_high_engine = iaa_bid.engines[0] iaa_bid._get_market_from_market_id = lambda x: low_to_high_engine.markets.target if partial: accepted_bid = Bid(*low_to_high_engine.markets.target._bids[0]) accepted_bid = \ accepted_bid._replace(price=(accepted_bid.energy-0.2) * (accepted_bid.price/accepted_bid.energy), energy=accepted_bid.energy-0.2) partial_bid = Bid('1234', 12, 0.2, 'owner', 'someone_else') low_to_high_engine.event_bid_changed( market_id=low_to_high_engine.markets.target, existing_bid=accepted_bid, new_bid=partial_bid) else: accepted_bid = low_to_high_engine.markets.target._bids[0] partial_bid = False source_bid = list(low_to_high_engine.markets.source.bids.values())[0] target_bid = list(low_to_high_engine.markets.target.bids.values())[0] bidinfo = BidInfo(source_bid=source_bid, target_bid=target_bid) low_to_high_engine.forwarded_bids[source_bid.id] = bidinfo low_to_high_engine.forwarded_bids[target_bid.id] = bidinfo low_to_high_engine.event_bid_traded(bid_trade=Trade('trade_id', pendulum.now( tz=TIME_ZONE), accepted_bid, seller='someone_else', buyer='owner', residual=partial_bid)) assert source_bid.id not in low_to_high_engine.forwarded_bids assert target_bid.id not in low_to_high_engine.forwarded_bids if partial: assert partial_bid.id in low_to_high_engine.forwarded_bids
def bid(self, price, energy, buyer, seller, original_bid_price=None, buyer_origin=None): bid = Bid("bid_id", pendulum.now(), price, energy, buyer, seller, buyer_origin=buyer_origin) return bid
def bid(self, price: float, energy: float, buyer: str, seller: str, bid_id: str = None) -> Bid: if energy <= 0: raise InvalidBid() bid = Bid( str(uuid.uuid4()) if bid_id is None else bid_id, price, energy, buyer, seller, self) self.bids[bid.id] = bid self.bid_history.append(bid) log.info(f"[BID][NEW][{self.time_slot_str}] {bid}") return bid
def test_iaa_double_sided_performs_pay_as_bid_matching(iaa_double_sided_2): low_high_engine = next(filter(lambda e: e.name == "Low -> High", iaa_double_sided_2.engines)) iaa_double_sided_2.lower_market._bids = [Bid('bid_id', 9, 10, 'B', 'S')] matched = list(low_high_engine._perform_pay_as_bid_matching()) assert len(matched) == 0 iaa_double_sided_2.lower_market._bids = [Bid('bid_id', 10, 10, 'B', 'S')] matched = list(low_high_engine._perform_pay_as_bid_matching()) assert len(matched) == 1 bid, offer = matched[0] assert bid == list(iaa_double_sided_2.lower_market.bids.values())[0] assert offer == list(iaa_double_sided_2.lower_market.offers.values())[0] iaa_double_sided_2.lower_market._bids = [Bid('bid_id1', 11, 10, 'B', 'S'), Bid('bid_id2', 9, 10, 'B', 'S'), Bid('bid_id3', 12, 10, 'B', 'S')] matched = list(low_high_engine._perform_pay_as_bid_matching()) assert len(matched) == 1 bid, offer = matched[0] assert bid.id == 'bid_id3' assert bid.price == 12 assert bid.energy == 10 assert offer == list(iaa_double_sided_2.lower_market.offers.values())[0]
def test_double_sided_pay_as_bid_market_match_offer_bids(pab_market): pab_market.calls_offers = [] pab_market.calls_bids = [] offer = Offer('offer1', now(), 2, 2, 'other', 2) pab_market.offers = {"offer1": offer} source_bid = Bid('bid_id3', now(), 12, 10, 'B', 'S', original_bid_price=12) pab_market.bids = {"bid_id": Bid('bid_id', now(), 10, 10, 'B', 'S'), "bid_id1": Bid('bid_id1', now(), 11, 10, 'B', 'S'), "bid_id2": Bid('bid_id2', now(), 9, 10, 'B', 'S'), "bid_id3": source_bid} pab_market.match_offers_bids() assert len(pab_market.calls_offers) == 1 offer = pab_market.calls_offers[0] assert offer.id == offer.id assert offer.energy == offer.energy assert offer.price == offer.price assert len(pab_market.calls_bids) == 1 bid = pab_market.calls_bids[0] assert bid.id == source_bid.id assert bid.energy == source_bid.energy assert bid.price == source_bid.price
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.price, residual_energy, bid.buyer, seller, buyer_origin='res') traded = Bid(bid.id, (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, (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 test_double_sided_performs_pay_as_bid_matching(market): market.offers = {"offer1": Offer('id', now(), 2, 2, 'other', 2)} market.bids = {"bid1": Bid('bid_id', now(), 9, 10, 'B', 'S')} matched = list(market._perform_pay_as_bid_matching()) assert len(matched) == 0 market.bids = {"bid1": Bid('bid_id', now(), 10, 10, 'B', 'S')} matched = list(market._perform_pay_as_bid_matching()) assert len(matched) == 1 bid, offer = matched[0] assert bid == list(market.bids.values())[0] assert offer == list(market.offers.values())[0] market.bids = {"bid1": Bid('bid_id1', now(), 11, 10, 'B', 'S'), "bid2": Bid('bid_id2', now(), 9, 10, 'B', 'S'), "bid3": Bid('bid_id3', now(), 12, 10, 'B', 'S')} matched = list(market._perform_pay_as_bid_matching()) assert len(matched) == 1 bid, offer = matched[0] assert bid.id == 'bid_id3' assert bid.price == 12 assert bid.energy == 10 assert offer == list(market.offers.values())[0]
def bid(self, price: float, energy: float, buyer: str, seller: str, original_bid_price=None, buyer_origin=None) -> Bid: bid = Bid(id='bid_id', price=price, energy=energy, buyer=buyer, seller=seller, original_bid_price=original_bid_price, buyer_origin=buyer_origin) self.bids[bid.id] = bid return bid
def bid(self, price, energy, buyer, seller, original_bid_price=None, buyer_origin=None): self.bid_count += 1 self.forwarded_bid = Bid(self.forwarded_bid_id, price, energy, buyer, seller, original_bid_price=original_bid_price, buyer_origin=buyer_origin) return self.forwarded_bid
def iaa_double_sided(): from d3a_interface.constants_limits import ConstSettings ConstSettings.IAASettings.MARKET_TYPE = 2 lower_market = FakeMarket(offers=[Offer('id', pendulum.now(), 2, 2, 'other', 2)], bids=[Bid('bid_id', pendulum.now(), 10, 10, 'B', 'S', 10)], transfer_fees=TransferFees(grid_fee_percentage=0.01, transfer_fee_const=0)) higher_market = FakeMarket([], [], transfer_fees=TransferFees(grid_fee_percentage=0.01, transfer_fee_const=0)) owner = FakeArea('owner') iaa = TwoSidedPayAsBidAgent(owner=owner, lower_market=lower_market, higher_market=higher_market) iaa.event_tick() iaa.owner.current_tick += 2 iaa.event_tick() yield iaa
def bid(self, price: float, energy: float, buyer: str, buyer_origin: str, bid_id: str = None, original_bid_price=None, adapt_price_with_fees=True, add_to_history=True, buyer_origin_id=None, buyer_id=None, attributes: Dict = None, requirements: List[Dict] = None) -> Bid: if energy <= 0: raise InvalidBid() if original_bid_price is None: original_bid_price = price if adapt_price_with_fees: price = self._update_new_bid_price_with_fee( price, original_bid_price) if price < 0.0: raise MarketException( "Negative price after taxes, bid cannot be posted.") bid = Bid(str(uuid.uuid4()) if bid_id is None else bid_id, self.now, price, energy, buyer, original_bid_price, buyer_origin, buyer_origin_id=buyer_origin_id, buyer_id=buyer_id, attributes=attributes, requirements=requirements) self.bids[bid.id] = bid if add_to_history is True: self.bid_history.append(bid) log.debug(f"[BID][NEW][{self.time_slot_str}] {bid}") return bid
def bid(self, price: float, energy: float, buyer: str, seller: str, buyer_origin, bid_id: str = None, original_bid_price=None, adapt_price_with_fees=True, add_to_history=True) -> Bid: if energy <= 0: raise InvalidBid() if original_bid_price is None: original_bid_price = price if adapt_price_with_fees: price = self._update_new_bid_price_with_fee(price, original_bid_price) bid = Bid(str(uuid.uuid4()) if bid_id is None else bid_id, self.now, price, energy, buyer, seller, original_bid_price, buyer_origin) self.bids[bid.id] = bid if add_to_history is True: self.bid_history.append(bid) log.debug(f"[BID][NEW][{self.time_slot_str}] {bid}") return bid
def test_create_bid_offer_matchings_can_match_with_only_one_bid(self): bid_list = [Bid('bid_id', pendulum.now(), 9, 90123456789, 'B', 'S')] offer_list = [ Offer('offer_id', pendulum.now(), 1, 1, 'S'), Offer('offer_id1', pendulum.now(), 2, 2, 'S'), Offer('offer_id2', pendulum.now(), 3, 3, 'S'), Offer('offer_id3', pendulum.now(), 4, 4, 'S'), Offer('offer_id4', pendulum.now(), 5, 5, 'S') ] matchings = TwoSidedPayAsClear._create_bid_offer_matchings( 15, offer_list, bid_list) assert len(matchings) == 5 self.validate_matching(matchings[0], 1, 'offer_id', 'bid_id') self.validate_matching(matchings[1], 2, 'offer_id1', 'bid_id') self.validate_matching(matchings[2], 3, 'offer_id2', 'bid_id') self.validate_matching(matchings[3], 4, 'offer_id3', 'bid_id') self.validate_matching(matchings[4], 5, 'offer_id4', 'bid_id')
def bid(self, price, energy, buyer, original_bid_price=None, buyer_origin=None, buyer_origin_id=None, buyer_id=None, attributes=None, requirements=None): return Bid(123, pendulum.now(), price, energy, buyer, original_bid_price, buyer_origin=buyer_origin, buyer_origin_id=buyer_origin_id, buyer_id=buyer_id, attributes=attributes, requirements=requirements)
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 bid(self, price: float, energy: float, buyer: str, original_bid_price=None, buyer_origin=None, buyer_origin_id=None, buyer_id=None, attributes=None, requirements=None) -> Bid: bid = Bid(id="bid_id", time=now(), price=price, energy=energy, buyer=buyer, original_bid_price=original_bid_price, buyer_origin=buyer_origin, buyer_origin_id=buyer_origin_id, buyer_id=buyer_id, attributes=attributes, requirements=requirements) self.bids[bid.id] = bid return bid
def test_double_sided_market_performs_pay_as_clear_matching(pac_market, offer, bid, mcp_rate, mcp_energy, algorithm): ConstSettings.IAASettings.PAY_AS_CLEAR_AGGREGATION_ALGORITHM = algorithm pac_market.offers = {"offer1": Offer('id1', now(), offer[0], 1, 'other'), "offer2": Offer('id2', now(), offer[1], 1, 'other'), "offer3": Offer('id3', now(), offer[2], 1, 'other'), "offer4": Offer('id4', now(), offer[3], 1, 'other'), "offer5": Offer('id5', now(), offer[4], 1, 'other'), "offer6": Offer('id6', now(), offer[5], 1, 'other'), "offer7": Offer('id7', now(), offer[6], 1, 'other')} pac_market.bids = {"bid1": Bid('bid_id1', now(), bid[0], 1, 'B', 'S'), "bid2": Bid('bid_id2', now(), bid[1], 1, 'B', 'S'), "bid3": Bid('bid_id3', now(), bid[2], 1, 'B', 'S'), "bid4": Bid('bid_id4', now(), bid[3], 1, 'B', 'S'), "bid5": Bid('bid_id5', now(), bid[4], 1, 'B', 'S'), "bid6": Bid('bid_id6', now(), bid[5], 1, 'B', 'S'), "bid7": Bid('bid_id7', now(), bid[6], 1, 'B', 'S')} matched_rate, matched_energy = pac_market._perform_pay_as_clear_matching() assert matched_rate == mcp_rate assert matched_energy == mcp_energy
def test_iaa_double_sided_performs_pay_as_clear_matching(iaa_double_sided_pay_as_clear, offer, bid, MCP): low_high_engine = \ next(filter(lambda e: e.name == "Low -> High", iaa_double_sided_pay_as_clear.engines)) iaa_double_sided_pay_as_clear.lower_market.sorted_offers = \ [Offer('id1', offer[0], 1, 'other'), Offer('id2', offer[1], 1, 'other'), Offer('id3', offer[2], 1, 'other'), Offer('id4', offer[3], 1, 'other'), Offer('id5', offer[4], 1, 'other'), Offer('id6', offer[5], 1, 'other'), Offer('id7', offer[6], 1, 'other')] iaa_double_sided_pay_as_clear.lower_market._bids = \ [Bid('bid_id1', bid[0], 1, 'B', 'S'), Bid('bid_id2', bid[1], 1, 'B', 'S'), Bid('bid_id3', bid[2], 1, 'B', 'S'), Bid('bid_id4', bid[3], 1, 'B', 'S'), Bid('bid_id5', bid[4], 1, 'B', 'S'), Bid('bid_id6', bid[5], 1, 'B', 'S'), Bid('bid_id7', bid[6], 1, 'B', 'S')] matched = low_high_engine._perform_pay_as_clear_matching()[0] assert matched == MCP
def bid(self, price: float, energy: float, buyer: str, seller: str, bid_id: str=None) -> Bid: bid = Bid(id='bid_id', price=price, energy=energy, buyer=buyer, seller=seller) self.bids[bid.id] = bid return bid
def test_device_operating_hours_deduction_with_partial_trade( load_hours_strategy_test5, market_test2): market_test2.most_affordable_energy = 0.1 load_hours_strategy_test5.event_activate() # load_hours_strategy_test5.area.past_markets = {TIME: market_test2} load_hours_strategy_test5.event_market_cycle() load_hours_strategy_test5.event_tick() assert round(((float(load_hours_strategy_test5.accept_offer.call_args[0][1].energy) * 1000 / load_hours_strategy_test5.energy_per_slot_Wh) * (load_hours_strategy_test5.area.config.slot_length / duration(hours=1))), 2) == \ round(((0.1/0.155) * 0.25), 2) @pytest.mark.parametrize("partial", [None, Bid('test_id', now(), 123, 321, 'A')]) def test_event_bid_traded_removes_bid_for_partial_and_non_trade( load_hours_strategy_test5, called, partial): ConstSettings.IAASettings.MARKET_TYPE = 2 trade_market = load_hours_strategy_test5.area.next_market load_hours_strategy_test5.remove_bid_from_pending = called load_hours_strategy_test5.event_activate() load_hours_strategy_test5.area.markets = {TIME: trade_market} load_hours_strategy_test5.event_market_cycle() # Get the bid that was posted on event_market_cycle bid = list(load_hours_strategy_test5._bids.values())[0][0] # Increase energy requirement to cover the energy from the bid load_hours_strategy_test5.state._energy_requirement_Wh[TIME] = 1000 trade = Trade('idt',
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 test_offer_id_stringified(): bid = Bid(object(), pendulum.now(), 10, 20, 'A', 'B') assert isinstance(bid.id, str) assert "<object object at" in bid.id
def test_device_operating_hours_deduction_with_partial_trade( load_hours_strategy_test5, market_test2): market_test2.most_affordable_energy = 0.1 load_hours_strategy_test5.event_activate() # load_hours_strategy_test5.area.past_markets = {TIME: market_test2} load_hours_strategy_test5.event_market_cycle() load_hours_strategy_test5.event_tick() assert round(((float(load_hours_strategy_test5.accept_offer.call_args[0][1].energy) * 1000 / load_hours_strategy_test5.energy_per_slot_Wh) * (load_hours_strategy_test5.area.config.slot_length / duration(hours=1))), 2) == \ round(((0.1/0.155) * 0.25), 2) @pytest.mark.parametrize( "partial", [None, Bid('test_id', now(), 123, 321, 'A', 'B')]) def test_event_bid_traded_removes_bid_for_partial_and_non_trade( load_hours_strategy_test5, called, partial): ConstSettings.IAASettings.MARKET_TYPE = 2 trade_market = load_hours_strategy_test5.area.next_market load_hours_strategy_test5.remove_bid_from_pending = called load_hours_strategy_test5.event_activate() load_hours_strategy_test5.area.markets = {TIME: trade_market} load_hours_strategy_test5.event_market_cycle() # Get the bid that was posted on event_market_cycle bid = list(load_hours_strategy_test5._bids.values())[0][0] # Increase energy requirement to cover the energy from the bid load_hours_strategy_test5.energy_requirement_Wh[TIME] = 1000 trade = Trade('idt',
def test_device_operating_hours_deduction_with_partial_trade( load_hours_strategy_test5, market_test2): market_test2.most_affordable_energy = 0.1 load_hours_strategy_test5.event_activate() # load_hours_strategy_test5.area.past_markets = {TIME: market_test2} load_hours_strategy_test5.event_market_cycle() load_hours_strategy_test5.event_tick() assert round(((float(load_hours_strategy_test5.accept_offer.call_args[0][1].energy) * 1000 / load_hours_strategy_test5.energy_per_slot_Wh) * (load_hours_strategy_test5.area.config.slot_length / duration(hours=1))), 2) == \ round(((0.1/0.155) * 0.25), 2) @pytest.mark.parametrize("partial", [None, Bid('test_id', 123, 321, 'A', 'B')]) def test_event_bid_traded_removes_bid_for_partial_and_non_trade( load_hours_strategy_test5, called, partial): ConstSettings.IAASettings.MARKET_TYPE = 2 trade_market = load_hours_strategy_test5.area.next_market load_hours_strategy_test5.remove_bid_from_pending = called load_hours_strategy_test5.event_activate() load_hours_strategy_test5.area.markets = {TIME: trade_market} load_hours_strategy_test5.event_market_cycle() load_hours_strategy_test5.event_tick() # Get the bid that was posted on event_market_cycle bid = list(load_hours_strategy_test5._bids.values())[0][0] # Increase energy requirement to cover the energy from the bid load_hours_strategy_test5.energy_requirement_Wh[TIME] = 1000
def bid(self, price, energy, buyer, seller): return Bid(123, price, energy, buyer, seller, self)
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 bid(self, price, energy, buyer, seller): self.bid_count += 1 self.forwarded_bid = Bid(self.forwarded_bid_id, price, energy, buyer, seller, market=self) return self.forwarded_bid