def _export_trade_csv_files(self, area: Area, directory: dir, balancing: bool = False, is_first: bool = False): """ Exports files containing individual trades (*-trades.csv files) return: dict[out_keys] """ if balancing: file_path = self._file_path( directory, "{}-balancing-trades".format(area.slug)) labels = ("slot", ) + BalancingTrade._csv_fields() past_markets = area.past_balancing_markets else: file_path = self._file_path(directory, "{}-trades".format(area.slug)) labels = ("slot", ) + Trade._csv_fields() past_markets = area.past_markets try: with open(file_path, 'a') as csv_file: writer = csv.writer(csv_file) if is_first: writer.writerow(labels) for market in past_markets: for trade in market.trades: row = (market.time_slot, ) + trade._to_csv() writer.writerow(row) except OSError: _log.exception("Could not export area trades")
def accept_offer(self, offer_or_id, buyer, energy=None, time=None, trade_rate: float = None): if time is None: time = self.time_slot offer = offer_or_id if (offer.energy > 0 and energy < 0) or (offer.energy < 0 and energy > 0): raise InvalidBalancingTradeException("BalancingOffer and energy " "are not compatible") if abs(energy) < abs(offer.energy): residual_energy = offer.energy - energy residual = BalancingOffer('res', pendulum.now(), offer.price, residual_energy, offer.seller) traded = BalancingOffer(offer.id, pendulum.now(), offer.price, energy, offer.seller) return BalancingTrade('trade_id', time, traded, traded.seller, buyer, residual) else: return BalancingTrade('trade_id', time, offer, offer.seller, buyer)
def accept_offer(self, offer_or_id: Union[str, BalancingOffer], buyer: str, *, energy: int = None, time: DateTime = None, already_tracked: bool = False, trade_rate: float = None, trade_bid_info: float = None, buyer_origin=None) -> BalancingTrade: if self.readonly: raise MarketReadOnlyException() if isinstance(offer_or_id, Offer): offer_or_id = offer_or_id.id offer = self.offers.pop(offer_or_id, None) if offer is None: raise OfferNotFoundException() if (offer.energy > 0 and energy < 0) or (offer.energy < 0 and energy > 0): raise InvalidBalancingTradeException("BalancingOffer and energy " "are not compatible") if energy is None: energy = offer.energy original_offer = offer residual_offer = None if trade_rate is None: trade_rate = offer.energy_rate orig_offer_price = self._calculate_original_prices(offer) try: if time is None: time = self.now if energy == 0: raise InvalidBalancingTradeException("Energy can not be zero.") elif abs(energy) < abs(offer.energy): # partial energy is requested assert trade_rate + FLOATING_POINT_TOLERANCE >= (offer.price / offer.energy) original_offer = offer accepted_offer, residual_offer = self.split_offer( offer, energy, orig_offer_price) fees, trade_price = self.determine_offer_price( energy / offer.energy, energy, trade_rate, trade_bid_info, orig_offer_price) offer = accepted_offer offer.update_price(trade_price) elif abs(energy) > abs(offer.energy): raise InvalidBalancingTradeException( "Energy can't be greater than offered energy") else: # Requested energy is equal to offer's energy - just proceed normally fees, trade_price = self._update_offer_fee_and_calculate_final_price( energy, trade_rate, 1, orig_offer_price ) if already_tracked is False else energy * trade_rate offer.update_price(trade_price) except Exception: # Exception happened - restore offer self.offers[offer.id] = offer raise # Delete the accepted offer from self.offers: self.offers.pop(offer.id, None) trade_id, residual_offer = \ self.bc_interface.handle_blockchain_trade_event( offer, buyer, original_offer, residual_offer ) trade = BalancingTrade(trade_id, time, offer, offer.seller, buyer, residual_offer, seller_origin=offer.seller_origin, buyer_origin=buyer_origin, fee_price=fees) self.bc_interface.track_trade_event(trade) if already_tracked is False: self._update_stats_after_trade(trade, offer, buyer) log.info(f"[BALANCING_TRADE] [{self.time_slot_str}] {trade}") # TODO: Use non-blockchain non-event-driven version for now for both blockchain and # normal runs. self._notify_listeners(MarketEvent.BALANCING_TRADE, trade=trade) return trade
def accept_offer(self, offer_or_id: Union[str, BalancingOffer], buyer: str, *, energy: int = None, time: DateTime = None, already_tracked: bool = False, trade_rate: float = None, trade_bid_info: float = None, buyer_origin=None) -> BalancingTrade: if self.readonly: raise MarketReadOnlyException() if isinstance(offer_or_id, Offer): offer_or_id = offer_or_id.id offer = self.offers.pop(offer_or_id, None) if offer is None: raise OfferNotFoundException() if (offer.energy > 0 and energy < 0) or (offer.energy < 0 and energy > 0): raise InvalidBalancingTradeException("BalancingOffer and energy " "are not compatible") if energy is None: energy = offer.energy original_offer = offer residual_offer = None if trade_rate is None: trade_rate = offer.price / offer.energy orig_offer_price = self._calculate_original_prices(offer) self._sorted_offers = sorted(self.offers.values(), key=lambda o: o.price / o.energy) try: if time is None: time = self._now energy_portion = energy / offer.energy if energy == 0: raise InvalidBalancingTradeException("Energy can not be zero.") # partial energy is requested elif abs(energy) < abs(offer.energy): original_offer = offer accepted_offer_id = offer.id \ if self.area is None or self.area.bc is None \ else offer.real_id assert trade_rate + FLOATING_POINT_TOLERANCE >= (offer.price / offer.energy) final_price = self._update_offer_fee_and_calculate_final_price( energy, trade_rate, energy_portion, orig_offer_price ) if already_tracked is False else energy * trade_rate accepted_offer = Offer(accepted_offer_id, abs(final_price), energy, offer.seller, seller_origin=offer.seller_origin) residual_price = (1 - energy_portion) * offer.price residual_energy = offer.energy - energy original_residual_price = \ ((offer.energy - energy) / offer.energy) * orig_offer_price residual_offer = Offer( str(uuid.uuid4()), abs(residual_price), residual_energy, offer.seller, original_offer_price=original_residual_price, seller_origin=offer.seller_origin) self.offers[residual_offer.id] = residual_offer log.debug(f"[BALANCING_OFFER][CHANGED][{self.time_slot_str}] " f"{original_offer} -> {residual_offer}") offer = accepted_offer self.bc_interface.change_offer(offer, original_offer, residual_offer) self._sorted_offers = sorted(self.offers.values(), key=lambda o: o.price / o.energy) self._notify_listeners(MarketEvent.BALANCING_OFFER_CHANGED, existing_offer=original_offer, new_offer=residual_offer) elif abs(energy) > abs(offer.energy): raise InvalidBalancingTradeException( "Energy can't be greater than offered energy") else: # Requested energy is equal to offer's energy - just proceed normally offer.price = self._update_offer_fee_and_calculate_final_price( energy, trade_rate, 1, orig_offer_price ) if already_tracked is False else energy * trade_rate except Exception: # Exception happened - restore offer self.offers[offer.id] = offer self._sorted_offers = sorted(self.offers.values(), key=lambda o: o.price / o.energy) raise trade_id, residual_offer = \ self.bc_interface.handle_blockchain_trade_event( offer, buyer, original_offer, residual_offer ) trade = BalancingTrade(trade_id, time, offer, offer.seller, buyer, residual_offer, seller_origin=offer.seller_origin, buyer_origin=buyer_origin) self.bc_interface.track_trade_event(trade) if already_tracked is False: self._update_stats_after_trade(trade, offer, buyer) log.info(f"[BALANCING_TRADE] [{self.time_slot_str}] {trade}") # TODO: Use non-blockchain non-event-driven version for now for both blockchain and # normal runs. self._notify_listeners(MarketEvent.BALANCING_TRADE, trade=trade) return trade
def accept_offer(self, offer_or_id: Union[str, BalancingOffer], buyer: str, *, energy: int = None, time: DateTime = None, price_drop: bool = False) -> BalancingTrade: if self.readonly: raise MarketReadOnlyException() if isinstance(offer_or_id, Offer): offer_or_id = offer_or_id.id residual_offer = None offer = self.offers.pop(offer_or_id, None) self._sorted_offers = sorted(self.offers.values(), key=lambda o: o.price / o.energy) if offer is None: raise OfferNotFoundException() if (offer.energy > 0 and energy < 0) or (offer.energy < 0 and energy > 0): raise InvalidBalancingTradeException("BalancingOffer and energy " "are not compatible") try: if time is None: time = self._now if energy is not None: # Partial trade if energy == 0: raise InvalidBalancingTradeException( "Energy can not be zero.") elif abs(energy) < abs(offer.energy): original_offer = offer accepted_offer = Offer( offer.id, abs((offer.price / offer.energy) * energy), energy, offer.seller, offer.market) residual_energy = (offer.energy - energy) residual_offer = Offer( str(uuid.uuid4()), abs((offer.price / offer.energy) * residual_energy), residual_energy, offer.seller, offer.market) self.offers[residual_offer.id] = residual_offer log.info( f"[BALANCING_OFFER][CHANGED][{self.time_slot_str}] " f"{original_offer} -> {residual_offer}") offer = accepted_offer self._sorted_offers = sorted( self.offers.values(), key=lambda o: o.price / o.energy) self._notify_listeners(MarketEvent.BALANCING_OFFER_CHANGED, existing_offer=original_offer, new_offer=residual_offer) elif abs(energy) > abs(offer.energy): raise InvalidBalancingTradeException( f"Energy {energy} can't be greater than offered energy {offer}" ) else: # Requested partial is equal to offered energy - just proceed normally pass except Exception: # Exception happened - restore offer self.offers[offer.id] = offer self._sorted_offers = sorted(self.offers.values(), key=lambda o: o.price / o.energy) raise trade = BalancingTrade(id=str(uuid.uuid4()), time=time, offer=offer, seller=offer.seller, buyer=buyer, residual=residual_offer, price_drop=price_drop) self.trades.append(trade) self._update_accumulated_trade_price_energy(trade) log.warning(f"[BALANCING_TRADE][{self.time_slot_str}] {trade}") self.traded_energy[offer.seller] += offer.energy self.traded_energy[buyer] -= offer.energy self.ious[buyer][offer.seller] += offer.price self._update_min_max_avg_trade_prices(offer.price / offer.energy) # Recalculate offer min/max price since offer was removed self._update_min_max_avg_offer_prices() offer._traded(trade, self) self._notify_listeners(MarketEvent.BALANCING_TRADE, trade=trade) return trade