def test_iaa_event_trade_buys_accepted_bid(iaa_double_sided): iaa_double_sided.higher_market.forwarded_bid = \ iaa_double_sided.higher_market.forwarded_bid iaa_double_sided.event_bid_traded( bid_trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), iaa_double_sided.higher_market.forwarded_bid, 'owner', 'someone_else'), market_id=iaa_double_sided.higher_market.id) assert len(iaa_double_sided.lower_market.calls_energy_bids) == 1 expected_price = 10 * (1 - iaa_double_sided.lower_market.transfer_fee_ratio) assert iaa_double_sided.higher_market.forwarded_bid.price == expected_price assert iaa_double_sided.lower_market.calls_bids_price[-1] == 10.0
def test_event_trade(area_test2, commercial_test2): commercial_test2.event_activate() commercial_test2.event_market_cycle() traded_offer = Offer(id='id', price=20, energy=1, seller='FakeArea',) commercial_test2.event_trade(market_id=area_test2.test_market.id, trade=Trade(id='id', time='time', offer=traded_offer, seller='FakeArea', buyer='buyer' ) ) assert len(area_test2.test_market.created_offers) == 1 assert area_test2.test_market.created_offers[-1].energy == sys.maxsize
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_buys_accepted_bid(iaa_double_sided): iaa_double_sided.higher_market.forwarded_bid = \ iaa_double_sided.higher_market.forwarded_bid._replace(price=20) iaa_double_sided.event_bid_traded( bid_trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), iaa_double_sided.higher_market.forwarded_bid, 'owner', 'someone_else', price_drop=False), market_id=iaa_double_sided.higher_market.id) assert len(iaa_double_sided.lower_market.calls_energy_bids) == 1 assert iaa_double_sided.higher_market.forwarded_bid.price == 20.0 assert iaa_double_sided.lower_market.calls_bids_price[-1] == 10.0
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 __init__(self, count): self.count = count self.id = str(count) self.trade = Trade('id', 'time', Offer('id', now(), 11.8, 0.5, 'FakeArea'), 'FakeArea', 'buyer') self.created_offers = [] self.offers = { 'id': Offer('id', now(), 11.8, 0.5, 'FakeArea'), 'id2': Offer('id2', now(), 20, 0.5, 'A'), 'id3': Offer('id3', now(), 20, 1, 'A'), 'id4': Offer('id4', now(), 19, 5.1, 'A') } self.bids = {} self.created_balancing_offers = []
def test_assert_if_trade_rate_is_higher_than_bid_rate( load_hours_strategy_test3): market_id = 0 load_hours_strategy_test3._bids[market_id] = \ [Bid("bid_id", now(), 30, 1, buyer="FakeArea", seller="producer")] expensive_bid = Bid("bid_id", now(), 31, 1, buyer="FakeArea", seller="producer") trade = Trade("trade_id", "time", expensive_bid, load_hours_strategy_test3, "buyer") with pytest.raises(AssertionError): load_hours_strategy_test3.event_trade(market_id=market_id, trade=trade)
def test_iaa_event_offer_split_and_trade_correctly_populate_forwarded_offer_entries( iaa2): residual_offer_id = 'res_id' original_offer_id = 'id' original = iaa2.higher_market.forwarded_offer assert original.energy == 2 accepted = Offer(original.id, pendulum.now(), 1, 1, original.seller) residual = Offer(residual_offer_id, pendulum.now(), 1, 1, original.seller) iaa2.event_offer_split(market_id=iaa2.higher_market.id, original_offer=original, accepted_offer=accepted, residual_offer=residual) engine = next( (e for e in iaa2.engines if residual_offer_id in e.forwarded_offers), None) assert engine is not None, "Residual of forwarded offers not found in forwarded_offers" # after the split event: # all three offer ids are part of the forwarded_offer member assert set(engine.forwarded_offers.keys()) == { residual_offer_id, original_offer_id, 'uuid' } # and the accepted offer was added assert engine.forwarded_offers[ original_offer_id].target_offer.energy == accepted.energy # and the residual offer was added assert engine.forwarded_offers[ residual_offer_id].target_offer.energy == accepted.energy iaa2.event_trade(trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), accepted, 'owner', 'someone_else', residual, fee_price=0.0), market_id=iaa2.lower_market.id) # after the trade event: # the forwarded_offers only contain the residual offer assert set(engine.forwarded_offers.keys()) == {residual_offer_id} offer_info = engine.forwarded_offers[residual_offer_id] assert offer_info.source_offer.id == "uuid" assert offer_info.target_offer.id == residual_offer_id
def test_iaa_event_trade_buys_partial_accepted_bid(iaa_double_sided): iaa_double_sided._get_market_from_market_id = lambda x: iaa_double_sided.higher_market total_bid = iaa_double_sided.higher_market.forwarded_bid accepted_bid_price = (total_bid.price / total_bid.energy) * 1 residual_bid_price = (total_bid.price / total_bid.energy) * 0.1 accepted_bid = Bid(total_bid.id, accepted_bid_price, 1, total_bid.buyer, total_bid.seller) residual_bid = Bid('residual_bid', residual_bid_price, 0.1, total_bid.buyer, total_bid.seller) iaa_double_sided.event_bid_changed( market_id=iaa_double_sided.higher_market, existing_bid=total_bid, new_bid=residual_bid) iaa_double_sided.event_bid_traded( bid_trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), accepted_bid, 'owner', 'someone_else', 'residual_offer'), market_id=iaa_double_sided.higher_market.id) assert iaa_double_sided.lower_market.calls_energy_bids[0] == 1
def test_event_trade(area_test2, bus_test2): bus_test2.event_activate() bus_test2.event_market_cycle() traded_offer = Offer( id="id", time=pendulum.now(), price=20, energy=1, seller="FakeArea", ) bus_test2.event_trade(market_id=area_test2.test_market.id, trade=Trade(id="id", time="time", offer_bid=traded_offer, seller="FakeArea", buyer="buyer")) assert len(area_test2.test_market.created_offers) == 1 assert area_test2.test_market.created_offers[-1].energy == sys.maxsize
def test_iaa_event_trade_buys_partial_accepted_bid(iaa_double_sided): iaa_double_sided._get_market_from_market_id = lambda x: iaa_double_sided.higher_market original_bid = iaa_double_sided.higher_market.forwarded_bid accepted_bid_price = (original_bid.price / original_bid.energy) * 1 residual_bid_price = (original_bid.price / original_bid.energy) * 0.1 accepted_bid = Bid(original_bid.id, original_bid.time, accepted_bid_price, 1, original_bid.buyer) residual_bid = Bid('residual_bid', original_bid.time, residual_bid_price, 0.1, original_bid.buyer) iaa_double_sided.event_bid_split(market_id=iaa_double_sided.higher_market, original_bid=original_bid, accepted_bid=accepted_bid, residual_bid=residual_bid) iaa_double_sided.event_bid_traded( bid_trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), accepted_bid, 'owner', 'someone_else', 'residual_offer'), market_id=iaa_double_sided.higher_market.id) assert iaa_double_sided.lower_market.calls_energy_bids[0] == 1
def accept_offer(self, offer, buyer, *, energy=None, time=None, already_tracked=False, trade_rate: float = None, trade_bid_info=None, buyer_origin=None): trade = Trade('trade_id', time, offer, offer.seller, buyer, seller_origin=offer.seller_origin, buyer_origin=buyer_origin) self.traded_offers.append(trade) return trade
def accept_offer(self, offer, id, *, energy=None, time=None, already_tracked=False, trade_rate: float = None, trade_bid_info=None, buyer_origin=None): if self.raises: raise MarketException else: offer.energy = energy return Trade('trade', 0, offer, offer.seller, 'FakeOwner', seller_origin=offer.seller_origin, buyer_origin=buyer_origin)
def test_iaa_event_trade_bid_does_not_update_forwarded_bids_on_partial(iaa_bid, called, partial): iaa_bid.lower_market.delete_bid = called low_to_high_engine = iaa_bid.engines[0] 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), low_to_high_engine.markets.target.bids[target_bid.id], seller='someone_else', buyer='owner', residual=partial)) if not partial: assert source_bid.id not in low_to_high_engine.forwarded_bids assert target_bid.id not in low_to_high_engine.forwarded_bids else: assert source_bid.id in low_to_high_engine.forwarded_bids assert target_bid.id in low_to_high_engine.forwarded_bids
def test_event_trade_after_offer_changed_partial_offer(area_test2, commercial_test2): original_offer = Offer(id='old_id', time=pendulum.now(), price=20, energy=1, seller='FakeArea') accepted_offer = Offer(id='old_id', time=pendulum.now(), price=15, energy=0.75, seller='FakeArea') residual_offer = Offer(id='res_id', time=pendulum.now(), price=5, energy=0.25, seller='FakeArea') commercial_test2.offers.post(original_offer, area_test2.test_market.id) commercial_test2.event_offer_split(market_id=area_test2.test_market.id, original_offer=original_offer, accepted_offer=accepted_offer, residual_offer=residual_offer) assert original_offer.id in commercial_test2.offers.split assert commercial_test2.offers.split[original_offer.id] == accepted_offer commercial_test2.event_trade(market_id=area_test2.test_market.id, trade=Trade(id='id', time='time', offer=original_offer, seller='FakeArea', buyer='buyer')) assert residual_offer in commercial_test2.offers.posted assert commercial_test2.offers.posted[ residual_offer] == area_test2.test_market.id assert len(commercial_test2.offers.posted) == 1 assert len(commercial_test2.offers.split) == 1 assert len(commercial_test2.offers.sold) == 1 assert original_offer in commercial_test2.offers.sold_in_market( area_test2.test_market.id)
def test_event_bid_traded_removes_bid_from_pending_if_energy_req_0(load_hours_strategy_test5, market_test2, called): 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(area=area_test2) bid = list(load_hours_strategy_test5._bids.values())[0][0] # Increase energy requirement to cover the energy from the bid + threshold load_hours_strategy_test5.energy_requirement_Wh[TIME] = bid.energy * 1000 + 0.000009 trade = Trade('idt', None, bid, 'B', load_hours_strategy_test5.owner.name, residual=True) load_hours_strategy_test5.event_bid_traded(market_id=trade_market.id, bid_trade=trade) assert len(load_hours_strategy_test5.remove_bid_from_pending.calls) == 1 assert load_hours_strategy_test5.remove_bid_from_pending.calls[0][0][0] == repr(bid.id) assert load_hours_strategy_test5.remove_bid_from_pending.calls[0][0][1] == \ repr(trade_market) ConstSettings.IAASettings.MARKET_TYPE = 1
def test_publish_event_converts_python_objects_to_json(self): offer = Offer("1", 2, 3, "A") trade = Trade("2", now(), Offer("accepted", 7, 8, "Z"), "B", "C") new_offer = Offer("3", 4, 5, "D") existing_offer = Offer("4", 5, 6, "E") kwargs = {"offer": offer, "trade": trade, "new_offer": new_offer, "existing_offer": existing_offer} for dispatcher in [self.area.dispatcher.market_event_dispatcher, self.device1.dispatcher.market_event_dispatcher, self.device2.dispatcher.market_event_dispatcher]: dispatcher.publish_event(dispatcher.area.uuid, MarketEvent.OFFER, **kwargs) assert dispatcher.redis.publish.call_count == 1 payload = json.loads(dispatcher.redis.publish.call_args_list[0][0][1]) assert isinstance(payload["kwargs"]["offer"], str) assert offer_from_JSON_string(payload["kwargs"]["offer"]) == offer assert isinstance(payload["kwargs"]["trade"], str) assert trade_from_JSON_string(payload["kwargs"]["trade"]) == trade assert isinstance(payload["kwargs"]["new_offer"], str) assert offer_from_JSON_string(payload["kwargs"]["new_offer"]) == new_offer assert isinstance(payload["kwargs"]["existing_offer"], str) assert offer_from_JSON_string(payload["kwargs"]["existing_offer"]) == existing_offer
def test_publish_event_subscribes_to_response_and_publishes(self): offer = Offer("1", now(), 2, 3, "A") trade = Trade("2", now(), Offer("accepted", now(), 7, 8, "Z"), "B", "C") new_offer = Offer("3", now(), 4, 5, "D") existing_offer = Offer("4", now(), 5, 6, "E") kwargs = {"offer": offer, "trade": trade, "new_offer": new_offer, "existing_offer": existing_offer} self.publisher.publish_event(MarketEvent.OFFER, **kwargs) self.publisher.redis.sub_to_channel.assert_called_once_with( "market/test_id/notify_event/response", self.publisher.response_callback ) expected_result = {k: v.to_json_string() for k, v in kwargs.items()} self.publisher.redis.publish.assert_called_once() assert self.publisher.redis.publish.call_args_list[0][0][0] == \ "market/test_id/notify_event" publish_call_args = json.loads(self.publisher.redis.publish.call_args_list[0][0][1]) assert publish_call_args["event_type"] == MarketEvent.OFFER.value assert len(DeepDiff(publish_call_args["kwargs"], expected_result)) == 0
def test_event_trade_after_offer_changed_partial_offer(area_test2, bus_test2): original_offer = Offer(id="old_id", time=pendulum.now(), price=20, energy=1, seller="FakeArea") accepted_offer = Offer(id="old_id", time=pendulum.now(), price=15, energy=0.75, seller="FakeArea") residual_offer = Offer(id="res_id", time=pendulum.now(), price=5, energy=0.25, seller="FakeArea") bus_test2.offers.post(original_offer, area_test2.test_market.id) bus_test2.event_offer_split(market_id=area_test2.test_market.id, original_offer=original_offer, accepted_offer=accepted_offer, residual_offer=residual_offer) assert original_offer.id in bus_test2.offers.split assert bus_test2.offers.split[original_offer.id] == accepted_offer bus_test2.event_trade(market_id=area_test2.test_market.id, trade=Trade(id="id", time="time", offer_bid=original_offer, seller="FakeArea", buyer="buyer")) assert residual_offer in bus_test2.offers.posted assert bus_test2.offers.posted[residual_offer] == area_test2.test_market.id assert len(bus_test2.offers.posted) == 1 assert len(bus_test2.offers.split) == 1 assert len(bus_test2.offers.sold) == 1 assert original_offer in bus_test2.offers.sold_in_market( area_test2.test_market.id)
def test_balancing_offers_are_created_if_device_in_registry( balancing_fixture, area_test2): ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True DeviceRegistry.REGISTRY = {'FakeArea': (30, 40)} balancing_fixture.event_activate() balancing_fixture.event_market_cycle() balancing_fixture.event_balancing_market_cycle() expected_balancing_demand_energy = \ balancing_fixture.balancing_energy_ratio.demand * \ balancing_fixture.energy_per_slot_Wh actual_balancing_demand_energy = \ area_test2.test_balancing_market.created_balancing_offers[0].energy assert len(area_test2.test_balancing_market.created_balancing_offers) == 1 assert actual_balancing_demand_energy == -expected_balancing_demand_energy actual_balancing_demand_price = \ area_test2.test_balancing_market.created_balancing_offers[0].price assert actual_balancing_demand_price == expected_balancing_demand_energy * 30 selected_offer = area_test2.current_market.sorted_offers[0] balancing_fixture.energy_requirement_Wh[area_test2.current_market.time_slot] = \ selected_offer.energy * 1000.0 balancing_fixture.event_trade(market_id=area_test2.current_market.id, trade=Trade(id='id', time=area_test2.now, offer=selected_offer, seller='B', buyer='FakeArea')) assert len(area_test2.test_balancing_market.created_balancing_offers) == 2 actual_balancing_supply_energy = \ area_test2.test_balancing_market.created_balancing_offers[1].energy expected_balancing_supply_energy = \ selected_offer.energy * balancing_fixture.balancing_energy_ratio.supply assert actual_balancing_supply_energy == expected_balancing_supply_energy actual_balancing_supply_price = \ area_test2.test_balancing_market.created_balancing_offers[1].price assert actual_balancing_supply_price == expected_balancing_supply_energy * 40 DeviceRegistry.REGISTRY = {}
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 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 _trade(price, buyer, energy=1, seller=None, fee_price=None): return Trade('id', 0, FakeOffer(price, energy, seller), seller, buyer, None, fee_price=fee_price)
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 _trade(price, buyer, energy=1, seller=None, fee_price=0.): return Trade('id', now(tz=constants.TIME_ZONE), FakeOffer(price, energy, seller), seller, buyer, None, fee_price=fee_price)
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 _trade(price, buyer, energy=1, seller=None): return Trade('id', 0, FakeOffer(price, energy, seller), 'seller', buyer, None)
def accept_offer(self, offer, id, *, energy=None, time=None, price_drop=False): if self.raises: raise MarketException else: offer.energy = energy return Trade('trade', 0, offer, offer.seller, 'FakeOwner')
def accept_offer(self, **kwargs): return Trade("", "", "", "", "")
def test_iaa_event_trade_buys_accepted_offer(iaa2): iaa2.event_trade(trade=Trade('trade_id', pendulum.now(tz=TIME_ZONE), iaa2.higher_market.forwarded_offer, 'owner', 'someone_else'), market_id=iaa2.higher_market.id) assert len(iaa2.lower_market.calls_energy) == 1