diff --git a/.pylintrc b/.pylintrc
index dbb2ca8038..a3d0730caa 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -66,16 +66,6 @@ confidence=
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=invalid-name,
- print-statement,
- parameter-unpacking,
- unpacking-in-except,
- old-raise-syntax,
- backtick,
- long-suffix,
- old-ne-operator,
- old-octal-literal,
- import-star-module-level,
- non-ascii-bytes-literal,
raw-checker-failed,
bad-inline-option,
locally-disabled,
@@ -84,67 +74,6 @@ disable=invalid-name,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
- apply-builtin,
- basestring-builtin,
- buffer-builtin,
- cmp-builtin,
- coerce-builtin,
- execfile-builtin,
- file-builtin,
- long-builtin,
- raw_input-builtin,
- reduce-builtin,
- standarderror-builtin,
- unicode-builtin,
- xrange-builtin,
- coerce-method,
- delslice-method,
- getslice-method,
- setslice-method,
- no-absolute-import,
- old-division,
- dict-iter-method,
- dict-view-method,
- next-method-called,
- metaclass-assignment,
- indexing-exception,
- raising-string,
- reload-builtin,
- oct-method,
- hex-method,
- nonzero-method,
- cmp-method,
- input-builtin,
- round-builtin,
- intern-builtin,
- unichr-builtin,
- map-builtin-not-iterating,
- zip-builtin-not-iterating,
- range-builtin-not-iterating,
- filter-builtin-not-iterating,
- using-cmp-argument,
- eq-without-hash,
- div-method,
- idiv-method,
- rdiv-method,
- exception-message-attribute,
- invalid-str-codec,
- sys-max-int,
- bad-python3-import,
- deprecated-string-function,
- deprecated-str-translate-call,
- deprecated-itertools-function,
- deprecated-types-field,
- next-method-defined,
- dict-items-not-iterating,
- dict-keys-not-iterating,
- dict-values-not-iterating,
- deprecated-operator-function,
- deprecated-urllib-function,
- xreadlines-attribute,
- deprecated-sys-function,
- exception-escape,
- comprehension-escape,
missing-module-docstring,
too-many-ancestors,
duplicate-code
@@ -607,5 +536,5 @@ min-public-methods=0
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
-overgeneral-exceptions=BaseException,
- Exception
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception
diff --git a/src/gsy_e/models/strategy/__init__.py b/src/gsy_e/models/strategy/__init__.py
index c71df8da93..f4e430eaba 100644
--- a/src/gsy_e/models/strategy/__init__.py
+++ b/src/gsy_e/models/strategy/__init__.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import json
import logging
import sys
@@ -60,6 +61,7 @@
@dataclass
class AcceptOfferParameters:
"""Parameters for the accept_offer MarketStrategyConnectionAdapter methods"""
+
market: Union["OneSidedMarket", str]
offer: Offer
buyer: TraderDetails
@@ -68,22 +70,27 @@ class AcceptOfferParameters:
def to_dict(self) -> dict:
"""Convert dataclass to dict in order to be able to send these arguments via Redis."""
- return {"offer_or_id": self.offer.to_json_string(),
- "buyer": self.buyer.serializable_dict(),
- "energy": self.energy,
- "trade_bid_info": self.trade_bid_info.serializable_dict()}
+ return {
+ "offer_or_id": self.offer.to_json_string(),
+ "buyer": self.buyer.serializable_dict(),
+ "energy": self.energy,
+ "trade_bid_info": self.trade_bid_info.serializable_dict(),
+ }
def accept_offer_using_market_object(self) -> Trade:
"""Calls accept offer on the market object that is contained in the dataclass,
- using the arguments from the other dataclass members"""
+ using the arguments from the other dataclass members"""
return self.market.accept_offer(
- offer_or_id=self.offer, buyer=self.buyer,
+ offer_or_id=self.offer,
+ buyer=self.buyer,
energy=self.energy,
- trade_bid_info=self.trade_bid_info)
+ trade_bid_info=self.trade_bid_info,
+ )
class _TradeLookerUpper:
"""Find trades that concern the strategy from the selected market"""
+
def __init__(self, owner_name: str):
self.owner_name = owner_name
@@ -94,8 +101,9 @@ def __getitem__(self, market: MarketBase) -> Generator[Trade, None, None]:
yield trade
-def market_strategy_connection_adapter_factory() -> Union["MarketStrategyConnectionAdapter",
- "MarketStrategyConnectionRedisAdapter"]:
+def market_strategy_connection_adapter_factory() -> (
+ Union["MarketStrategyConnectionAdapter", "MarketStrategyConnectionRedisAdapter"]
+):
"""
Return an object of either MarketStrategyConnectionRedisAdapter or
MarketStrategyConnectionAdapter, depending on whether the flag EVENT_DISPATCHING_VIA_REDIS is
@@ -114,6 +122,7 @@ class MarketStrategyConnectionRedisAdapter:
the class methods. Useful for markets that are run in a remote service, different from
the strategy.
"""
+
def __init__(self):
self.redis = BlockingCommunicator()
self._trade_buffer: Optional[Trade] = None
@@ -132,8 +141,7 @@ def offer(self, market: Union["OneSidedMarket", str], offer_args: dict) -> Offer
"""
market_id = market.id if not isinstance(market, str) else market
- self._send_events_to_market("OFFER", market_id, offer_args,
- self._redis_offer_response)
+ self._send_events_to_market("OFFER", market_id, offer_args, self._redis_offer_response)
offer = self._offer_buffer
assert offer is not None
self._offer_buffer = None
@@ -141,12 +149,18 @@ def offer(self, market: Union["OneSidedMarket", str], offer_args: dict) -> Offer
def accept_offer(self, offer_parameters: AcceptOfferParameters) -> Trade:
"""Accept an offer on a market via Redis."""
- market_id = (offer_parameters.market.id
- if not isinstance(offer_parameters.market, str)
- else offer_parameters.market)
+ market_id = (
+ offer_parameters.market.id
+ if not isinstance(offer_parameters.market, str)
+ else offer_parameters.market
+ )
- self._send_events_to_market("ACCEPT_OFFER", market_id, offer_parameters.to_dict(),
- self._redis_accept_offer_response)
+ self._send_events_to_market(
+ "ACCEPT_OFFER",
+ market_id,
+ offer_parameters.to_dict(),
+ self._redis_accept_offer_response,
+ )
trade = self._trade_buffer
self._trade_buffer = None
assert trade is not None
@@ -160,17 +174,24 @@ def _redis_accept_offer_response(self, payload: dict):
else:
raise D3ARedisException(
f"Error when receiving response on channel {payload['channel']}:: "
- f"{data['exception']}: {data['error_message']} {data}")
+ f"{data['exception']}: {data['error_message']} {data}"
+ )
def delete_offer(self, market: Union["OneSidedMarket", str], offer: Offer) -> None:
"""Delete offer from a market"""
market_id = market.id if not isinstance(market, str) else market
data = {"offer_or_id": offer.to_json_string()}
- self._send_events_to_market("DELETE_OFFER", market_id, data,
- self._redis_delete_offer_response)
+ self._send_events_to_market(
+ "DELETE_OFFER", market_id, data, self._redis_delete_offer_response
+ )
- def _send_events_to_market(self, event_type_str: str, market_id: Union[str, MarketBase],
- data: dict, callback: Callable) -> None:
+ def _send_events_to_market(
+ self,
+ event_type_str: str,
+ market_id: Union[str, MarketBase],
+ data: dict,
+ callback: Callable,
+ ) -> None:
if not isinstance(market_id, str):
market_id = market_id.id
response_channel = f"{market_id}/{event_type_str}/RESPONSE"
@@ -186,8 +207,11 @@ def event_response_was_received_callback():
self.redis.poll_until_response_received(event_response_was_received_callback)
if data["transaction_uuid"] not in self._event_response_uuids:
- logging.error("Transaction ID not found after %s seconds: %s",
- REDIS_PUBLISH_RESPONSE_TIMEOUT, data)
+ logging.error(
+ "Transaction ID not found after %s seconds: %s",
+ REDIS_PUBLISH_RESPONSE_TIMEOUT,
+ data,
+ )
else:
self._event_response_uuids.remove(data["transaction_uuid"])
@@ -199,7 +223,8 @@ def _redis_delete_offer_response(self, payload: dict) -> None:
else:
raise D3ARedisException(
f"Error when receiving response on channel {payload['channel']}:: "
- f"{data['exception']}: {data['error_message']}")
+ f"{data['exception']}: {data['error_message']}"
+ )
def _redis_offer_response(self, payload):
data = json.loads(payload["data"])
@@ -209,7 +234,8 @@ def _redis_offer_response(self, payload):
else:
raise D3ARedisException(
f"Error when receiving response on channel {payload['channel']}:: "
- f"{data['exception']}: {data['error_message']}")
+ f"{data['exception']}: {data['error_message']}"
+ )
class MarketStrategyConnectionAdapter:
@@ -217,6 +243,7 @@ class MarketStrategyConnectionAdapter:
Adapter to the MarketBase class. Used by default when accessing the market object directly and
not via Redis.
"""
+
@staticmethod
def accept_offer(offer_parameters: AcceptOfferParameters) -> Trade:
"""Accept an offer on a market."""
@@ -252,7 +279,7 @@ def __init__(self, strategy: "BaseStrategy"):
self.split = {} # type: Dict[str, Offer]
def _delete_past_offers(
- self, existing_offers: Dict[Offer, str], current_time_slot: DateTime
+ self, existing_offers: Dict[Offer, str], current_time_slot: DateTime
) -> Dict[Offer, str]:
offers = {}
for offer, market_id in existing_offers.items():
@@ -290,8 +317,9 @@ def sold_offer(self, offer: Offer, market_id: str) -> None:
def is_offer_posted(self, market_id: str, offer_id: str) -> bool:
"""Check if offer is posted on the market"""
- return offer_id in [offer.id for offer, _market in self.posted.items()
- if market_id == _market]
+ return offer_id in [
+ offer.id for offer, _market in self.posted.items() if market_id == _market
+ ]
def _get_sold_offer_ids_in_market(self, market_id: str) -> List[str]:
sold_offer_ids = []
@@ -304,9 +332,11 @@ def open_in_market(self, market_id: str, time_slot: DateTime = None) -> List[Off
open_offers = []
sold_offer_ids = self._get_sold_offer_ids_in_market(market_id)
for offer, _market_id in self.posted.items():
- if (offer.id not in sold_offer_ids
- and market_id == _market_id
- and (time_slot is None or offer.time_slot == time_slot)):
+ if (
+ offer.id not in sold_offer_ids
+ and market_id == _market_id
+ and (time_slot is None or offer.time_slot == time_slot)
+ ):
open_offers.append(offer)
return open_offers
@@ -316,27 +346,35 @@ def open_offer_energy(self, market_id: str, time_slot: DateTime = None) -> float
def posted_in_market(self, market_id: str, time_slot: DateTime = None) -> List[Offer]:
"""Get list of posted offers in market"""
- return [offer
- for offer, _market in self.posted.items()
- if market_id == _market and (time_slot is None or offer.time_slot == time_slot)]
+ return [
+ offer
+ for offer, _market in self.posted.items()
+ if market_id == _market and (time_slot is None or offer.time_slot == time_slot)
+ ]
def posted_offer_energy(self, market_id: str, time_slot: DateTime = None) -> float:
"""Get energy of all posted offers"""
- return sum(o.energy
- for o in self.posted_in_market(market_id, time_slot)
- if time_slot is None or o.time_slot == time_slot)
+ return sum(
+ o.energy
+ for o in self.posted_in_market(market_id, time_slot)
+ if time_slot is None or o.time_slot == time_slot
+ )
def sold_offer_energy(self, market_id: str, time_slot: DateTime = None) -> float:
"""Get energy of all sold offers"""
- return sum(o.energy
- for o in self.sold_in_market(market_id)
- if time_slot is None or o.time_slot == time_slot)
+ return sum(
+ o.energy
+ for o in self.sold_in_market(market_id)
+ if time_slot is None or o.time_slot == time_slot
+ )
def sold_offer_price(self, market_id: str, time_slot: DateTime = None) -> float:
"""Get sum of all sold offers' price"""
- return sum(o.price
- for o in self.sold_in_market(market_id)
- if time_slot is None or o.time_slot == time_slot)
+ return sum(
+ o.price
+ for o in self.sold_in_market(market_id)
+ if time_slot is None or o.time_slot == time_slot
+ )
def sold_in_market(self, market_id: str) -> List[Offer]:
"""Get list of sold offers in a market"""
@@ -344,9 +382,14 @@ def sold_in_market(self, market_id: str) -> List[Offer]:
# pylint: disable=too-many-arguments
def can_offer_be_posted(
- self, offer_energy: float, offer_price: float, available_energy: float,
- market: "MarketBase", replace_existing: bool = False,
- time_slot: Optional[DateTime] = None) -> bool:
+ self,
+ offer_energy: float,
+ offer_price: float,
+ available_energy: float,
+ market: "MarketBase",
+ replace_existing: bool = False,
+ time_slot: Optional[DateTime] = None,
+ ) -> bool:
"""
Check whether an offer with the specified parameters can be posted on the market
Args:
@@ -370,8 +413,9 @@ def can_offer_be_posted(
total_posted_energy = offer_energy + posted_offer_energy
- return ((total_posted_energy - available_energy) < FLOATING_POINT_TOLERANCE
- and offer_price >= 0.0)
+ return (
+ total_posted_energy - available_energy
+ ) < FLOATING_POINT_TOLERANCE and offer_price >= 0.0
def post(self, offer: Offer, market_id: str) -> None:
"""Add offer to the posted dict"""
@@ -379,8 +423,9 @@ def post(self, offer: Offer, market_id: str) -> None:
if offer.id not in self.split:
self.posted[offer] = market_id
- def remove_offer_from_cache_and_market(self, market: "OneSidedMarket",
- offer_id: str = None) -> List[str]:
+ def remove_offer_from_cache_and_market(
+ self, market: "OneSidedMarket", offer_id: str = None
+ ) -> List[str]:
"""Delete offer from the market and remove it from the dicts"""
if offer_id is None:
to_delete_offers = self.open_in_market(market.id)
@@ -424,8 +469,9 @@ def on_trade(self, market_id: str, trade: Trade) -> None:
except AttributeError as ex:
raise SimulationException("Trade event before strategy was initialized.") from ex
- def on_offer_split(self, original_offer: Offer, accepted_offer: Offer, residual_offer: Offer,
- market_id: str) -> None:
+ def on_offer_split(
+ self, original_offer: Offer, accepted_offer: Offer, residual_offer: Offer, market_id: str
+ ) -> None:
"""React to the event of an offer split"""
if original_offer.seller.name == self.strategy.owner.name:
self.split[original_offer.id] = accepted_offer
@@ -440,6 +486,7 @@ class BaseStrategy(EventMixin, AreaBehaviorBase, ABC):
markets, thus removing the need to access the market to view the offers that the strategy
has posted. Define a common interface which all strategies should implement.
"""
+
# pylint: disable=too-many-public-methods
def __init__(self):
super().__init__()
@@ -451,8 +498,7 @@ def __init__(self):
self._settlement_market_strategy = self._create_settlement_market_strategy()
self._future_market_strategy = self._create_future_market_strategy()
- @staticmethod
- def serialize():
+ def serialize(self):
"""Serialize strategy status."""
return {}
@@ -469,8 +515,7 @@ def simulation_config(self) -> SimulationConfig:
def _create_settlement_market_strategy(cls):
return SettlementMarketStrategyInterface()
- @classmethod
- def _create_future_market_strategy(cls):
+ def _create_future_market_strategy(self):
return FutureMarketStrategyInterface()
def energy_traded(self, market_id: str, time_slot: DateTime = None) -> float:
@@ -489,8 +534,10 @@ def trades(self) -> _TradeLookerUpper:
@property
def _is_eligible_for_balancing_market(self) -> bool:
"""Check if strategy can participate in the balancing market"""
- return (self.owner.name in DeviceRegistry.REGISTRY and
- ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET)
+ return (
+ self.owner.name in DeviceRegistry.REGISTRY
+ and ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET
+ )
def _remove_existing_offers(self, market: "OneSidedMarket", time_slot: DateTime) -> None:
"""Remove all existing offers in the market with respect to time_slot."""
@@ -510,10 +557,12 @@ def post_offer(self, market, replace_existing=True, **offer_kwargs) -> Offer:
# Remove all existing offers that are still open in the market
self._remove_existing_offers(market, offer_kwargs.get("time_slot") or market.time_slot)
- if (not offer_kwargs.get("seller") or
- not isinstance(offer_kwargs.get("seller"), TraderDetails)):
+ if not offer_kwargs.get("seller") or not isinstance(
+ offer_kwargs.get("seller"), TraderDetails
+ ):
offer_kwargs["seller"] = TraderDetails(
- self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid)
+ self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
+ )
if not offer_kwargs.get("time_slot"):
offer_kwargs["time_slot"] = market.time_slot
@@ -522,13 +571,16 @@ def post_offer(self, market, replace_existing=True, **offer_kwargs) -> Offer:
return offer
- def post_first_offer(self, market: "OneSidedMarket", energy_kWh: float,
- initial_energy_rate: float) -> Optional[Offer]:
+ def post_first_offer(
+ self, market: "OneSidedMarket", energy_kWh: float, initial_energy_rate: float
+ ) -> Optional[Offer]:
"""Post first and only offer for the strategy. Will fail if another offer already
- exists."""
+ exists."""
if any(offer.seller.uuid == self.owner.uuid for offer in market.get_offers().values()):
- self.owner.log.debug("There is already another offer posted on the market, therefore"
- " do not repost another first offer.")
+ self.owner.log.debug(
+ "There is already another offer posted on the market, therefore"
+ " do not repost another first offer."
+ )
return None
return self.post_offer(
market,
@@ -537,8 +589,9 @@ def post_first_offer(self, market: "OneSidedMarket", energy_kWh: float,
energy=energy_kWh,
)
- def get_posted_offers(self, market: "OneSidedMarket",
- time_slot: Optional[DateTime] = None) -> List[Offer]:
+ def get_posted_offers(
+ self, market: "OneSidedMarket", time_slot: Optional[DateTime] = None
+ ) -> List[Offer]:
"""Get list of posted offers from a market"""
return self.offers.posted_in_market(market.id, time_slot)
@@ -560,8 +613,15 @@ def are_offers_posted(self, market_id: str) -> bool:
"""Checks if any offers have been posted in the market slot with the given ID."""
return len(self.offers.posted_in_market(market_id)) > 0
- def accept_offer(self, market: "OneSidedMarket", offer: Offer, *, buyer: TraderDetails = None,
- energy: float = None, trade_bid_info: "TradeBidOfferInfo" = None):
+ def accept_offer(
+ self,
+ market: "OneSidedMarket",
+ offer: Offer,
+ *,
+ buyer: TraderDetails = None,
+ energy: float = None,
+ trade_bid_info: "TradeBidOfferInfo" = None,
+ ):
"""
Accept an offer on a market.
Args:
@@ -580,8 +640,7 @@ def accept_offer(self, market: "OneSidedMarket", offer: Offer, *, buyer: TraderD
if not isinstance(offer, Offer):
offer = market.offers[offer]
trade = self._market_adapter.accept_offer(
- AcceptOfferParameters(
- market, offer, buyer, energy, trade_bid_info)
+ AcceptOfferParameters(market, offer, buyer, energy, trade_bid_info)
)
self.offers.bought_offer(trade.match_details["offer"], market.id)
@@ -610,8 +669,14 @@ def event_offer_traded(self, *, market_id: str, trade: Trade) -> None:
"""
self.offers.on_trade(market_id, trade)
- def event_offer_split(self, *, market_id: str, original_offer: Offer, accepted_offer: Offer,
- residual_offer: Offer) -> None:
+ def event_offer_split(
+ self,
+ *,
+ market_id: str,
+ original_offer: Offer,
+ accepted_offer: Offer,
+ residual_offer: Offer,
+ ) -> None:
"""React to the event of an offer split"""
self.offers.on_offer_split(original_offer, accepted_offer, residual_offer, market_id)
@@ -624,23 +689,38 @@ def event_market_cycle(self) -> None:
def _assert_if_trade_offer_price_is_too_low(self, market_id: str, trade: Trade) -> None:
if trade.is_offer_trade and trade.seller.name == self.owner.name:
- offer = [o for o in self.offers.sold[market_id]
- if o.id == trade.match_details["offer"].id][0]
- assert (trade.trade_rate >=
- offer.energy_rate - FLOATING_POINT_TOLERANCE)
+ offer = [
+ o for o in self.offers.sold[market_id] if o.id == trade.match_details["offer"].id
+ ][0]
+ assert trade.trade_rate >= offer.energy_rate - FLOATING_POINT_TOLERANCE
# pylint: disable=too-many-arguments
- def can_offer_be_posted(self, offer_energy: float, offer_price: float, available_energy: float,
- market: "OneSidedMarket", time_slot: Optional[DateTime],
- replace_existing: bool = False) -> bool:
+ def can_offer_be_posted(
+ self,
+ offer_energy: float,
+ offer_price: float,
+ available_energy: float,
+ market: "OneSidedMarket",
+ time_slot: Optional[DateTime],
+ replace_existing: bool = False,
+ ) -> bool:
"""Check if an offer with the selected attributes can be posted"""
return self.offers.can_offer_be_posted(
- offer_energy, offer_price, available_energy, market, time_slot=time_slot,
- replace_existing=replace_existing)
+ offer_energy,
+ offer_price,
+ available_energy,
+ market,
+ time_slot=time_slot,
+ replace_existing=replace_existing,
+ )
def can_settlement_offer_be_posted(
- self, offer_energy: float, offer_price: float,
- market: "OneSidedMarket", replace_existing: bool = False) -> bool:
+ self,
+ offer_energy: float,
+ offer_price: float,
+ market: "OneSidedMarket",
+ replace_existing: bool = False,
+ ) -> bool:
"""
Checks whether an offer can be posted to the settlement market
:param offer_energy: Energy of the offer that we want to post
@@ -654,8 +734,13 @@ def can_settlement_offer_be_posted(
return False
unsettled_energy_kWh = self.state.get_unsettled_deviation_kWh(market.time_slot)
return self.offers.can_offer_be_posted(
- offer_energy, offer_price, unsettled_energy_kWh, market, time_slot=market.time_slot,
- replace_existing=replace_existing)
+ offer_energy,
+ offer_price,
+ unsettled_energy_kWh,
+ market,
+ time_slot=market.time_slot,
+ replace_existing=replace_existing,
+ )
@property
def spot_market(self) -> "MarketBase":
@@ -669,8 +754,9 @@ def spot_market_time_slot(self) -> Optional[DateTime]:
return None
return self.spot_market.time_slot
- def update_offer_rates(self, market: "OneSidedMarket", updated_rate: float,
- time_slot: Optional[DateTime] = None) -> None:
+ def update_offer_rates(
+ self, market: "OneSidedMarket", updated_rate: float, time_slot: Optional[DateTime] = None
+ ) -> None:
"""Update the total price of all offers in the specified market based on their new rate."""
if market.id not in self.offers.open.values():
return
@@ -685,12 +771,14 @@ def update_offer_rates(self, market: "OneSidedMarket", updated_rate: float,
new_offer = market.offer(
updated_price,
offer.energy,
- TraderDetails(self.owner.name,
- self.owner.uuid,
- offer.seller.origin,
- offer.seller.origin_uuid),
+ TraderDetails(
+ self.owner.name,
+ self.owner.uuid,
+ offer.seller.origin,
+ offer.seller.origin_uuid,
+ ),
original_price=updated_price,
- time_slot=offer.time_slot or market.time_slot or time_slot
+ time_slot=offer.time_slot or market.time_slot or time_slot,
)
self.offers.replace(offer, new_offer, market.id)
except MarketException:
@@ -705,6 +793,7 @@ class BidEnabledStrategy(BaseStrategy):
Base strategy for all areas / assets that are eligible to post bids and interact with a
two sided market
"""
+
def __init__(self):
super().__init__()
self._bids = {}
@@ -733,8 +822,13 @@ def _remove_existing_bids(self, market: MarketBase, time_slot: DateTime) -> None
# pylint: disable=too-many-arguments
def post_bid(
- self, market: MarketBase, price: float, energy: float, replace_existing: bool = True,
- time_slot: Optional[DateTime] = None) -> Bid:
+ self,
+ market: MarketBase,
+ price: float,
+ energy: float,
+ replace_existing: bool = True,
+ time_slot: Optional[DateTime] = None,
+ ) -> Bid:
"""
Post bid to a specified market.
Args:
@@ -755,17 +849,16 @@ def post_bid(
bid = market.bid(
price,
energy,
- TraderDetails(
- self.owner.name, self.owner.uuid,
- self.owner.name, self.owner.uuid),
+ TraderDetails(self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid),
original_price=price,
- time_slot=time_slot or market.time_slot
+ time_slot=time_slot or market.time_slot,
)
self.add_bid_to_posted(market.id, bid)
return bid
- def update_bid_rates(self, market: "TwoSidedMarket", updated_rate: float,
- time_slot: Optional[DateTime] = None) -> None:
+ def update_bid_rates(
+ self, market: "TwoSidedMarket", updated_rate: float, time_slot: Optional[DateTime] = None
+ ) -> None:
"""Replace the rate of all bids in the market slot with the given updated rate."""
for bid in self.get_posted_bids(market, time_slot):
if abs(bid.energy_rate - updated_rate) <= FLOATING_POINT_TOLERANCE:
@@ -773,14 +866,24 @@ def update_bid_rates(self, market: "TwoSidedMarket", updated_rate: float,
assert bid.buyer.name == self.owner.name
self.remove_bid_from_pending(market.id, bid.id)
- self.post_bid(market, bid.energy * updated_rate,
- bid.energy, replace_existing=False,
- time_slot=bid.time_slot)
+ self.post_bid(
+ market,
+ bid.energy * updated_rate,
+ bid.energy,
+ replace_existing=False,
+ time_slot=bid.time_slot,
+ )
# pylint: disable=too-many-arguments
- def can_bid_be_posted(self, bid_energy: float, bid_price: float, required_energy_kWh: float,
- market: "TwoSidedMarket", replace_existing: bool = False,
- time_slot: Optional[DateTime] = None) -> bool:
+ def can_bid_be_posted(
+ self,
+ bid_energy: float,
+ bid_price: float,
+ required_energy_kWh: float,
+ market: "TwoSidedMarket",
+ replace_existing: bool = False,
+ time_slot: Optional[DateTime] = None,
+ ) -> bool:
"""Check if a bid can be posted to the market"""
if replace_existing:
@@ -788,13 +891,17 @@ def can_bid_be_posted(self, bid_energy: float, bid_price: float, required_energy
else:
posted_bid_energy = self.posted_bid_energy(market.id, time_slot)
- total_posted_energy = (bid_energy + posted_bid_energy)
+ total_posted_energy = bid_energy + posted_bid_energy
return total_posted_energy <= required_energy_kWh and bid_price >= 0.0
- def can_settlement_bid_be_posted(self, bid_energy: float, bid_price: float,
- market: "TwoSidedMarket",
- replace_existing: bool = False) -> bool:
+ def can_settlement_bid_be_posted(
+ self,
+ bid_energy: float,
+ bid_price: float,
+ market: "TwoSidedMarket",
+ replace_existing: bool = False,
+ ) -> bool:
"""
Checks whether a bid can be posted to the settlement market
:param bid_energy: Energy of the bid that we want to post
@@ -808,8 +915,13 @@ def can_settlement_bid_be_posted(self, bid_energy: float, bid_price: float,
return False
unsettled_energy_kWh = self.state.get_unsettled_deviation_kWh(market.time_slot)
return self.can_bid_be_posted(
- bid_energy, bid_price, unsettled_energy_kWh, market, time_slot=market.time_slot,
- replace_existing=replace_existing)
+ bid_energy,
+ bid_price,
+ unsettled_energy_kWh,
+ market,
+ time_slot=market.time_slot,
+ replace_existing=replace_existing,
+ )
def is_bid_posted(self, market: "TwoSidedMarket", bid_id: str) -> bool:
"""Check if bid is posted to the market"""
@@ -828,19 +940,25 @@ def posted_bid_energy(self, market_id: str, time_slot: Optional[DateTime] = None
"""
if market_id not in self._bids:
return 0.0
- return sum(b.energy
- for b in self._bids[market_id]
- if time_slot is None or b.time_slot == time_slot)
+ return sum(
+ b.energy
+ for b in self._bids[market_id]
+ if time_slot is None or b.time_slot == time_slot
+ )
def _traded_bid_energy(self, market_id: str, time_slot: Optional[DateTime] = None) -> float:
- return sum(b.energy
- for b in self._get_traded_bids_from_market(market_id)
- if time_slot is None or b.time_slot == time_slot)
+ return sum(
+ b.energy
+ for b in self._get_traded_bids_from_market(market_id)
+ if time_slot is None or b.time_slot == time_slot
+ )
def _traded_bid_costs(self, market_id: str, time_slot: Optional[DateTime] = None) -> float:
- return sum(b.price
- for b in self._get_traded_bids_from_market(market_id)
- if time_slot is None or b.time_slot == time_slot)
+ return sum(
+ b.price
+ for b in self._get_traded_bids_from_market(market_id)
+ if time_slot is None or b.time_slot == time_slot
+ )
def remove_bid_from_pending(self, market_id: str, bid_id: str = None) -> List[str]:
"""Remove bid from pending bids dict"""
@@ -855,13 +973,14 @@ def remove_bid_from_pending(self, market_id: str, bid_id: str = None) -> List[st
for b_id in deleted_bid_ids:
if b_id in market.bids.keys():
market.delete_bid(b_id)
- self._bids[market.id] = [bid for bid in self.get_posted_bids(market)
- if bid.id not in deleted_bid_ids]
+ self._bids[market.id] = [
+ bid for bid in self.get_posted_bids(market) if bid.id not in deleted_bid_ids
+ ]
return deleted_bid_ids
def add_bid_to_posted(self, market_id: str, bid: Bid) -> None:
"""Add bid to posted bids dict"""
- if market_id not in self._bids.keys():
+ if market_id not in self._bids:
self._bids[market_id] = []
self._bids[market_id].append(bid)
@@ -885,11 +1004,20 @@ def are_bids_posted(self, market_id: str, time_slot: DateTime = None) -> bool:
# time_slot is empty when called for spot markets, where we can retrieve the bids for a
# time_slot only by the market_id. For the future markets, the time_slot needs to be
# defined for the correct bid selection.
- return len([bid for bid in self._bids[market_id]
- if time_slot is None or bid.time_slot == time_slot]) > 0
+ return (
+ len(
+ [
+ bid
+ for bid in self._bids[market_id]
+ if time_slot is None or bid.time_slot == time_slot
+ ]
+ )
+ > 0
+ )
- def post_first_bid(self, market: "MarketBase", energy_Wh: float,
- initial_energy_rate: float) -> Optional[Bid]:
+ def post_first_bid(
+ self, market: "MarketBase", energy_Wh: float, initial_energy_rate: float
+ ) -> Optional[Bid]:
"""Post first and only bid for the strategy. Will fail if another bid already exists."""
# It will be safe to remove this check once we remove the event_market_cycle being
# called twice, but still it is nice to have it here as a precaution. In general, there
@@ -897,8 +1025,10 @@ def post_first_bid(self, market: "MarketBase", energy_Wh: float,
# it needs to be updated. If this check is not there, the market cycle event will post
# one bid twice, which actually happens on the very first market slot cycle.
if any(bid.buyer.name == self.owner.name for bid in market.get_bids().values()):
- self.owner.log.debug("There is already another bid posted on the market, therefore"
- " do not repost another first bid.")
+ self.owner.log.debug(
+ "There is already another bid posted on the market, therefore"
+ " do not repost another first bid."
+ )
return None
return self.post_bid(
market,
@@ -907,18 +1037,22 @@ def post_first_bid(self, market: "MarketBase", energy_Wh: float,
)
def get_posted_bids(
- self, market: "MarketBase", time_slot: Optional[DateTime] = None) -> List[Bid]:
+ self, market: "MarketBase", time_slot: Optional[DateTime] = None
+ ) -> List[Bid]:
"""Get list of posted bids from a market"""
if market.id not in self._bids:
return []
return [b for b in self._bids[market.id] if time_slot is None or b.time_slot == time_slot]
def _assert_bid_can_be_posted_on_market(self, market_id):
- assert (ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.TWO_SIDED.value or
- self.area.is_market_future(market_id) or
- self.area.is_market_settlement(market_id)), (
+ assert (
+ ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.TWO_SIDED.value
+ or self.area.is_market_future(market_id)
+ or self.area.is_market_settlement(market_id)
+ ), (
"Invalid state, cannot receive a bid if single sided market is globally configured or "
- "if it is not a future or settlement market bid.")
+ "if it is not a future or settlement market bid."
+ )
def event_bid_deleted(self, *, market_id: str, bid: Bid) -> None:
self._assert_bid_can_be_posted_on_market(market_id)
@@ -928,8 +1062,9 @@ def event_bid_deleted(self, *, market_id: str, bid: Bid) -> None:
self.remove_bid_from_pending(market_id, bid.id)
# pylint: disable=unused-argument
- def event_bid_split(self, *, market_id: str, original_bid: Bid, accepted_bid: Bid,
- residual_bid: Bid) -> None:
+ def event_bid_split(
+ self, *, market_id: str, original_bid: Bid, accepted_bid: Bid, residual_bid: Bid
+ ) -> None:
self._assert_bid_can_be_posted_on_market(market_id)
if accepted_bid.buyer.name != self.owner.name:
@@ -958,8 +1093,7 @@ def _delete_past_bids(self, existing_bids: Dict) -> Dict:
updated_bids_dict = {}
for market_id, bids in existing_bids.items():
if market_id == self.area.future_markets.id:
- updated_bids_dict.update(
- {market_id: self._get_future_bids_from_list(bids)})
+ updated_bids_dict.update({market_id: self._get_future_bids_from_list(bids)})
return updated_bids_dict
def event_market_cycle(self) -> None:
@@ -976,8 +1110,11 @@ def assert_if_trade_bid_price_is_too_high(self, market: "MarketBase", trade: "Tr
the bid.
"""
if trade.is_bid_trade and trade.buyer.name == self.owner.name:
- bid = [bid for bid in self.get_posted_bids(market)
- if bid.id == trade.match_details["bid"].id]
+ bid = [
+ bid
+ for bid in self.get_posted_bids(market)
+ if bid.id == trade.match_details["bid"].id
+ ]
if not bid:
return
assert trade.trade_rate <= bid[0].energy_rate + FLOATING_POINT_TOLERANCE
diff --git a/src/gsy_e/models/strategy/external_strategies/load.py b/src/gsy_e/models/strategy/external_strategies/load.py
index c61a280123..f57ea4d91f 100644
--- a/src/gsy_e/models/strategy/external_strategies/load.py
+++ b/src/gsy_e/models/strategy/external_strategies/load.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import json
import logging
from typing import TYPE_CHECKING, Callable, Dict, List, Union
@@ -25,10 +26,16 @@
from gsy_e.gsy_e_core.exceptions import GSyException
from gsy_e.models.strategy.energy_parameters.load import (
- LoadProfileForecastEnergyParams, LoadHoursForecastEnergyParams)
-from gsy_e.models.strategy.external_strategies import (CommandTypeNotSupported, ExternalMixin,
- ExternalStrategyConnectionManager,
- IncomingRequest, OrderCanNotBePosted)
+ LoadProfileForecastEnergyParams,
+ LoadHoursForecastEnergyParams,
+)
+from gsy_e.models.strategy.external_strategies import (
+ CommandTypeNotSupported,
+ ExternalMixin,
+ ExternalStrategyConnectionManager,
+ IncomingRequest,
+ OrderCanNotBePosted,
+)
from gsy_e.models.strategy.external_strategies.forecast_mixin import ForecastExternalMixin
from gsy_e.models.strategy.load_hours import LoadHoursStrategy
from gsy_e.models.strategy.predefined_load import DefinedLoadStrategy
@@ -58,11 +65,12 @@ class LoadExternalMixin(ExternalMixin):
@property
def channel_dict(self) -> Dict:
"""Bid-related Redis API channels."""
- return {**super().channel_dict,
- self.channel_names.bid: self.bid,
- self.channel_names.delete_bid: self.delete_bid,
- self.channel_names.list_bids: self.list_bids,
- }
+ return {
+ **super().channel_dict,
+ self.channel_names.bid: self.bid,
+ self.channel_names.delete_bid: self.delete_bid,
+ self.channel_names.list_bids: self.list_bids,
+ }
def filtered_market_bids(self, market: "TwoSidedMarket") -> List[Dict]:
"""
@@ -77,7 +85,8 @@ def filtered_market_bids(self, market: "TwoSidedMarket") -> List[Dict]:
return [
{"id": bid.id, "price": bid.price, "energy": bid.energy}
for _, bid in market.get_bids().items()
- if bid.buyer.name == self.device.name]
+ if bid.buyer.name == self.device.name
+ ]
def event_activate(self, **kwargs):
"""Activate the device."""
@@ -89,26 +98,31 @@ def list_bids(self, payload: Dict) -> None:
self._get_transaction_id(payload)
response_channel = self.channel_names.list_bids_response
if not ExternalStrategyConnectionManager.check_for_connected_and_reply(
- self.redis, response_channel, self.connected):
+ self.redis, response_channel, self.connected
+ ):
return
arguments = json.loads(payload["data"])
- self.pending_requests.append(
- IncomingRequest("list_bids", arguments, response_channel))
+ self.pending_requests.append(IncomingRequest("list_bids", arguments, response_channel))
def _list_bids_impl(self, arguments: Dict, response_channel: str) -> None:
"""Implementation for the list_bids callback, publish this device bids."""
try:
market = self._get_market_from_command_argument(arguments)
response = {
- "command": "list_bids", "status": "ready",
- "bid_list": self.filtered_market_bids(market),
- "transaction_id": arguments.get("transaction_id")}
+ "command": "list_bids",
+ "status": "ready",
+ "bid_list": self.filtered_market_bids(market),
+ "transaction_id": arguments.get("transaction_id"),
+ }
except GSyException:
error_message = f"Error when handling list bids on area {self.device.name}"
logger.exception(error_message)
- response = {"command": "list_bids", "status": "error",
- "error_message": error_message,
- "transaction_id": arguments.get("transaction_id")}
+ response = {
+ "command": "list_bids",
+ "status": "error",
+ "error_message": error_message,
+ "transaction_id": arguments.get("transaction_id"),
+ }
self.redis.publish_json(response_channel, response)
def delete_bid(self, payload: Dict) -> None:
@@ -116,7 +130,8 @@ def delete_bid(self, payload: Dict) -> None:
transaction_id = self._get_transaction_id(payload)
response_channel = self.channel_names.delete_bid_response
if not ExternalStrategyConnectionManager.check_for_connected_and_reply(
- self.redis, response_channel, self.connected):
+ self.redis, response_channel, self.connected
+ ):
return
try:
arguments = json.loads(payload["data"])
@@ -126,13 +141,17 @@ def delete_bid(self, payload: Dict) -> None:
except (GSyException, json.JSONDecodeError) as exception:
self.redis.publish_json(
response_channel,
- {"command": "bid_delete",
- "error": f"Incorrect delete bid request. Available parameters: (bid)."
- f"Exception: {str(exception)}",
- "transaction_id": transaction_id})
+ {
+ "command": "bid_delete",
+ "error": f"Incorrect delete bid request. Available parameters: (bid)."
+ f"Exception: {str(exception)}",
+ "transaction_id": transaction_id,
+ },
+ )
else:
self.pending_requests.append(
- IncomingRequest("delete_bid", arguments, response_channel))
+ IncomingRequest("delete_bid", arguments, response_channel)
+ )
def _delete_bid_impl(self, arguments: Dict, response_channel: str) -> None:
"""Implementation for the delete_bid callback, delete the received bid from market."""
@@ -140,47 +159,60 @@ def _delete_bid_impl(self, arguments: Dict, response_channel: str) -> None:
market = self._get_market_from_command_argument(arguments)
to_delete_bid_id = arguments.get("bid")
deleted_bids = self.remove_bid_from_pending(market.id, bid_id=to_delete_bid_id)
- response = {"command": "bid_delete", "status": "ready", "deleted_bids": deleted_bids,
- "transaction_id": arguments.get("transaction_id")}
+ response = {
+ "command": "bid_delete",
+ "status": "ready",
+ "deleted_bids": deleted_bids,
+ "transaction_id": arguments.get("transaction_id"),
+ }
except GSyException:
- error_message = (f"Error when handling bid delete on area {self.device.name}: "
- f"Bid Arguments: {arguments}, "
- "Bid does not exist on the current market.")
+ error_message = (
+ f"Error when handling bid delete on area {self.device.name}: "
+ f"Bid Arguments: {arguments}, "
+ "Bid does not exist on the current market."
+ )
logger.exception(error_message)
- response = {"command": "bid_delete", "status": "error",
- "error_message": error_message,
- "transaction_id": arguments.get("transaction_id")}
+ response = {
+ "command": "bid_delete",
+ "status": "error",
+ "error_message": error_message,
+ "transaction_id": arguments.get("transaction_id"),
+ }
self.redis.publish_json(response_channel, response)
def bid(self, payload: Dict) -> None:
"""Callback for bid Redis endpoint."""
transaction_id = self._get_transaction_id(payload)
required_args = {"price", "energy", "transaction_id"}
- allowed_args = required_args.union({"replace_existing",
- "time_slot",
- "attributes",
- "requirements"})
+ allowed_args = required_args.union(
+ {"replace_existing", "time_slot", "attributes", "requirements"}
+ )
response_channel = self.channel_names.bid_response
if not ExternalStrategyConnectionManager.check_for_connected_and_reply(
- self.redis, response_channel, self.connected):
+ self.redis, response_channel, self.connected
+ ):
return
arguments = json.loads(payload["data"])
if ( # Check that all required arguments have been provided
- all(arg in arguments.keys() for arg in required_args)
- # Check that every provided argument is allowed
- and all(arg in allowed_args for arg in arguments.keys())):
- self.pending_requests.append(
- IncomingRequest("bid", arguments, response_channel))
+ all(arg in arguments.keys() for arg in required_args)
+ # Check that every provided argument is allowed
+ and all(arg in allowed_args for arg in arguments.keys())
+ ):
+ self.pending_requests.append(IncomingRequest("bid", arguments, response_channel))
else:
self.redis.publish_json(
response_channel,
- {"command": "bid",
- "error": (
- "Incorrect bid request. ",
- f"Required parameters: {required_args}"
- f"Available parameters: {allowed_args}."),
- "transaction_id": transaction_id})
+ {
+ "command": "bid",
+ "error": (
+ "Incorrect bid request. ",
+ f"Required parameters: {required_args}"
+ f"Available parameters: {allowed_args}.",
+ ),
+ "transaction_id": transaction_id,
+ },
+ )
def _bid_impl(self, arguments: Dict, bid_response_channel: str) -> None:
"""Implementation for the bid callback, post the bid in the market."""
@@ -190,7 +222,8 @@ def _bid_impl(self, arguments: Dict, bid_response_channel: str) -> None:
if filtered_fields:
response_message = (
"The following arguments are not supported for this market and have been "
- f"removed from your order: {filtered_fields}.")
+ f"removed from your order: {filtered_fields}."
+ )
market = self._get_market_from_command_argument(arguments)
replace_existing = arguments.get("replace_existing", True)
@@ -199,27 +232,33 @@ def _bid_impl(self, arguments: Dict, bid_response_channel: str) -> None:
arguments["price"],
self.get_energy_requirement_kWh_from_market(market),
market,
- replace_existing=replace_existing)
+ replace_existing=replace_existing,
+ )
bid = self.post_bid(
- market,
- arguments["price"],
- arguments["energy"],
- replace_existing=replace_existing)
+ market, arguments["price"], arguments["energy"], replace_existing=replace_existing
+ )
response = {
- "command": "bid", "status": "ready",
- "bid": bid.to_json_string(),
- "market_type": market.type_name,
- "transaction_id": arguments.get("transaction_id"),
- "message": response_message}
+ "command": "bid",
+ "status": "ready",
+ "bid": bid.to_json_string(),
+ "market_type": market.type_name,
+ "transaction_id": arguments.get("transaction_id"),
+ "message": response_message,
+ }
except (AssertionError, GSyException):
- error_message = (f"Error when handling bid create on area {self.device.name}: "
- f"Bid Arguments: {arguments}")
+ error_message = (
+ f"Error when handling bid create on area {self.device.name}: "
+ f"Bid Arguments: {arguments}"
+ )
logger.exception(error_message)
- response = {"command": "bid", "status": "error",
- "error_message": error_message,
- "market_type": market.type_name,
- "transaction_id": arguments.get("transaction_id")}
+ response = {
+ "command": "bid",
+ "status": "error",
+ "error_message": error_message,
+ "market_type": market.type_name,
+ "transaction_id": arguments.get("transaction_id"),
+ }
self.redis.publish_json(bid_response_channel, response)
@property
@@ -227,8 +266,10 @@ def _device_info_dict(self) -> Dict:
"""Return the asset info."""
return {
**super()._device_info_dict,
- "energy_requirement_kWh":
- self.state.get_energy_requirement_Wh(self.spot_market.time_slot) / 1000.0,
+ "energy_requirement_kWh": self.state.get_energy_requirement_Wh(
+ self.spot_market.time_slot
+ )
+ / 1000.0,
"energy_active_in_bids": self.posted_bid_energy(self.spot_market.id),
"energy_traded": self.energy_traded(self.spot_market.id),
"total_cost": self.energy_traded_costs(self.spot_market.id),
@@ -284,25 +325,30 @@ def _offer_aggregator(self, arguments: Dict) -> Dict:
market = self._get_market_from_command_argument(arguments)
if self.area.is_market_settlement(market.id):
if not self.state.can_post_settlement_offer(market.time_slot):
- raise OrderCanNotBePosted("The load did not consume too much energy, ",
- "settlement offer can not be posted")
- response = (
- self._offer_aggregator_impl(
- arguments, market, self._get_time_slot_from_external_arguments(arguments),
- self.state.get_unsettled_deviation_kWh(
- market.time_slot)))
+ raise OrderCanNotBePosted(
+ "The load did not consume too much energy, ",
+ "settlement offer can not be posted",
+ )
+ response = self._offer_aggregator_impl(
+ arguments,
+ market,
+ self._get_time_slot_from_external_arguments(arguments),
+ self.state.get_unsettled_deviation_kWh(market.time_slot),
+ )
else:
raise CommandTypeNotSupported("Offer not supported for Loads on spot markets.")
except (OrderCanNotBePosted, CommandTypeNotSupported) as ex:
response = {
- "command": "offer", "status": "error",
+ "command": "offer",
+ "status": "error",
"market_type": market.type_name,
"area_uuid": self.device.uuid,
"error_message": "Error when handling offer create "
- f"on area {self.device.name} with arguments {arguments}:"
- f"{ex}",
- "transaction_id": arguments.get("transaction_id")}
+ f"on area {self.device.name} with arguments {arguments}:"
+ f"{ex}",
+ "transaction_id": arguments.get("transaction_id"),
+ }
return response
@@ -312,34 +358,48 @@ def _bid_aggregator(self, arguments: Dict) -> Dict:
market = self._get_market_from_command_argument(arguments)
if self.area.is_market_settlement(market.id):
if not self.state.can_post_settlement_bid(market.time_slot):
- raise OrderCanNotBePosted("The load did not consume to little energy, "
- "settlement bid can not be posted.")
+ raise OrderCanNotBePosted(
+ "The load did not consume to little energy, "
+ "settlement bid can not be posted."
+ )
required_energy_kWh = self.state.get_unsettled_deviation_kWh(market.time_slot)
elif self.area.is_market_future(market.id):
- required_energy_kWh = self.state.get_energy_requirement_Wh(
- str_to_pendulum_datetime(arguments["time_slot"])) / 1000.
+ required_energy_kWh = (
+ self.state.get_energy_requirement_Wh(
+ str_to_pendulum_datetime(arguments["time_slot"])
+ )
+ / 1000.0
+ )
elif self.area.is_market_spot(market.id):
required_energy_kWh = (
- self.state.get_energy_requirement_Wh(market.time_slot) / 1000.)
+ self.state.get_energy_requirement_Wh(market.time_slot) / 1000.0
+ )
else:
- logger.debug("The order cannot be posted on the market. "
- "(arguments: %s, market_id: %s", arguments, market.id)
+ logger.debug(
+ "The order cannot be posted on the market (arguments: %s, market_id: %s)",
+ arguments,
+ market.id,
+ )
raise OrderCanNotBePosted("The order cannot be posted on the market.")
- response = (
- self._bid_aggregator_impl(arguments, market,
- self._get_time_slot_from_external_arguments(arguments),
- required_energy_kWh))
+ response = self._bid_aggregator_impl(
+ arguments,
+ market,
+ self._get_time_slot_from_external_arguments(arguments),
+ required_energy_kWh,
+ )
except OrderCanNotBePosted as ex:
response = {
- "command": "offer", "status": "error",
+ "command": "offer",
+ "status": "error",
"market_type": market.type_name,
"area_uuid": self.device.uuid,
"error_message": "Error when handling bid create "
- f"on area {self.device.name} with arguments {arguments}:"
- f"{ex}",
- "transaction_id": arguments.get("transaction_id")}
+ f"on area {self.device.name} with arguments {arguments}:"
+ f"{ex}",
+ "transaction_id": arguments.get("transaction_id"),
+ }
return response
@@ -348,21 +408,25 @@ def _delete_bid_aggregator(self, arguments: Dict) -> Dict:
try:
market = self._get_market_from_command_argument(arguments)
to_delete_bid_id = arguments.get("bid")
- deleted_bids = (
- self.remove_bid_from_pending(market.id, bid_id=to_delete_bid_id))
+ deleted_bids = self.remove_bid_from_pending(market.id, bid_id=to_delete_bid_id)
response = {
- "command": "bid_delete", "status": "ready", "deleted_bids": deleted_bids,
+ "command": "bid_delete",
+ "status": "ready",
+ "deleted_bids": deleted_bids,
"area_uuid": self.device.uuid,
- "transaction_id": arguments.get("transaction_id")}
+ "transaction_id": arguments.get("transaction_id"),
+ }
except GSyException:
logger.exception("Error when handling delete bid on area %s", self.device.name)
response = {
- "command": "bid_delete", "status": "error",
+ "command": "bid_delete",
+ "status": "error",
"area_uuid": self.device.uuid,
"error_message": "Error when handling bid delete "
- f"on area {self.device.name} with arguments {arguments}. "
- "Bid does not exist on the current market.",
- "transaction_id": arguments.get("transaction_id")}
+ f"on area {self.device.name} with arguments {arguments}. "
+ "Bid does not exist on the current market.",
+ "transaction_id": arguments.get("transaction_id"),
+ }
return response
def _list_bids_aggregator(self, arguments: Dict) -> Dict:
@@ -370,17 +434,21 @@ def _list_bids_aggregator(self, arguments: Dict) -> Dict:
try:
market = self._get_market_from_command_argument(arguments)
response = {
- "command": "list_bids", "status": "ready",
+ "command": "list_bids",
+ "status": "ready",
"bid_list": self.filtered_market_bids(market),
"area_uuid": self.device.uuid,
- "transaction_id": arguments.get("transaction_id")}
+ "transaction_id": arguments.get("transaction_id"),
+ }
except GSyException:
logger.exception("Error when handling list bids on area %s", self.device.name)
response = {
- "command": "list_bids", "status": "error",
+ "command": "list_bids",
+ "status": "error",
"area_uuid": self.device.uuid,
"error_message": f"Error when listing bids on area {self.device.name}.",
- "transaction_id": arguments.get("transaction_id")}
+ "transaction_id": arguments.get("transaction_id"),
+ }
return response
@@ -394,7 +462,7 @@ class LoadProfileExternalStrategy(LoadExternalMixin, DefinedLoadStrategy):
class LoadForecastExternalStrategyMixin(ForecastExternalMixin):
"""
- Strategy responsible for reading forecast and measurement consumption data via hardware API
+ Strategy responsible for reading forecast and measurement consumption data via hardware API
"""
def update_energy_forecast(self) -> None:
@@ -422,82 +490,113 @@ def _set_energy_measurement_of_last_market(self):
class LoadProfileForecastExternalStrategy(
- LoadForecastExternalStrategyMixin, LoadProfileExternalStrategy):
+ LoadForecastExternalStrategyMixin, LoadProfileExternalStrategy
+):
"""
- Strategy responsible for reading forecast and measurement consumption data via hardware
- API. In case the hardware API is not available the normal profile strategy will be used
- instead.
+ Strategy responsible for reading forecast and measurement consumption data via hardware
+ API. In case the hardware API is not available the normal profile strategy will be used
+ instead.
"""
+
# pylint: disable=too-many-arguments
- def __init__(self, fit_to_limit=True, energy_rate_increase_per_update=None,
- update_interval=None,
- initial_buying_rate: Union[float, dict, str] =
- ConstSettings.LoadSettings.BUYING_RATE_RANGE.initial,
- final_buying_rate: Union[float, dict, str] =
- ConstSettings.LoadSettings.BUYING_RATE_RANGE.final,
- balancing_energy_ratio: tuple =
- (ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
- ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO),
- use_market_maker_rate: bool = False,
- daily_load_profile=None,
- daily_load_profile_uuid=None):
+ def __init__(
+ self,
+ fit_to_limit=True,
+ energy_rate_increase_per_update=None,
+ update_interval=None,
+ initial_buying_rate: Union[
+ float, dict, str
+ ] = ConstSettings.LoadSettings.BUYING_RATE_RANGE.initial,
+ final_buying_rate: Union[
+ float, dict, str
+ ] = ConstSettings.LoadSettings.BUYING_RATE_RANGE.final,
+ balancing_energy_ratio: tuple = (
+ ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
+ ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO,
+ ),
+ use_market_maker_rate: bool = False,
+ daily_load_profile=None,
+ daily_load_profile_uuid=None,
+ **kwargs,
+ ):
"""
Constructor of LoadForecastStrategy
"""
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
+
if update_interval is None:
update_interval = duration(
- minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL)
-
- super().__init__(daily_load_profile=None,
- fit_to_limit=fit_to_limit,
- energy_rate_increase_per_update=energy_rate_increase_per_update,
- update_interval=update_interval,
- final_buying_rate=final_buying_rate,
- initial_buying_rate=initial_buying_rate,
- balancing_energy_ratio=balancing_energy_ratio,
- use_market_maker_rate=use_market_maker_rate,
- daily_load_profile_uuid=daily_load_profile_uuid,
- )
+ minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL
+ )
+
+ super().__init__(
+ daily_load_profile=None,
+ fit_to_limit=fit_to_limit,
+ energy_rate_increase_per_update=energy_rate_increase_per_update,
+ update_interval=update_interval,
+ final_buying_rate=final_buying_rate,
+ initial_buying_rate=initial_buying_rate,
+ balancing_energy_ratio=balancing_energy_ratio,
+ use_market_maker_rate=use_market_maker_rate,
+ daily_load_profile_uuid=daily_load_profile_uuid,
+ )
self._energy_params = LoadProfileForecastEnergyParams(
- daily_load_profile, daily_load_profile_uuid)
+ daily_load_profile, daily_load_profile_uuid
+ )
class LoadHoursForecastExternalStrategy(
- LoadForecastExternalStrategyMixin, LoadHoursExternalStrategy):
+ LoadForecastExternalStrategyMixin, LoadHoursExternalStrategy
+):
"""
- Strategy responsible for reading forecast and measurement consumption data via hardware
- API. In case the hardware API is not available the normal load hours strategy will be used
- instead.
+ Strategy responsible for reading forecast and measurement consumption data via hardware
+ API. In case the hardware API is not available the normal load hours strategy will be used
+ instead.
"""
+
# pylint: disable=too-many-arguments,unused-argument
- def __init__(self, fit_to_limit=True, energy_rate_increase_per_update=None,
- update_interval=None,
- initial_buying_rate: Union[float, dict, str] =
- ConstSettings.LoadSettings.BUYING_RATE_RANGE.initial,
- final_buying_rate: Union[float, dict, str] =
- ConstSettings.LoadSettings.BUYING_RATE_RANGE.final,
- balancing_energy_ratio: tuple =
- (ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
- ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO),
- use_market_maker_rate: bool = False,
- avg_power_W=0,
- hrs_of_day=None):
+ def __init__(
+ self,
+ fit_to_limit=True,
+ energy_rate_increase_per_update=None,
+ update_interval=None,
+ initial_buying_rate: Union[
+ float, dict, str
+ ] = ConstSettings.LoadSettings.BUYING_RATE_RANGE.initial,
+ final_buying_rate: Union[
+ float, dict, str
+ ] = ConstSettings.LoadSettings.BUYING_RATE_RANGE.final,
+ balancing_energy_ratio: tuple = (
+ ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
+ ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO,
+ ),
+ use_market_maker_rate: bool = False,
+ avg_power_W=0,
+ hrs_of_day=None,
+ **kwargs,
+ ):
"""
Constructor of LoadForecastStrategy
"""
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
+
if update_interval is None:
update_interval = duration(
- minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL)
-
- super().__init__(None,
- fit_to_limit=fit_to_limit,
- energy_rate_increase_per_update=energy_rate_increase_per_update,
- update_interval=update_interval,
- final_buying_rate=final_buying_rate,
- initial_buying_rate=initial_buying_rate,
- balancing_energy_ratio=balancing_energy_ratio,
- use_market_maker_rate=use_market_maker_rate)
-
- self._energy_params = LoadHoursForecastEnergyParams(
- avg_power_W, hrs_of_day)
+ minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL
+ )
+
+ super().__init__(
+ None,
+ fit_to_limit=fit_to_limit,
+ energy_rate_increase_per_update=energy_rate_increase_per_update,
+ update_interval=update_interval,
+ final_buying_rate=final_buying_rate,
+ initial_buying_rate=initial_buying_rate,
+ balancing_energy_ratio=balancing_energy_ratio,
+ use_market_maker_rate=use_market_maker_rate,
+ )
+
+ self._energy_params = LoadHoursForecastEnergyParams(avg_power_W, hrs_of_day)
diff --git a/src/gsy_e/models/strategy/external_strategies/pv.py b/src/gsy_e/models/strategy/external_strategies/pv.py
index a8b84e90b0..aa521062b6 100644
--- a/src/gsy_e/models/strategy/external_strategies/pv.py
+++ b/src/gsy_e/models/strategy/external_strategies/pv.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import json
import logging
from typing import TYPE_CHECKING, Callable, Dict
@@ -25,9 +26,13 @@
from pendulum import duration
from gsy_e.gsy_e_core.exceptions import GSyException
-from gsy_e.models.strategy.external_strategies import (CommandTypeNotSupported, ExternalMixin,
- ExternalStrategyConnectionManager,
- IncomingRequest, OrderCanNotBePosted)
+from gsy_e.models.strategy.external_strategies import (
+ CommandTypeNotSupported,
+ ExternalMixin,
+ ExternalStrategyConnectionManager,
+ IncomingRequest,
+ OrderCanNotBePosted,
+)
from gsy_e.models.strategy.external_strategies.forecast_mixin import ForecastExternalMixin
from gsy_e.models.strategy.predefined_pv import PVPredefinedStrategy, PVUserProfileStrategy
from gsy_e.models.strategy.pv import PVStrategy
@@ -45,6 +50,7 @@ class PVExternalMixin(ExternalMixin):
Mixin for enabling an external api for the PV strategies.
Should always be inherited together with a superclass of PVStrategy.
"""
+
state: "PVState"
offers: "Offers"
can_offer_be_posted: Callable
@@ -55,11 +61,12 @@ class PVExternalMixin(ExternalMixin):
@property
def channel_dict(self) -> Dict:
"""Offer-related Redis API channels."""
- return {**super().channel_dict,
- self.channel_names.offer: self.offer,
- self.channel_names.delete_offer: self.delete_offer,
- self.channel_names.list_offers: self.list_offers,
- }
+ return {
+ **super().channel_dict,
+ self.channel_names.offer: self.offer,
+ self.channel_names.delete_offer: self.delete_offer,
+ self.channel_names.list_offers: self.list_offers,
+ }
def event_activate(self, **kwargs) -> None:
"""Activate the device."""
@@ -71,28 +78,36 @@ def list_offers(self, payload: Dict) -> None:
self._get_transaction_id(payload)
response_channel = self.channel_names.list_offers_response
if not ExternalStrategyConnectionManager.check_for_connected_and_reply(
- self.redis, response_channel, self.connected):
+ self.redis, response_channel, self.connected
+ ):
return
arguments = json.loads(payload["data"])
- self.pending_requests.append(
- IncomingRequest("list_offers", arguments, response_channel))
+ self.pending_requests.append(IncomingRequest("list_offers", arguments, response_channel))
def _list_offers_impl(self, arguments: Dict, response_channel: str) -> None:
"""Implementation for the list_offers callback, publish this device offers."""
try:
market = self._get_market_from_command_argument(arguments)
- filtered_offers = [{"id": v.id, "price": v.price, "energy": v.energy}
- for _, v in market.get_offers().items()
- if v.seller.name == self.device.name]
- response = {"command": "list_offers", "status": "ready",
- "offer_list": filtered_offers,
- "transaction_id": arguments.get("transaction_id")}
+ filtered_offers = [
+ {"id": v.id, "price": v.price, "energy": v.energy}
+ for _, v in market.get_offers().items()
+ if v.seller.name == self.device.name
+ ]
+ response = {
+ "command": "list_offers",
+ "status": "ready",
+ "offer_list": filtered_offers,
+ "transaction_id": arguments.get("transaction_id"),
+ }
except GSyException:
error_message = f"Error when handling list offers on area {self.device.name}"
logger.exception(error_message)
- response = {"command": "list_offers", "status": "error",
- "error_message": error_message,
- "transaction_id": arguments.get("transaction_id")}
+ response = {
+ "command": "list_offers",
+ "status": "error",
+ "error_message": error_message,
+ "transaction_id": arguments.get("transaction_id"),
+ }
self.redis.publish_json(response_channel, response)
def delete_offer(self, payload: Dict) -> None:
@@ -100,24 +115,30 @@ def delete_offer(self, payload: Dict) -> None:
transaction_id = self._get_transaction_id(payload)
response_channel = self.channel_names.delete_offer_response
if not ExternalStrategyConnectionManager.check_for_connected_and_reply(
- self.redis, response_channel, self.connected):
+ self.redis, response_channel, self.connected
+ ):
return
try:
arguments = json.loads(payload["data"])
market = self._get_market_from_command_argument(arguments)
if arguments.get("offer") and not self.offers.is_offer_posted(
- market.id, arguments["offer"]):
+ market.id, arguments["offer"]
+ ):
raise GSyException("Offer_id is not associated with any posted offer.")
except (GSyException, json.JSONDecodeError):
logger.exception("Error when handling delete offer request. Payload %s", payload)
self.redis.publish_json(
response_channel,
- {"command": "offer_delete",
- "error": "Incorrect delete offer request. Available parameters: (offer).",
- "transaction_id": transaction_id})
+ {
+ "command": "offer_delete",
+ "error": "Incorrect delete offer request. Available parameters: (offer).",
+ "transaction_id": transaction_id,
+ },
+ )
else:
self.pending_requests.append(
- IncomingRequest("delete_offer", arguments, response_channel))
+ IncomingRequest("delete_offer", arguments, response_channel)
+ )
def _delete_offer_impl(self, arguments: Dict, response_channel: str) -> None:
"""Implementation for the delete_offer callback, delete the received offer from market."""
@@ -125,29 +146,38 @@ def _delete_offer_impl(self, arguments: Dict, response_channel: str) -> None:
market = self._get_market_from_command_argument(arguments)
to_delete_offer_id = arguments.get("offer")
deleted_offers = self.offers.remove_offer_from_cache_and_market(
- market, to_delete_offer_id)
- response = {"command": "offer_delete", "status": "ready",
- "deleted_offers": deleted_offers,
- "transaction_id": arguments.get("transaction_id")}
+ market, to_delete_offer_id
+ )
+ response = {
+ "command": "offer_delete",
+ "status": "ready",
+ "deleted_offers": deleted_offers,
+ "transaction_id": arguments.get("transaction_id"),
+ }
except GSyException:
- error_message = (f"Error when handling offer delete on area {self.device.name}: "
- f"Offer Arguments: {arguments}")
+ error_message = (
+ f"Error when handling offer delete on area {self.device.name}: "
+ f"Offer Arguments: {arguments}"
+ )
logger.exception(error_message)
- response = {"command": "offer_delete", "status": "error",
- "error_message": error_message,
- "transaction_id": arguments.get("transaction_id")}
+ response = {
+ "command": "offer_delete",
+ "status": "error",
+ "error_message": error_message,
+ "transaction_id": arguments.get("transaction_id"),
+ }
self.redis.publish_json(response_channel, response)
def offer(self, payload: Dict) -> None:
"""Callback for offer Redis endpoint."""
transaction_id = self._get_transaction_id(payload)
required_args = {"price", "energy", "transaction_id"}
- allowed_args = required_args.union({"replace_existing",
- "time_slot"})
+ allowed_args = required_args.union({"replace_existing", "time_slot"})
response_channel = self.channel_names.offer_response
if not ExternalStrategyConnectionManager.check_for_connected_and_reply(
- self.redis, response_channel, self.connected):
+ self.redis, response_channel, self.connected
+ ):
return
try:
arguments = json.loads(payload["data"])
@@ -161,15 +191,18 @@ def offer(self, payload: Dict) -> None:
logger.exception("Incorrect offer request. Payload %s.", payload)
self.redis.publish_json(
response_channel,
- {"command": "offer",
- "error": (
- "Incorrect bid request. ",
- f"Required parameters: {required_args}"
- f"Available parameters: {allowed_args}."),
- "transaction_id": transaction_id})
+ {
+ "command": "offer",
+ "error": (
+ "Incorrect bid request. ",
+ f"Required parameters: {required_args}"
+ f"Available parameters: {allowed_args}.",
+ ),
+ "transaction_id": transaction_id,
+ },
+ )
else:
- self.pending_requests.append(
- IncomingRequest("offer", arguments, response_channel))
+ self.pending_requests.append(IncomingRequest("offer", arguments, response_channel))
def _offer_impl(self, arguments: Dict, response_channel: str) -> None:
try:
@@ -180,36 +213,48 @@ def _offer_impl(self, arguments: Dict, response_channel: str) -> None:
arguments["price"],
self.state.get_available_energy_kWh(market.time_slot),
market,
- replace_existing=replace_existing)
+ replace_existing=replace_existing,
+ )
offer_arguments = {
- k: v for k, v in arguments.items() if k not in ["transaction_id", "time_slot"]}
- offer = self.post_offer(
- market, replace_existing=replace_existing, **offer_arguments)
+ k: v for k, v in arguments.items() if k not in ["transaction_id", "time_slot"]
+ }
+ offer = self.post_offer(market, replace_existing=replace_existing, **offer_arguments)
self.redis.publish_json(
response_channel,
- {"command": "offer", "status": "ready",
- "market_type": market.type_name,
- "offer": offer.to_json_string(),
- "transaction_id": arguments.get("transaction_id")})
+ {
+ "command": "offer",
+ "status": "ready",
+ "market_type": market.type_name,
+ "offer": offer.to_json_string(),
+ "transaction_id": arguments.get("transaction_id"),
+ },
+ )
except (AssertionError, GSyException):
- error_message = (f"Error when handling offer create on area {self.device.name}: "
- f"Offer Arguments: {arguments}")
+ error_message = (
+ f"Error when handling offer create on area {self.device.name}: "
+ f"Offer Arguments: {arguments}"
+ )
logger.exception(error_message)
self.redis.publish_json(
response_channel,
- {"command": "offer", "status": "error",
- "market_type": market.type_name,
- "error_message": error_message,
- "transaction_id": arguments.get("transaction_id")})
+ {
+ "command": "offer",
+ "status": "error",
+ "market_type": market.type_name,
+ "error_message": error_message,
+ "transaction_id": arguments.get("transaction_id"),
+ },
+ )
@property
def _device_info_dict(self):
return {
**super()._device_info_dict,
- "available_energy_kWh":
- self.state.get_available_energy_kWh(self.spot_market.time_slot),
+ "available_energy_kWh": self.state.get_available_energy_kWh(
+ self.spot_market.time_slot
+ ),
"energy_active_in_offers": self.offers.open_offer_energy(self.spot_market.id),
"energy_traded": self.energy_traded(self.spot_market.id),
"total_cost": self.energy_traded_costs(self.spot_market.id),
@@ -266,44 +311,56 @@ def _delete_offer_aggregator(self, arguments: Dict) -> Dict:
"""Callback for the delete offer endpoint when sent by aggregator."""
market = self._get_market_from_command_argument(arguments)
if arguments.get("offer") and not self.offers.is_offer_posted(
- market.id, arguments["offer"]):
+ market.id, arguments["offer"]
+ ):
raise GSyException("Offer_id is not associated with any posted offer.")
try:
to_delete_offer_id = arguments.get("offer")
deleted_offers = self.offers.remove_offer_from_cache_and_market(
- market, to_delete_offer_id)
+ market, to_delete_offer_id
+ )
response = {
- "command": "offer_delete", "status": "ready",
+ "command": "offer_delete",
+ "status": "ready",
"area_uuid": self.device.uuid,
"deleted_offers": deleted_offers,
- "transaction_id": arguments.get("transaction_id")
+ "transaction_id": arguments.get("transaction_id"),
}
except GSyException:
response = {
- "command": "offer_delete", "status": "error",
+ "command": "offer_delete",
+ "status": "error",
"area_uuid": self.device.uuid,
"error_message": f"Error when handling offer delete "
- f"on area {self.device.name} with arguments {arguments}.",
- "transaction_id": arguments.get("transaction_id")}
+ f"on area {self.device.name} with arguments {arguments}.",
+ "transaction_id": arguments.get("transaction_id"),
+ }
return response
def _list_offers_aggregator(self, arguments: Dict) -> Dict:
try:
market = self._get_market_from_command_argument(arguments)
- filtered_offers = [{"id": v.id, "price": v.price, "energy": v.energy}
- for v in market.get_offers().values()
- if v.seller.name == self.device.name]
+ filtered_offers = [
+ {"id": v.id, "price": v.price, "energy": v.energy}
+ for v in market.get_offers().values()
+ if v.seller.name == self.device.name
+ ]
response = {
- "command": "list_offers", "status": "ready", "offer_list": filtered_offers,
+ "command": "list_offers",
+ "status": "ready",
+ "offer_list": filtered_offers,
"area_uuid": self.device.uuid,
- "transaction_id": arguments.get("transaction_id")}
+ "transaction_id": arguments.get("transaction_id"),
+ }
except GSyException:
response = {
- "command": "list_offers", "status": "error",
+ "command": "list_offers",
+ "status": "error",
"area_uuid": self.device.uuid,
"error_message": f"Error when listing offers on area {self.device.name}.",
- "transaction_id": arguments.get("transaction_id")}
+ "transaction_id": arguments.get("transaction_id"),
+ }
return response
def _bid_aggregator(self, arguments: Dict) -> Dict:
@@ -312,26 +369,30 @@ def _bid_aggregator(self, arguments: Dict) -> Dict:
market = self._get_market_from_command_argument(arguments)
if self.area.is_market_settlement(market.id):
if not self.state.can_post_settlement_bid(market.time_slot):
- raise OrderCanNotBePosted("The PV did not produce too little energy, "
- "settlement bid can not be posted.")
- response = (
- self._bid_aggregator_impl(arguments, market,
- self._get_time_slot_from_external_arguments(
- arguments),
- self.state.get_unsettled_deviation_kWh(
- market.time_slot)))
+ raise OrderCanNotBePosted(
+ "The PV did not produce too little energy, "
+ "settlement bid can not be posted."
+ )
+ response = self._bid_aggregator_impl(
+ arguments,
+ market,
+ self._get_time_slot_from_external_arguments(arguments),
+ self.state.get_unsettled_deviation_kWh(market.time_slot),
+ )
else:
raise CommandTypeNotSupported("Bid not supported for PV on spot markets.")
except (OrderCanNotBePosted, CommandTypeNotSupported) as ex:
response = {
- "command": "offer", "status": "error",
+ "command": "offer",
+ "status": "error",
"area_uuid": self.device.uuid,
"market_type": market.type_name,
"error_message": "Error when handling offer create "
- f"on area {self.device.name} with arguments {arguments}:"
- f"{ex}",
- "transaction_id": arguments.get("transaction_id")}
+ f"on area {self.device.name} with arguments {arguments}:"
+ f"{ex}",
+ "transaction_id": arguments.get("transaction_id"),
+ }
return response
@@ -341,33 +402,43 @@ def _offer_aggregator(self, arguments: Dict) -> Dict:
market = self._get_market_from_command_argument(arguments)
if self.area.is_market_settlement(market.id):
if not self.state.can_post_settlement_offer(market.time_slot):
- raise OrderCanNotBePosted("The PV did not produce too much energy, "
- "settlement offer can not be posted.")
+ raise OrderCanNotBePosted(
+ "The PV did not produce too much energy, "
+ "settlement offer can not be posted."
+ )
available_energy_kWh = self.state.get_unsettled_deviation_kWh(market.time_slot)
elif self.area.is_market_future(market.id):
available_energy_kWh = self.state.get_available_energy_kWh(
- str_to_pendulum_datetime(arguments["time_slot"]))
+ str_to_pendulum_datetime(arguments["time_slot"])
+ )
elif self.area.is_market_spot(market.id):
available_energy_kWh = self.state.get_available_energy_kWh(market.time_slot)
else:
- logger.debug("The order cannot be posted on the market. "
- "(arguments: %s, market_id: %s", arguments, market.id)
+ logger.debug(
+ "The order cannot be posted on the market (arguments: %s, market_id: %s)",
+ arguments,
+ market.id,
+ )
raise OrderCanNotBePosted("The order cannot be posted on the market.")
- response = (
- self._offer_aggregator_impl(arguments, market,
- self._get_time_slot_from_external_arguments(arguments),
- available_energy_kWh))
+ response = self._offer_aggregator_impl(
+ arguments,
+ market,
+ self._get_time_slot_from_external_arguments(arguments),
+ available_energy_kWh,
+ )
except OrderCanNotBePosted as ex:
response = {
- "command": "offer", "status": "error",
+ "command": "offer",
+ "status": "error",
"market_type": market.type_name,
"area_uuid": self.device.uuid,
"error_message": "Error when handling offer create "
- f"on area {self.device.name} with arguments {arguments}:"
- f"{ex}",
- "transaction_id": arguments.get("transaction_id")}
+ f"on area {self.device.name} with arguments {arguments}:"
+ f"{ex}",
+ "transaction_id": arguments.get("transaction_id"),
+ }
return response
@@ -391,27 +462,33 @@ class PVForecastExternalStrategy(ForecastExternalMixin, PVPredefinedExternalStra
# pylint: disable=too-many-arguments,unused-argument
def __init__(
- self, panel_count=1,
- initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
- final_selling_rate: float = ConstSettings.PVSettings.SELLING_RATE_RANGE.final,
- fit_to_limit: bool = True,
- update_interval=duration(
- minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL),
- energy_rate_decrease_per_update=None,
- use_market_maker_rate: bool = False,
- cloud_coverage: int = None,
- capacity_kW: float = None
+ self,
+ panel_count=1,
+ initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
+ final_selling_rate: float = ConstSettings.PVSettings.SELLING_RATE_RANGE.final,
+ fit_to_limit: bool = True,
+ update_interval=duration(minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL),
+ energy_rate_decrease_per_update=None,
+ use_market_maker_rate: bool = False,
+ cloud_coverage: int = None,
+ capacity_kW: float = None,
+ **kwargs,
):
"""
Constructor of PVForecastStrategy
"""
- super().__init__(panel_count=panel_count,
- initial_selling_rate=initial_selling_rate,
- final_selling_rate=final_selling_rate,
- fit_to_limit=fit_to_limit,
- update_interval=update_interval,
- energy_rate_decrease_per_update=energy_rate_decrease_per_update,
- use_market_maker_rate=use_market_maker_rate)
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
+
+ super().__init__(
+ panel_count=panel_count,
+ initial_selling_rate=initial_selling_rate,
+ final_selling_rate=final_selling_rate,
+ fit_to_limit=fit_to_limit,
+ update_interval=update_interval,
+ energy_rate_decrease_per_update=energy_rate_decrease_per_update,
+ use_market_maker_rate=use_market_maker_rate,
+ )
def update_energy_forecast(self) -> None:
"""Set energy forecast for future markets."""
@@ -432,6 +509,6 @@ def set_produced_energy_forecast_in_state(self, reconfigure=False) -> None:
def _set_energy_measurement_of_last_market(self):
"""
- Setting energy measurement for the previous slot is already done by
- update_energy_measurement
- """
+ Setting energy measurement for the previous slot is already done by
+ update_energy_measurement
+ """
diff --git a/src/gsy_e/models/strategy/load_hours.py b/src/gsy_e/models/strategy/load_hours.py
index 2073aec5eb..24c1fde44d 100644
--- a/src/gsy_e/models/strategy/load_hours.py
+++ b/src/gsy_e/models/strategy/load_hours.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from collections import namedtuple
from typing import Union, Dict
@@ -24,8 +25,10 @@
from gsy_framework.exceptions import GSyDeviceException
from gsy_framework.read_user_profile import read_arbitrary_profile, InputProfileTypes
from gsy_framework.utils import (
- limit_float_precision, get_from_profile_same_weekday_and_time,
- is_time_slot_in_simulation_duration)
+ limit_float_precision,
+ get_from_profile_same_weekday_and_time,
+ is_time_slot_in_simulation_duration,
+)
from gsy_framework.validators.load_validator import LoadValidator
from numpy import random
from pendulum import duration
@@ -56,21 +59,30 @@ def serialize(self):
**self._energy_params.serialize(),
**self.bid_update.serialize(),
"balancing_energy_ratio": self.balancing_energy_ratio,
- "use_market_maker_rate": self.use_market_maker_rate
+ "use_market_maker_rate": self.use_market_maker_rate,
}
# pylint: disable=too-many-arguments
- def __init__(self, avg_power_W, hrs_of_day=None,
- fit_to_limit=True, energy_rate_increase_per_update=None,
- update_interval=None,
- initial_buying_rate: Union[float, Dict, str] =
- ConstSettings.LoadSettings.BUYING_RATE_RANGE.initial,
- final_buying_rate: Union[float, Dict, str] =
- ConstSettings.LoadSettings.BUYING_RATE_RANGE.final,
- balancing_energy_ratio: tuple =
- (ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
- ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO),
- use_market_maker_rate: bool = False):
+ def __init__(
+ self,
+ avg_power_W,
+ hrs_of_day=None,
+ fit_to_limit=True,
+ energy_rate_increase_per_update=None,
+ update_interval=None,
+ initial_buying_rate: Union[
+ float, Dict, str
+ ] = ConstSettings.LoadSettings.BUYING_RATE_RANGE.initial,
+ final_buying_rate: Union[
+ float, Dict, str
+ ] = ConstSettings.LoadSettings.BUYING_RATE_RANGE.final,
+ balancing_energy_ratio: tuple = (
+ ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
+ ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO,
+ ),
+ use_market_maker_rate: bool = False,
+ **kwargs
+ ):
"""
Constructor of LoadHoursStrategy
:param avg_power_W: Power rating of load device
@@ -84,13 +96,20 @@ def __init__(self, avg_power_W, hrs_of_day=None,
:param use_market_maker_rate: If set to True, Load would track its final buying rate
as per utility's trading rate
"""
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
super().__init__()
self._energy_params = LoadHoursEnergyParameters(avg_power_W, hrs_of_day)
self.balancing_energy_ratio = BalancingRatio(*balancing_energy_ratio)
self.use_market_maker_rate = use_market_maker_rate
- self._init_price_update(fit_to_limit, energy_rate_increase_per_update, update_interval,
- initial_buying_rate, final_buying_rate)
+ self._init_price_update(
+ fit_to_limit,
+ energy_rate_increase_per_update,
+ update_interval,
+ initial_buying_rate,
+ final_buying_rate,
+ )
self._calculate_active_markets()
self._cycled_market = set()
@@ -107,46 +126,65 @@ def _create_settlement_market_strategy(cls):
def _create_future_market_strategy(self):
return future_market_strategy_factory(self.asset_type)
- def _init_price_update(self, fit_to_limit, energy_rate_increase_per_update, update_interval,
- initial_buying_rate, final_buying_rate):
+ def _init_price_update(
+ self,
+ fit_to_limit,
+ energy_rate_increase_per_update,
+ update_interval,
+ initial_buying_rate,
+ final_buying_rate,
+ ):
LoadValidator.validate_rate(
fit_to_limit=fit_to_limit,
- energy_rate_increase_per_update=energy_rate_increase_per_update)
+ energy_rate_increase_per_update=energy_rate_increase_per_update,
+ )
if update_interval is None:
update_interval = duration(
- minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL)
+ minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL
+ )
if isinstance(update_interval, int):
update_interval = duration(minutes=update_interval)
BidEnabledStrategy.__init__(self)
self.bid_update = TemplateStrategyBidUpdater(
- initial_rate=initial_buying_rate, final_rate=final_buying_rate,
+ initial_rate=initial_buying_rate,
+ final_rate=final_buying_rate,
fit_to_limit=fit_to_limit,
energy_rate_change_per_update=energy_rate_increase_per_update,
- update_interval=update_interval, rate_limit_object=min)
+ update_interval=update_interval,
+ rate_limit_object=min,
+ )
- def _validate_rates(self, initial_rate, final_rate, energy_rate_change_per_update,
- fit_to_limit):
+ def _validate_rates(
+ self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit
+ ):
# all parameters have to be validated for each time slot starting from the current time
for time_slot in initial_rate.keys():
if not is_time_slot_in_simulation_duration(time_slot, self.area.config):
continue
- if (self.area and
- self.area.current_market
- and time_slot < self.area.current_market.time_slot):
+ if (
+ self.area
+ and self.area.current_market
+ and time_slot < self.area.current_market.time_slot
+ ):
continue
- rate_change = (None if fit_to_limit else
- get_from_profile_same_weekday_and_time(
- energy_rate_change_per_update, time_slot))
+ rate_change = (
+ None
+ if fit_to_limit
+ else get_from_profile_same_weekday_and_time(
+ energy_rate_change_per_update, time_slot
+ )
+ )
LoadValidator.validate_rate(
initial_buying_rate=initial_rate[time_slot],
energy_rate_increase_per_update=rate_change,
final_buying_rate=get_from_profile_same_weekday_and_time(final_rate, time_slot),
- fit_to_limit=fit_to_limit)
+ fit_to_limit=fit_to_limit,
+ )
def event_activate(self, **kwargs):
self._energy_params.event_activate_energy(self.area)
@@ -185,32 +223,37 @@ def _set_energy_measurement_of_last_market(self):
self._energy_params.set_energy_measurement_kWh(self.area.current_market.time_slot)
def _delete_past_state(self):
- if (constants.RETAIN_PAST_MARKET_STRATEGIES_STATE is True or
- self.area.current_market is None):
+ if (
+ constants.RETAIN_PAST_MARKET_STRATEGIES_STATE is True
+ or self.area.current_market is None
+ ):
return
self.state.delete_past_state_values(self.area.current_market.time_slot)
self.bid_update.delete_past_state_values(self.area.current_market.time_slot)
- self._future_market_strategy.delete_past_state_values(
- self.area.current_market.time_slot)
+ self._future_market_strategy.delete_past_state_values(self.area.current_market.time_slot)
def _area_reconfigure_prices(self, **kwargs):
if kwargs.get("initial_buying_rate") is not None:
- initial_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["initial_buying_rate"])
+ initial_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["initial_buying_rate"]
+ )
else:
initial_rate = self.bid_update.initial_rate_profile_buffer
if kwargs.get("final_buying_rate") is not None:
- final_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["final_buying_rate"])
+ final_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["final_buying_rate"]
+ )
else:
final_rate = self.bid_update.final_rate_profile_buffer
if kwargs.get("energy_rate_increase_per_update") is not None:
energy_rate_change_per_update = read_arbitrary_profile(
- InputProfileTypes.IDENTITY, kwargs["energy_rate_increase_per_update"])
+ InputProfileTypes.IDENTITY, kwargs["energy_rate_increase_per_update"]
+ )
else:
- energy_rate_change_per_update = (self.bid_update.
- energy_rate_change_per_update_profile_buffer)
+ energy_rate_change_per_update = (
+ self.bid_update.energy_rate_change_per_update_profile_buffer
+ )
if kwargs.get("fit_to_limit") is not None:
fit_to_limit = kwargs["fit_to_limit"]
else:
@@ -219,7 +262,8 @@ def _area_reconfigure_prices(self, **kwargs):
update_interval = (
duration(minutes=kwargs["update_interval"])
if isinstance(kwargs["update_interval"], int)
- else kwargs["update_interval"])
+ else kwargs["update_interval"]
+ )
else:
update_interval = self.bid_update.update_interval
@@ -227,8 +271,9 @@ def _area_reconfigure_prices(self, **kwargs):
self.use_market_maker_rate = kwargs["use_market_maker_rate"]
try:
- self._validate_rates(initial_rate, final_rate, energy_rate_change_per_update,
- fit_to_limit)
+ self._validate_rates(
+ initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit
+ )
except GSyDeviceException:
self.log.exception("LoadHours._area_reconfigure_prices failed. Exception: ")
return
@@ -238,7 +283,7 @@ def _area_reconfigure_prices(self, **kwargs):
final_rate=final_rate,
energy_rate_change_per_update=energy_rate_change_per_update,
fit_to_limit=fit_to_limit,
- update_interval=update_interval
+ update_interval=update_interval,
)
def area_reconfigure_event(self, *args, **kwargs):
@@ -252,10 +297,12 @@ def event_activate_price(self):
"""Update the strategy prices upon the activation and validate them afterwards."""
self._replace_rates_with_market_maker_rates()
- self._validate_rates(self.bid_update.initial_rate_profile_buffer,
- self.bid_update.final_rate_profile_buffer,
- self.bid_update.energy_rate_change_per_update_profile_buffer,
- self.bid_update.fit_to_limit)
+ self._validate_rates(
+ self.bid_update.initial_rate_profile_buffer,
+ self.bid_update.final_rate_profile_buffer,
+ self.bid_update.energy_rate_change_per_update_profile_buffer,
+ self.bid_update.fit_to_limit,
+ )
@staticmethod
def _find_acceptable_offer(market):
@@ -267,7 +314,8 @@ def _offer_rate_can_be_accepted(self, offer: Offer, market_slot: MarketBase):
max_affordable_offer_rate = self.bid_update.get_updated_rate(market_slot.time_slot)
return (
limit_float_precision(offer.energy_rate)
- <= max_affordable_offer_rate + FLOATING_POINT_TOLERANCE)
+ <= max_affordable_offer_rate + FLOATING_POINT_TOLERANCE
+ )
def _one_sided_market_event_tick(self, market, offer=None):
if not self.state.can_buy_more_energy(market.time_slot):
@@ -284,20 +332,26 @@ def _one_sided_market_event_tick(self, market, offer=None):
acceptable_offer = offer
time_slot = market.time_slot
- if (acceptable_offer and self._energy_params.allowed_operating_hours(time_slot)
- and self._offer_rate_can_be_accepted(acceptable_offer, market)):
+ if (
+ acceptable_offer
+ and self._energy_params.allowed_operating_hours(time_slot)
+ and self._offer_rate_can_be_accepted(acceptable_offer, market)
+ ):
energy_Wh = self.state.calculate_energy_to_accept(
- acceptable_offer.energy * 1000.0, time_slot)
- self.accept_offer(market, acceptable_offer,
- buyer=TraderDetails(
- self.owner.name, self.owner.uuid,
- self.owner.name, self.owner.uuid),
- energy=energy_Wh / 1000.0)
+ acceptable_offer.energy * 1000.0, time_slot
+ )
+ self.accept_offer(
+ market,
+ acceptable_offer,
+ buyer=TraderDetails(
+ self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
+ ),
+ energy=energy_Wh / 1000.0,
+ )
self._energy_params.decrement_energy_requirement(
- energy_kWh=energy_Wh / 1000,
- time_slot=time_slot,
- area_name=self.owner.name)
+ energy_kWh=energy_Wh / 1000, time_slot=time_slot, area_name=self.owner.name
+ )
except MarketException:
self.log.exception("An Error occurred while buying an offer")
@@ -347,16 +401,21 @@ def _post_first_bid(self):
if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.ONE_SIDED.value:
return
for market in self.active_markets:
- if (self.state.can_buy_more_energy(market.time_slot) and
- self._energy_params.allowed_operating_hours(market.time_slot)
- and not self.are_bids_posted(market.id)):
+ if (
+ self.state.can_buy_more_energy(market.time_slot)
+ and self._energy_params.allowed_operating_hours(market.time_slot)
+ and not self.are_bids_posted(market.id)
+ ):
bid_energy = self.state.get_energy_requirement_Wh(market.time_slot)
if self._is_eligible_for_balancing_market:
- bid_energy -= (self.state.get_desired_energy_Wh(market.time_slot) *
- self.balancing_energy_ratio.demand)
+ bid_energy -= (
+ self.state.get_desired_energy_Wh(market.time_slot)
+ * self.balancing_energy_ratio.demand
+ )
try:
- self.post_first_bid(market, bid_energy,
- self.bid_update.initial_rate[market.time_slot])
+ self.post_first_bid(
+ market, bid_energy, self.bid_update.initial_rate[market.time_slot]
+ )
except MarketException:
pass
@@ -380,7 +439,8 @@ def event_bid_traded(self, *, market_id, bid_trade):
self._energy_params.decrement_energy_requirement(
energy_kWh=bid_trade.traded_energy,
time_slot=bid_trade.time_slot,
- area_name=self.owner.name)
+ area_name=self.owner.name,
+ )
def event_offer_traded(self, *, market_id, trade):
"""Register the offer traded by the device and its effects. Extends the superclass method.
@@ -406,19 +466,21 @@ def _demand_balancing_offer(self, market):
if not self._is_eligible_for_balancing_market:
return
- ramp_up_energy = (self.balancing_energy_ratio.demand *
- self.state.get_desired_energy_Wh(market.time_slot))
+ ramp_up_energy = self.balancing_energy_ratio.demand * self.state.get_desired_energy_Wh(
+ market.time_slot
+ )
self._energy_params.decrement_energy_requirement(
- energy_kWh=ramp_up_energy / 1000,
- time_slot=market.time_slot,
- area_name=self.owner.name)
+ energy_kWh=ramp_up_energy / 1000, time_slot=market.time_slot, area_name=self.owner.name
+ )
ramp_up_price = DeviceRegistry.REGISTRY[self.owner.name][0] * ramp_up_energy
if ramp_up_energy != 0 and ramp_up_price != 0:
self.area.get_balancing_market(market.time_slot).balancing_offer(
- ramp_up_price, -ramp_up_energy, TraderDetails(
- self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid))
+ ramp_up_price,
+ -ramp_up_energy,
+ TraderDetails(self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid),
+ )
# committing to reduce its consumption when required
def _supply_balancing_offer(self, market, trade):
@@ -429,8 +491,10 @@ def _supply_balancing_offer(self, market, trade):
ramp_down_energy = self.balancing_energy_ratio.supply * trade.traded_energy
ramp_down_price = DeviceRegistry.REGISTRY[self.owner.name][1] * ramp_down_energy
self.area.get_balancing_market(market.time_slot).balancing_offer(
- ramp_down_price, ramp_down_energy, TraderDetails(
- self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid))
+ ramp_down_price,
+ ramp_down_energy,
+ TraderDetails(self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid),
+ )
@property
def active_markets(self):
@@ -442,16 +506,21 @@ def active_markets(self):
return self._active_markets
def _calculate_active_markets(self):
- self._active_markets = [
- market for market in self.area.all_markets
- if self._is_market_active(market)
- ] if self.area else []
+ self._active_markets = (
+ [market for market in self.area.all_markets if self._is_market_active(market)]
+ if self.area
+ else []
+ )
def _is_market_active(self, market):
- return (self._energy_params.allowed_operating_hours(market.time_slot) and
- market.in_sim_duration and
- (not self.area.current_market or
- market.time_slot >= self.area.current_market.time_slot))
+ return (
+ self._energy_params.allowed_operating_hours(market.time_slot)
+ and market.in_sim_duration
+ and (
+ not self.area.current_market
+ or market.time_slot >= self.area.current_market.time_slot
+ )
+ )
def _update_energy_requirement_future_markets(self):
if not ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS:
diff --git a/src/gsy_e/models/strategy/predefined_load.py b/src/gsy_e/models/strategy/predefined_load.py
index 28d534aef0..8a137e4aee 100644
--- a/src/gsy_e/models/strategy/predefined_load.py
+++ b/src/gsy_e/models/strategy/predefined_load.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from typing import Union
from gsy_framework.constants_limits import ConstSettings
@@ -26,23 +27,32 @@
class DefinedLoadStrategy(LoadHoursStrategy):
"""
- Strategy for creating a load profile. It accepts as an input a load csv file or a
- dictionary that contains the load values for each time point
+ Strategy for creating a load profile. It accepts as an input a load csv file or a
+ dictionary that contains the load values for each time point
"""
+
# pylint: disable=too-many-arguments
- def __init__(self, daily_load_profile=None,
- fit_to_limit=True, energy_rate_increase_per_update=None,
- update_interval=None,
- initial_buying_rate: Union[float, dict, str] =
- ConstSettings.LoadSettings.BUYING_RATE_RANGE.initial,
- final_buying_rate: Union[float, dict, str] =
- ConstSettings.LoadSettings.BUYING_RATE_RANGE.final,
- balancing_energy_ratio: tuple =
- (ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
- ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO),
- use_market_maker_rate: bool = False,
- daily_load_profile_uuid: str = None,
- daily_load_measurement_uuid: str = None):
+ def __init__(
+ self,
+ daily_load_profile=None,
+ fit_to_limit=True,
+ energy_rate_increase_per_update=None,
+ update_interval=None,
+ initial_buying_rate: Union[
+ float, dict, str
+ ] = ConstSettings.LoadSettings.BUYING_RATE_RANGE.initial,
+ final_buying_rate: Union[
+ float, dict, str
+ ] = ConstSettings.LoadSettings.BUYING_RATE_RANGE.final,
+ balancing_energy_ratio: tuple = (
+ ConstSettings.BalancingSettings.OFFER_DEMAND_RATIO,
+ ConstSettings.BalancingSettings.OFFER_SUPPLY_RATIO,
+ ),
+ use_market_maker_rate: bool = False,
+ daily_load_profile_uuid: str = None,
+ daily_load_measurement_uuid: str = None,
+ **kwargs
+ ):
"""
Constructor of DefinedLoadStrategy
:param daily_load_profile: input profile for a day. Can be either a csv file path,
@@ -58,20 +68,28 @@ def __init__(self, daily_load_profile=None,
:param use_market_maker_rate: If set to True, Load would track its final buying rate
as per utility's trading rate
"""
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
+
if update_interval is None:
- update_interval = \
- duration(minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL)
-
- super().__init__(avg_power_W=0, hrs_of_day=list(range(0, 24)),
- fit_to_limit=fit_to_limit,
- energy_rate_increase_per_update=energy_rate_increase_per_update,
- update_interval=update_interval,
- final_buying_rate=final_buying_rate,
- initial_buying_rate=initial_buying_rate,
- balancing_energy_ratio=balancing_energy_ratio,
- use_market_maker_rate=use_market_maker_rate)
+ update_interval = duration(
+ minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL
+ )
+
+ super().__init__(
+ avg_power_W=0,
+ hrs_of_day=list(range(0, 24)),
+ fit_to_limit=fit_to_limit,
+ energy_rate_increase_per_update=energy_rate_increase_per_update,
+ update_interval=update_interval,
+ final_buying_rate=final_buying_rate,
+ initial_buying_rate=initial_buying_rate,
+ balancing_energy_ratio=balancing_energy_ratio,
+ use_market_maker_rate=use_market_maker_rate,
+ )
self._energy_params = DefinedLoadEnergyParameters(
- daily_load_profile, daily_load_profile_uuid, daily_load_measurement_uuid)
+ daily_load_profile, daily_load_profile_uuid, daily_load_measurement_uuid
+ )
# needed for profile_handler
self.daily_load_profile_uuid = daily_load_profile_uuid
diff --git a/src/gsy_e/models/strategy/predefined_pv.py b/src/gsy_e/models/strategy/predefined_pv.py
index b33749724f..b938080af9 100644
--- a/src/gsy_e/models/strategy/predefined_pv.py
+++ b/src/gsy_e/models/strategy/predefined_pv.py
@@ -20,26 +20,31 @@
from pendulum import duration
from gsy_e.models.strategy.energy_parameters.pv import (
- PVPredefinedEnergyParameters, PVUserProfileEnergyParameters)
+ PVPredefinedEnergyParameters,
+ PVUserProfileEnergyParameters,
+)
from gsy_e.models.strategy.pv import PVStrategy
class PVPredefinedStrategy(PVStrategy):
"""
- Strategy responsible for using one of the predefined PV profiles.
+ Strategy responsible for using one of the predefined PV profiles.
"""
+
# pylint: disable=too-many-arguments
def __init__(
- self, panel_count: int = 1,
- initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
- final_selling_rate: float = ConstSettings.PVSettings.SELLING_RATE_RANGE.final,
- cloud_coverage: int = None,
- fit_to_limit: bool = True,
- update_interval=None,
- energy_rate_decrease_per_update=None,
- use_market_maker_rate: bool = False,
- capacity_kW: float = None,
- ):
+ self,
+ panel_count: int = 1,
+ initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
+ final_selling_rate: float = ConstSettings.PVSettings.SELLING_RATE_RANGE.final,
+ cloud_coverage: int = None,
+ fit_to_limit: bool = True,
+ update_interval=None,
+ energy_rate_decrease_per_update=None,
+ use_market_maker_rate: bool = False,
+ capacity_kW: float = None,
+ **kwargs
+ ):
"""
Constructor of PVPredefinedStrategy
Args:
@@ -57,23 +62,28 @@ def __init__(
energy_rate_decrease_per_update: Slope of PV Offer change per update
capacity_kW: power rating of the predefined profiles
"""
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
if update_interval is None:
- update_interval = \
- duration(minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL)
-
- super().__init__(panel_count=panel_count,
- initial_selling_rate=initial_selling_rate,
- final_selling_rate=final_selling_rate,
- fit_to_limit=fit_to_limit,
- update_interval=update_interval,
- energy_rate_decrease_per_update=energy_rate_decrease_per_update,
- capacity_kW=capacity_kW,
- use_market_maker_rate=use_market_maker_rate
- )
+ update_interval = duration(
+ minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL
+ )
+
+ super().__init__(
+ panel_count=panel_count,
+ initial_selling_rate=initial_selling_rate,
+ final_selling_rate=final_selling_rate,
+ fit_to_limit=fit_to_limit,
+ update_interval=update_interval,
+ energy_rate_decrease_per_update=energy_rate_decrease_per_update,
+ capacity_kW=capacity_kW,
+ use_market_maker_rate=use_market_maker_rate,
+ )
self._energy_params = PVPredefinedEnergyParameters(
- panel_count, cloud_coverage, capacity_kW)
+ panel_count, cloud_coverage, capacity_kW
+ )
def read_config_event(self):
# this is to trigger to read from self.simulation_config.cloud_coverage:
@@ -102,20 +112,23 @@ def area_reconfigure_event(self, *args, **kwargs):
class PVUserProfileStrategy(PVStrategy):
"""
- Strategy responsible for reading a profile in the form of a dict of values.
+ Strategy responsible for reading a profile in the form of a dict of values.
"""
+
# pylint: disable=too-many-arguments
def __init__(
- self, power_profile=None, panel_count: int = 1,
- initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
- final_selling_rate: float = ConstSettings.PVSettings.SELLING_RATE_RANGE.final,
- fit_to_limit: bool = True,
- update_interval=duration(
- minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL),
- energy_rate_decrease_per_update=None,
- use_market_maker_rate: bool = False,
- power_profile_uuid: str = None,
- power_measurement_uuid: str = None
+ self,
+ power_profile=None,
+ panel_count: int = 1,
+ initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
+ final_selling_rate: float = ConstSettings.PVSettings.SELLING_RATE_RANGE.final,
+ fit_to_limit: bool = True,
+ update_interval=duration(minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL),
+ energy_rate_decrease_per_update=None,
+ use_market_maker_rate: bool = False,
+ power_profile_uuid: str = None,
+ power_measurement_uuid: str = None,
+ **kwargs
):
"""
Constructor of PVUserProfileStrategy
@@ -126,15 +139,21 @@ def __init__(
panel_count: number of solar panels for this PV plant
final_selling_rate: lower threshold for the PV sale price
"""
- super().__init__(panel_count=panel_count,
- initial_selling_rate=initial_selling_rate,
- final_selling_rate=final_selling_rate,
- fit_to_limit=fit_to_limit,
- update_interval=update_interval,
- energy_rate_decrease_per_update=energy_rate_decrease_per_update,
- use_market_maker_rate=use_market_maker_rate)
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
+
+ super().__init__(
+ panel_count=panel_count,
+ initial_selling_rate=initial_selling_rate,
+ final_selling_rate=final_selling_rate,
+ fit_to_limit=fit_to_limit,
+ update_interval=update_interval,
+ energy_rate_decrease_per_update=energy_rate_decrease_per_update,
+ use_market_maker_rate=use_market_maker_rate,
+ )
self._energy_params = PVUserProfileEnergyParameters(
- panel_count, power_profile, power_profile_uuid, power_measurement_uuid)
+ panel_count, power_profile, power_profile_uuid, power_measurement_uuid
+ )
# needed for profile_handler
self.power_profile_uuid = power_profile_uuid
diff --git a/src/gsy_e/models/strategy/predefined_wind.py b/src/gsy_e/models/strategy/predefined_wind.py
index 986d6287dd..5b4d56b9d9 100644
--- a/src/gsy_e/models/strategy/predefined_wind.py
+++ b/src/gsy_e/models/strategy/predefined_wind.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from gsy_framework.constants_limits import ConstSettings
from pendulum import duration
@@ -23,20 +24,28 @@
class WindUserProfileStrategy(PVUserProfileStrategy):
"""Strategy for a wind turbine creating energy following the power_profile"""
+
# pylint: disable=too-many-arguments
- def __init__(self, power_profile=None,
- initial_selling_rate:
- float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
- final_selling_rate: float = ConstSettings.WindSettings.FINAL_SELLING_RATE,
- fit_to_limit: bool = True,
- update_interval=duration(
- minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL),
- energy_rate_decrease_per_update=None,
- power_profile_uuid: str = None
- ):
- super().__init__(power_profile=power_profile, initial_selling_rate=initial_selling_rate,
- final_selling_rate=final_selling_rate,
- fit_to_limit=fit_to_limit, update_interval=update_interval,
- energy_rate_decrease_per_update=energy_rate_decrease_per_update,
- power_profile_uuid=power_profile_uuid
- )
+ def __init__(
+ self,
+ power_profile=None,
+ initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
+ final_selling_rate: float = ConstSettings.WindSettings.FINAL_SELLING_RATE,
+ fit_to_limit: bool = True,
+ update_interval=duration(minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL),
+ energy_rate_decrease_per_update=None,
+ power_profile_uuid: str = None,
+ **kwargs
+ ):
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
+
+ super().__init__(
+ power_profile=power_profile,
+ initial_selling_rate=initial_selling_rate,
+ final_selling_rate=final_selling_rate,
+ fit_to_limit=fit_to_limit,
+ update_interval=update_interval,
+ energy_rate_decrease_per_update=energy_rate_decrease_per_update,
+ power_profile_uuid=power_profile_uuid,
+ )
diff --git a/src/gsy_e/models/strategy/pv.py b/src/gsy_e/models/strategy/pv.py
index c0caee30ec..d8d72dca5e 100644
--- a/src/gsy_e/models/strategy/pv.py
+++ b/src/gsy_e/models/strategy/pv.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import traceback
from logging import getLogger
@@ -22,8 +23,7 @@
from gsy_framework.data_classes import TraderDetails
from gsy_framework.exceptions import GSyException
from gsy_framework.read_user_profile import read_arbitrary_profile, InputProfileTypes
-from gsy_framework.utils import (
- get_from_profile_same_weekday_and_time, key_in_dict_and_not_none)
+from gsy_framework.utils import get_from_profile_same_weekday_and_time, key_in_dict_and_not_none
from gsy_framework.validators import PVValidator
from pendulum import duration
@@ -45,16 +45,18 @@ class PVStrategy(BidEnabledStrategy, UseMarketMakerMixin):
"""PV Strategy class for gaussian generation profile."""
# pylint: disable=too-many-arguments
- def __init__(self, panel_count: int = 1,
- initial_selling_rate:
- float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
- final_selling_rate:
- float = ConstSettings.PVSettings.SELLING_RATE_RANGE.final,
- fit_to_limit: bool = True,
- update_interval=None,
- energy_rate_decrease_per_update=None,
- capacity_kW: float = None,
- use_market_maker_rate: bool = False):
+ def __init__(
+ self,
+ panel_count: int = 1,
+ initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
+ final_selling_rate: float = ConstSettings.PVSettings.SELLING_RATE_RANGE.final,
+ fit_to_limit: bool = True,
+ update_interval=None,
+ energy_rate_decrease_per_update=None,
+ capacity_kW: float = None,
+ use_market_maker_rate: bool = False,
+ **kwargs
+ ):
"""
Args:
panel_count: Number of solar panels for this PV plant
@@ -65,18 +67,26 @@ def __init__(self, panel_count: int = 1,
energy_rate_decrease_per_update: Slope of PV Offer change per update
capacity_kW: power rating of the predefined profiles
"""
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
+
super().__init__()
self._energy_params = PVEnergyParameters(panel_count, capacity_kW)
self.use_market_maker_rate = use_market_maker_rate
- self._init_price_update(update_interval, initial_selling_rate, final_selling_rate,
- fit_to_limit, energy_rate_decrease_per_update)
+ self._init_price_update(
+ update_interval,
+ initial_selling_rate,
+ final_selling_rate,
+ fit_to_limit,
+ energy_rate_decrease_per_update,
+ )
def serialize(self):
"""Return dict with the current strategy parameter values."""
return {
**self._energy_params.serialize(),
**self.offer_update.serialize(),
- "use_market_maker_rate": self.use_market_maker_rate
+ "use_market_maker_rate": self.use_market_maker_rate,
}
@classmethod
@@ -91,27 +101,38 @@ def state(self) -> PVState:
return self._energy_params._state # pylint: disable=protected-access
# pylint: disable=too-many-arguments
- def _init_price_update(self, update_interval, initial_selling_rate, final_selling_rate,
- fit_to_limit, energy_rate_decrease_per_update):
+ def _init_price_update(
+ self,
+ update_interval,
+ initial_selling_rate,
+ final_selling_rate,
+ fit_to_limit,
+ energy_rate_decrease_per_update,
+ ):
# Instantiate instance variables that should not be shared with child classes
self.final_selling_rate = final_selling_rate
if update_interval is None:
- update_interval = \
- duration(minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL)
+ update_interval = duration(
+ minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL
+ )
if isinstance(update_interval, int):
update_interval = duration(minutes=update_interval)
PVValidator.validate_rate(
fit_to_limit=fit_to_limit,
- energy_rate_decrease_per_update=energy_rate_decrease_per_update)
+ energy_rate_decrease_per_update=energy_rate_decrease_per_update,
+ )
- self.offer_update = TemplateStrategyOfferUpdater(initial_selling_rate, final_selling_rate,
- fit_to_limit,
- energy_rate_decrease_per_update,
- update_interval)
+ self.offer_update = TemplateStrategyOfferUpdater(
+ initial_selling_rate,
+ final_selling_rate,
+ fit_to_limit,
+ energy_rate_decrease_per_update,
+ update_interval,
+ )
def area_reconfigure_event(self, *args, **kwargs):
"""Reconfigure the device properties at runtime using the provided arguments."""
@@ -125,23 +146,28 @@ def _area_reconfigure_prices(self, **kwargs):
initial_rate = (
read_arbitrary_profile(InputProfileTypes.IDENTITY, kwargs["initial_selling_rate"])
if kwargs.get("initial_selling_rate") is not None
- else self.offer_update.initial_rate_profile_buffer)
+ else self.offer_update.initial_rate_profile_buffer
+ )
final_rate = (
read_arbitrary_profile(InputProfileTypes.IDENTITY, kwargs["final_selling_rate"])
if kwargs.get("final_selling_rate") is not None
- else self.offer_update.final_rate_profile_buffer)
+ else self.offer_update.final_rate_profile_buffer
+ )
energy_rate_change_per_update = (
- read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["energy_rate_decrease_per_update"])
+ read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["energy_rate_decrease_per_update"]
+ )
if kwargs.get("energy_rate_decrease_per_update") is not None
- else self.offer_update.energy_rate_change_per_update_profile_buffer)
+ else self.offer_update.energy_rate_change_per_update_profile_buffer
+ )
fit_to_limit = (
kwargs["fit_to_limit"]
if kwargs.get("fit_to_limit") is not None
- else self.offer_update.fit_to_limit)
+ else self.offer_update.fit_to_limit
+ )
if key_in_dict_and_not_none(kwargs, "update_interval"):
if isinstance(kwargs["update_interval"], int):
@@ -155,11 +181,15 @@ def _area_reconfigure_prices(self, **kwargs):
self.use_market_maker_rate = kwargs["use_market_maker_rate"]
try:
- self._validate_rates(initial_rate, final_rate, energy_rate_change_per_update,
- fit_to_limit)
+ self._validate_rates(
+ initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit
+ )
except GSyException as e: # pylint: disable=broad-except
- log.error("PVStrategy._area_reconfigure_prices failed. Exception: %s. "
- "Traceback: %s", e, traceback.format_exc())
+ log.error(
+ "PVStrategy._area_reconfigure_prices failed. Exception: %s. Traceback: %s",
+ e,
+ traceback.format_exc(),
+ )
return
self.offer_update.set_parameters(
@@ -167,23 +197,33 @@ def _area_reconfigure_prices(self, **kwargs):
final_rate=final_rate,
energy_rate_change_per_update=energy_rate_change_per_update,
fit_to_limit=fit_to_limit,
- update_interval=update_interval
+ update_interval=update_interval,
)
- def _validate_rates(self, initial_rate, final_rate, energy_rate_change_per_update,
- fit_to_limit):
+ def _validate_rates(
+ self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit
+ ):
# all parameters have to be validated for each time slot here
for time_slot in initial_rate.keys():
- if self.area and self.area.current_market \
- and time_slot < self.area.current_market.time_slot:
+ if (
+ self.area
+ and self.area.current_market
+ and time_slot < self.area.current_market.time_slot
+ ):
continue
- rate_change = None if fit_to_limit else \
- get_from_profile_same_weekday_and_time(energy_rate_change_per_update, time_slot)
+ rate_change = (
+ None
+ if fit_to_limit
+ else get_from_profile_same_weekday_and_time(
+ energy_rate_change_per_update, time_slot
+ )
+ )
PVValidator.validate_rate(
initial_selling_rate=initial_rate[time_slot],
final_selling_rate=get_from_profile_same_weekday_and_time(final_rate, time_slot),
energy_rate_decrease_per_update=rate_change,
- fit_to_limit=fit_to_limit)
+ fit_to_limit=fit_to_limit,
+ )
def event_activate(self, **kwargs):
self.event_activate_price()
@@ -194,10 +234,12 @@ def event_activate(self, **kwargs):
def event_activate_price(self):
self._replace_rates_with_market_maker_rates()
- self._validate_rates(self.offer_update.initial_rate_profile_buffer,
- self.offer_update.final_rate_profile_buffer,
- self.offer_update.energy_rate_change_per_update_profile_buffer,
- self.offer_update.fit_to_limit)
+ self._validate_rates(
+ self.offer_update.initial_rate_profile_buffer,
+ self.offer_update.final_rate_profile_buffer,
+ self.offer_update.energy_rate_change_per_update_profile_buffer,
+ self.offer_update.fit_to_limit,
+ )
def event_activate_energy(self):
"""Activate energy parameters of the PV."""
@@ -228,7 +270,8 @@ def set_produced_energy_forecast_in_state(self, reconfigure=True):
time_slots.extend(self.area.future_market_time_slots)
for time_slot in time_slots:
self._energy_params.set_produced_energy_forecast(
- time_slot, self.simulation_config.slot_length)
+ time_slot, self.simulation_config.slot_length
+ )
def event_market_cycle(self):
super().event_market_cycle()
@@ -246,14 +289,15 @@ def _set_energy_measurement_of_last_market(self):
self._energy_params.set_energy_measurement_kWh(self.area.current_market.time_slot)
def _delete_past_state(self):
- if (constants.RETAIN_PAST_MARKET_STRATEGIES_STATE is True or
- self.area.current_market is None):
+ if (
+ constants.RETAIN_PAST_MARKET_STRATEGIES_STATE is True
+ or self.area.current_market is None
+ ):
return
self.state.delete_past_state_values(self.area.current_market.time_slot)
self.offer_update.delete_past_state_values(self.area.current_market.time_slot)
- self._future_market_strategy.delete_past_state_values(
- self.area.current_market.time_slot)
+ self._future_market_strategy.delete_past_state_values(self.area.current_market.time_slot)
def event_market_cycle_price(self):
"""Manage price parameters during the market cycle event."""
@@ -266,16 +310,16 @@ def event_market_cycle_price(self):
# market in order to validate that more offers need to be posted.
offer_energy_kWh -= self.offers.open_offer_energy(market.id)
if offer_energy_kWh > 0:
- offer_price = \
- self.offer_update.initial_rate[market.time_slot] * offer_energy_kWh
+ offer_price = self.offer_update.initial_rate[market.time_slot] * offer_energy_kWh
try:
offer = market.offer(
offer_price,
offer_energy_kWh,
- TraderDetails(self.owner.name, self.owner.uuid, self.owner.name,
- self.owner.uuid),
+ TraderDetails(
+ self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
+ ),
original_price=offer_price,
- time_slot=market.time_slot
+ time_slot=market.time_slot,
)
self.offers.post(offer, market.id)
except MarketException:
@@ -292,7 +336,8 @@ def event_offer_traded(self, *, market_id, trade):
if trade.seller.name == self.owner.name:
self.state.decrement_available_energy(
- trade.traded_energy, trade.time_slot, self.owner.name)
+ trade.traded_energy, trade.time_slot, self.owner.name
+ )
def event_bid_traded(self, *, market_id, bid_trade):
super().event_bid_traded(market_id=market_id, bid_trade=bid_trade)
diff --git a/src/gsy_e/models/strategy/smart_meter.py b/src/gsy_e/models/strategy/smart_meter.py
index 4ca699126c..5d59d0e4fd 100644
--- a/src/gsy_e/models/strategy/smart_meter.py
+++ b/src/gsy_e/models/strategy/smart_meter.py
@@ -35,8 +35,10 @@
from gsy_e.models.strategy.energy_parameters.smart_meter import SmartMeterEnergyParameters
from gsy_e.models.strategy.mixins import UseMarketMakerMixin
from gsy_e.models.strategy.state import SmartMeterState
-from gsy_e.models.strategy.update_frequency import (TemplateStrategyBidUpdater,
- TemplateStrategyOfferUpdater)
+from gsy_e.models.strategy.update_frequency import (
+ TemplateStrategyBidUpdater,
+ TemplateStrategyOfferUpdater,
+)
log = getLogger(__name__)
@@ -52,25 +54,25 @@ def serialize(self):
**self.offer_update.serialize(),
# Price consumption parameters
**self.bid_update.serialize(),
- "use_market_maker_rate": self.use_market_maker_rate
+ "use_market_maker_rate": self.use_market_maker_rate,
}
# pylint: disable=too-many-arguments
def __init__(
- self,
- smart_meter_profile: Union[Path, str, Dict[int, float], Dict[str, float]] = None,
- initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
- final_selling_rate: float = ConstSettings.SmartMeterSettings.SELLING_RATE_RANGE.final,
- energy_rate_decrease_per_update: Union[float, None] = None,
- initial_buying_rate: float = (
- ConstSettings.SmartMeterSettings.BUYING_RATE_RANGE.initial),
- final_buying_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
- energy_rate_increase_per_update: Union[float, None] = None,
- fit_to_limit: bool = True,
- update_interval=None,
- use_market_maker_rate: bool = False,
- smart_meter_profile_uuid: str = None,
- smart_meter_measurement_uuid: str = None
+ self,
+ smart_meter_profile: Union[Path, str, Dict[int, float], Dict[str, float]] = None,
+ initial_selling_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
+ final_selling_rate: float = ConstSettings.SmartMeterSettings.SELLING_RATE_RANGE.final,
+ energy_rate_decrease_per_update: Union[float, None] = None,
+ initial_buying_rate: float = (ConstSettings.SmartMeterSettings.BUYING_RATE_RANGE.initial),
+ final_buying_rate: float = ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
+ energy_rate_increase_per_update: Union[float, None] = None,
+ fit_to_limit: bool = True,
+ update_interval=None,
+ use_market_maker_rate: bool = False,
+ smart_meter_profile_uuid: str = None,
+ smart_meter_measurement_uuid: str = None,
+ **kwargs
):
"""
Args:
@@ -95,10 +97,14 @@ def __init__(
use_market_maker_rate: If set to True, the Smart Meter will track its final buying and
selling rate as per utility's trading rate.
"""
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
+
super().__init__()
self._energy_params = SmartMeterEnergyParameters(
- smart_meter_profile, smart_meter_profile_uuid, smart_meter_measurement_uuid)
+ smart_meter_profile, smart_meter_profile_uuid, smart_meter_measurement_uuid
+ )
# needed for profile_handler
self.smart_meter_profile_uuid = smart_meter_profile_uuid
@@ -111,7 +117,8 @@ def __init__(
self.validator.validate(
fit_to_limit=fit_to_limit,
energy_rate_increase_per_update=energy_rate_increase_per_update,
- energy_rate_decrease_per_update=energy_rate_decrease_per_update)
+ energy_rate_decrease_per_update=energy_rate_decrease_per_update,
+ )
# Instances to update the Smart Meter's bids and offers across all market slots
self.bid_update = TemplateStrategyBidUpdater(
@@ -120,7 +127,8 @@ def __init__(
fit_to_limit=fit_to_limit,
energy_rate_change_per_update=energy_rate_increase_per_update,
update_interval=update_interval,
- rate_limit_object=min)
+ rate_limit_object=min,
+ )
self.offer_update = TemplateStrategyOfferUpdater(
initial_rate=initial_selling_rate,
@@ -128,7 +136,8 @@ def __init__(
fit_to_limit=fit_to_limit,
energy_rate_change_per_update=energy_rate_decrease_per_update,
update_interval=update_interval,
- rate_limit_object=max)
+ rate_limit_object=max,
+ )
@property
def state(self) -> SmartMeterState:
@@ -150,15 +159,19 @@ def event_activate_price(self):
initial_rate=self.bid_update.initial_rate_profile_buffer,
final_rate=self.bid_update.final_rate_profile_buffer,
energy_rate_change_per_update=(
- self.bid_update.energy_rate_change_per_update_profile_buffer),
- fit_to_limit=self.bid_update.fit_to_limit)
+ self.bid_update.energy_rate_change_per_update_profile_buffer
+ ),
+ fit_to_limit=self.bid_update.fit_to_limit,
+ )
self._validate_production_rates(
initial_rate=self.offer_update.initial_rate_profile_buffer,
final_rate=self.offer_update.final_rate_profile_buffer,
energy_rate_change_per_update=(
- self.offer_update.energy_rate_change_per_update_profile_buffer),
- fit_to_limit=self.offer_update.fit_to_limit)
+ self.offer_update.energy_rate_change_per_update_profile_buffer
+ ),
+ fit_to_limit=self.offer_update.fit_to_limit,
+ )
def event_activate_energy(self):
"""Read the power profile and update the energy requirements for future market slots.
@@ -167,8 +180,7 @@ def event_activate_energy(self):
"""
self._energy_params.activate(self.owner)
time_slots = [m.time_slot for m in self.area.all_markets]
- self._energy_params.set_energy_forecast_for_future_markets(
- time_slots, reconfigure=True)
+ self._energy_params.set_energy_forecast_for_future_markets(time_slots, reconfigure=True)
def event_market_cycle(self):
"""Prepare rates and execute bids/offers when a new market slot begins.
@@ -179,8 +191,7 @@ def event_market_cycle(self):
self._energy_params.read_and_rotate_profiles()
self._reset_rates_and_update_prices()
time_slots = [m.time_slot for m in self.area.all_markets]
- self._energy_params.set_energy_forecast_for_future_markets(
- time_slots, reconfigure=False)
+ self._energy_params.set_energy_forecast_for_future_markets(time_slots, reconfigure=False)
self._set_energy_measurement_of_last_market()
# Create bids/offers for the expected energy consumption/production in future markets
for market in self.area.all_markets:
@@ -241,7 +252,8 @@ def event_offer_traded(self, *, market_id, trade):
else:
self._assert_if_trade_offer_price_is_too_low(market_id, trade)
self.state.decrement_available_energy(
- trade.traded_energy, market.time_slot, self.owner.name)
+ trade.traded_energy, market.time_slot, self.owner.name
+ )
def event_bid_traded(self, *, market_id, bid_trade):
"""Register the bid traded by the device. Extends the superclass method.
@@ -257,7 +269,8 @@ def event_bid_traded(self, *, market_id, bid_trade):
self._energy_params.decrement_energy_requirement(
energy_kWh=bid_trade.traded_energy,
time_slot=market.time_slot,
- area_name=self.owner.name)
+ area_name=self.owner.name,
+ )
def area_reconfigure_event(self, *args, **kwargs):
"""Reconfigure the device properties at runtime using the provided arguments.
@@ -278,23 +291,28 @@ def _area_reconfigure_production_prices(self, **kwargs):
initial_rate = (
read_arbitrary_profile(InputProfileTypes.IDENTITY, kwargs["initial_selling_rate"])
if kwargs.get("initial_selling_rate") is not None
- else self.offer_update.initial_rate_profile_buffer)
+ else self.offer_update.initial_rate_profile_buffer
+ )
final_rate = (
read_arbitrary_profile(InputProfileTypes.IDENTITY, kwargs["final_selling_rate"])
if kwargs.get("final_selling_rate") is not None
- else self.offer_update.final_rate_profile_buffer)
+ else self.offer_update.final_rate_profile_buffer
+ )
energy_rate_change_per_update = (
- read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["energy_rate_decrease_per_update"])
+ read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["energy_rate_decrease_per_update"]
+ )
if kwargs.get("energy_rate_decrease_per_update") is not None
- else self.offer_update.energy_rate_change_per_update_profile_buffer)
+ else self.offer_update.energy_rate_change_per_update_profile_buffer
+ )
fit_to_limit = (
kwargs["fit_to_limit"]
if kwargs.get("fit_to_limit") is not None
- else self.offer_update.fit_to_limit)
+ else self.offer_update.fit_to_limit
+ )
if kwargs.get("update_interval") is not None:
if isinstance(kwargs["update_interval"], int):
@@ -309,7 +327,8 @@ def _area_reconfigure_production_prices(self, **kwargs):
try:
self._validate_production_rates(
- initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit)
+ initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit
+ )
except GSyException as ex:
log.exception("SmartMeterStrategy._area_reconfigure_production_prices failed: %s", ex)
return
@@ -319,29 +338,35 @@ def _area_reconfigure_production_prices(self, **kwargs):
final_rate=final_rate,
energy_rate_change_per_update=energy_rate_change_per_update,
fit_to_limit=fit_to_limit,
- update_interval=update_interval)
+ update_interval=update_interval,
+ )
def _area_reconfigure_consumption_prices(self, **kwargs):
initial_rate = (
read_arbitrary_profile(InputProfileTypes.IDENTITY, kwargs["initial_buying_rate"])
if kwargs.get("initial_buying_rate") is not None
- else self.bid_update.initial_rate_profile_buffer)
+ else self.bid_update.initial_rate_profile_buffer
+ )
final_rate = (
read_arbitrary_profile(InputProfileTypes.IDENTITY, kwargs["final_buying_rate"])
if kwargs.get("final_buying_rate") is not None
- else self.bid_update.final_rate_profile_buffer)
+ else self.bid_update.final_rate_profile_buffer
+ )
energy_rate_change_per_update = (
- read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["energy_rate_increase_per_update"])
+ read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["energy_rate_increase_per_update"]
+ )
if kwargs.get("energy_rate_increase_per_update") is not None
- else self.bid_update.energy_rate_change_per_update_profile_buffer)
+ else self.bid_update.energy_rate_change_per_update_profile_buffer
+ )
fit_to_limit = (
kwargs["fit_to_limit"]
if kwargs.get("fit_to_limit") is not None
- else self.bid_update.fit_to_limit)
+ else self.bid_update.fit_to_limit
+ )
if kwargs.get("update_interval") is not None:
if isinstance(kwargs["update_interval"], int):
@@ -356,7 +381,8 @@ def _area_reconfigure_consumption_prices(self, **kwargs):
try:
self._validate_consumption_rates(
- initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit)
+ initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit
+ )
except GSyException as ex:
log.exception(ex)
return
@@ -366,7 +392,8 @@ def _area_reconfigure_consumption_prices(self, **kwargs):
final_rate=final_rate,
energy_rate_change_per_update=energy_rate_change_per_update,
fit_to_limit=fit_to_limit,
- update_interval=update_interval)
+ update_interval=update_interval,
+ )
def _reset_rates_and_update_prices(self):
"""Set the initial/final rates and update the price of all bids/offers consequently."""
@@ -386,9 +413,11 @@ def _post_offer(self, market):
offer = market.offer(
offer_price,
offer_energy_kWh,
- TraderDetails(self.owner.name, self.owner.uuid, self.owner.name,
- self.owner.uuid),
- original_price=offer_price)
+ TraderDetails(
+ self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
+ ),
+ original_price=offer_price,
+ )
self.offers.post(offer, market.id)
except MarketException:
pass
@@ -404,8 +433,9 @@ def _post_first_bid(self, market):
# self.balancing_energy_ratio.demand
try:
if not self.are_bids_posted(market.id):
- self.post_first_bid(market, bid_energy,
- self.bid_update.initial_rate[market.time_slot])
+ self.post_first_bid(
+ market, bid_energy, self.bid_update.initial_rate[market.time_slot]
+ )
except MarketException:
pass
@@ -420,8 +450,10 @@ def _convert_update_interval_to_duration(update_interval):
return None
def _delete_past_state(self):
- if (constants.RETAIN_PAST_MARKET_STRATEGIES_STATE is True or
- self.area.current_market is None):
+ if (
+ constants.RETAIN_PAST_MARKET_STRATEGIES_STATE is True
+ or self.area.current_market is None
+ ):
return
# Delete past energy requirements and availability
@@ -431,39 +463,53 @@ def _delete_past_state(self):
# Delete offer rates for previous market slots
self.offer_update.delete_past_state_values(self.area.current_market.time_slot)
# Delete the state of the current slot from the future market cache
- self._future_market_strategy.delete_past_state_values(
- self.area.current_market.time_slot)
+ self._future_market_strategy.delete_past_state_values(self.area.current_market.time_slot)
def _validate_consumption_rates(
- self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit):
+ self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit
+ ):
for time_slot in initial_rate.keys():
- rate_change = None if fit_to_limit else get_from_profile_same_weekday_and_time(
- energy_rate_change_per_update, time_slot)
+ rate_change = (
+ None
+ if fit_to_limit
+ else get_from_profile_same_weekday_and_time(
+ energy_rate_change_per_update, time_slot
+ )
+ )
self.validator.validate_rate(
initial_buying_rate=initial_rate[time_slot],
energy_rate_increase_per_update=rate_change,
final_buying_rate=get_from_profile_same_weekday_and_time(final_rate, time_slot),
- fit_to_limit=fit_to_limit)
+ fit_to_limit=fit_to_limit,
+ )
def _validate_production_rates(
- self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit):
+ self, initial_rate, final_rate, energy_rate_change_per_update, fit_to_limit
+ ):
for time_slot in initial_rate.keys():
- rate_change = None if fit_to_limit else get_from_profile_same_weekday_and_time(
- energy_rate_change_per_update, time_slot)
+ rate_change = (
+ None
+ if fit_to_limit
+ else get_from_profile_same_weekday_and_time(
+ energy_rate_change_per_update, time_slot
+ )
+ )
self.validator.validate_rate(
initial_selling_rate=initial_rate[time_slot],
final_selling_rate=get_from_profile_same_weekday_and_time(final_rate, time_slot),
energy_rate_decrease_per_update=rate_change,
- fit_to_limit=fit_to_limit)
+ fit_to_limit=fit_to_limit,
+ )
def _offer_rate_can_be_accepted(self, offer: Offer, market_slot: MarketBase):
"""Check if the offer rate is less than what the device wants to pay."""
max_affordable_offer_rate = self.bid_update.get_updated_rate(market_slot.time_slot)
return (
limit_float_precision(offer.energy_rate)
- <= max_affordable_offer_rate + FLOATING_POINT_TOLERANCE)
+ <= max_affordable_offer_rate + FLOATING_POINT_TOLERANCE
+ )
def _event_tick_consumption(self):
for market in self.area.all_markets:
@@ -500,15 +546,20 @@ def _one_sided_market_event_tick(self, market, offer=None):
if acceptable_offer and self._offer_rate_can_be_accepted(acceptable_offer, market):
# If the device can still buy more energy
energy_Wh = self.state.calculate_energy_to_accept(
- acceptable_offer.energy * 1000.0, time_slot)
- self.accept_offer(market, acceptable_offer, buyer=TraderDetails(
- self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
- ), energy=energy_Wh / 1000.0)
+ acceptable_offer.energy * 1000.0, time_slot
+ )
+ self.accept_offer(
+ market,
+ acceptable_offer,
+ buyer=TraderDetails(
+ self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
+ ),
+ energy=energy_Wh / 1000.0,
+ )
self._energy_params.decrement_energy_requirement(
- energy_kWh=energy_Wh / 1000,
- time_slot=time_slot,
- area_name=self.owner.name)
+ energy_kWh=energy_Wh / 1000, time_slot=time_slot, area_name=self.owner.name
+ )
except MarketException:
self.log.exception("An Error occurred while buying an offer.")
diff --git a/src/gsy_e/models/strategy/storage.py b/src/gsy_e/models/strategy/storage.py
index 51927cc5e6..1529dec631 100644
--- a/src/gsy_e/models/strategy/storage.py
+++ b/src/gsy_e/models/strategy/storage.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from collections import namedtuple
from enum import Enum
from logging import getLogger
@@ -25,8 +26,7 @@
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.exceptions import GSyException
from gsy_framework.read_user_profile import InputProfileTypes, read_arbitrary_profile
-from gsy_framework.utils import (
- get_from_profile_same_weekday_and_time, key_in_dict_and_not_none)
+from gsy_framework.utils import get_from_profile_same_weekday_and_time, key_in_dict_and_not_none
from gsy_framework.validators import StorageValidator
from pendulum import duration
@@ -39,7 +39,9 @@
from gsy_e.models.strategy import BidEnabledStrategy
from gsy_e.models.strategy.future.strategy import future_market_strategy_factory
from gsy_e.models.strategy.update_frequency import (
- TemplateStrategyBidUpdater, TemplateStrategyOfferUpdater)
+ TemplateStrategyBidUpdater,
+ TemplateStrategyOfferUpdater,
+)
log = getLogger(__name__)
@@ -67,41 +69,49 @@ def serialize(self):
}
def __init__( # pylint: disable=too-many-arguments, too-many-locals
- self, initial_soc: float = StorageSettings.MIN_ALLOWED_SOC,
+ self,
+ initial_soc: float = StorageSettings.MIN_ALLOWED_SOC,
min_allowed_soc=StorageSettings.MIN_ALLOWED_SOC,
battery_capacity_kWh: float = StorageSettings.CAPACITY,
max_abs_battery_power_kW: float = StorageSettings.MAX_ABS_POWER,
cap_price_strategy: bool = False,
- initial_selling_rate: Union[float, dict] =
- StorageSettings.SELLING_RATE_RANGE.initial,
- final_selling_rate: Union[float, dict] =
- StorageSettings.SELLING_RATE_RANGE.final,
- initial_buying_rate: Union[float, dict] =
- StorageSettings.BUYING_RATE_RANGE.initial,
- final_buying_rate: Union[float, dict] =
- StorageSettings.BUYING_RATE_RANGE.final,
- fit_to_limit=True, energy_rate_increase_per_update=None,
+ initial_selling_rate: Union[float, dict] = StorageSettings.SELLING_RATE_RANGE.initial,
+ final_selling_rate: Union[float, dict] = StorageSettings.SELLING_RATE_RANGE.final,
+ initial_buying_rate: Union[float, dict] = StorageSettings.BUYING_RATE_RANGE.initial,
+ final_buying_rate: Union[float, dict] = StorageSettings.BUYING_RATE_RANGE.final,
+ fit_to_limit=True,
+ energy_rate_increase_per_update=None,
energy_rate_decrease_per_update=None,
update_interval=None,
initial_energy_origin: Enum = ESSEnergyOrigin.EXTERNAL,
balancing_energy_ratio: tuple = (
- BalancingSettings.OFFER_DEMAND_RATIO, BalancingSettings.OFFER_SUPPLY_RATIO)):
+ BalancingSettings.OFFER_DEMAND_RATIO,
+ BalancingSettings.OFFER_SUPPLY_RATIO,
+ ),
+ **kwargs
+ ):
+
+ if kwargs.get("linear_pricing") is not None:
+ fit_to_limit = kwargs.get("linear_pricing")
if update_interval is None:
update_interval = duration(
- minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL)
+ minutes=ConstSettings.GeneralSettings.DEFAULT_UPDATE_INTERVAL
+ )
if min_allowed_soc is None:
min_allowed_soc = StorageSettings.MIN_ALLOWED_SOC
self.initial_soc = initial_soc
StorageValidator.validate(
- initial_soc=initial_soc, min_allowed_soc=min_allowed_soc,
+ initial_soc=initial_soc,
+ min_allowed_soc=min_allowed_soc,
battery_capacity_kWh=battery_capacity_kWh,
max_abs_battery_power_kW=max_abs_battery_power_kW,
fit_to_limit=fit_to_limit,
energy_rate_increase_per_update=energy_rate_increase_per_update,
- energy_rate_decrease_per_update=energy_rate_decrease_per_update)
+ energy_rate_decrease_per_update=energy_rate_decrease_per_update,
+ )
if isinstance(update_interval, int):
update_interval = duration(minutes=update_interval)
@@ -109,30 +119,41 @@ def __init__( # pylint: disable=too-many-arguments, too-many-locals
super().__init__()
self.offer_update = TemplateStrategyOfferUpdater(
- initial_rate=initial_selling_rate, final_rate=final_selling_rate,
+ initial_rate=initial_selling_rate,
+ final_rate=final_selling_rate,
fit_to_limit=fit_to_limit,
energy_rate_change_per_update=energy_rate_decrease_per_update,
- update_interval=update_interval)
+ update_interval=update_interval,
+ )
for time_slot in self.offer_update.initial_rate_profile_buffer.keys():
StorageValidator.validate(
initial_selling_rate=self.offer_update.initial_rate_profile_buffer[time_slot],
final_selling_rate=get_from_profile_same_weekday_and_time(
- self.offer_update.final_rate_profile_buffer, time_slot))
+ self.offer_update.final_rate_profile_buffer, time_slot
+ ),
+ )
self.bid_update = TemplateStrategyBidUpdater(
- initial_rate=initial_buying_rate, final_rate=final_buying_rate,
+ initial_rate=initial_buying_rate,
+ final_rate=final_buying_rate,
fit_to_limit=fit_to_limit,
energy_rate_change_per_update=energy_rate_increase_per_update,
- update_interval=update_interval, rate_limit_object=min
+ update_interval=update_interval,
+ rate_limit_object=min,
)
for time_slot in self.bid_update.initial_rate_profile_buffer.keys():
StorageValidator.validate(
initial_buying_rate=self.bid_update.initial_rate_profile_buffer[time_slot],
final_buying_rate=get_from_profile_same_weekday_and_time(
- self.bid_update.final_rate_profile_buffer, time_slot))
+ self.bid_update.final_rate_profile_buffer, time_slot
+ ),
+ )
self._state = StorageState(
- initial_soc=initial_soc, initial_energy_origin=initial_energy_origin,
- capacity=battery_capacity_kWh, max_abs_battery_power_kW=max_abs_battery_power_kW,
- min_allowed_soc=min_allowed_soc)
+ initial_soc=initial_soc,
+ initial_energy_origin=initial_energy_origin,
+ capacity=battery_capacity_kWh,
+ max_abs_battery_power_kW=max_abs_battery_power_kW,
+ min_allowed_soc=min_allowed_soc,
+ )
self.cap_price_strategy = cap_price_strategy
self.balancing_energy_ratio = BalancingRatio(*balancing_energy_ratio)
@@ -145,37 +166,45 @@ def state(self) -> StorageState:
def _area_reconfigure_prices(self, **kwargs): # pylint: disable=too-many-branches
if key_in_dict_and_not_none(kwargs, "initial_selling_rate"):
- initial_selling_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["initial_selling_rate"])
+ initial_selling_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["initial_selling_rate"]
+ )
else:
initial_selling_rate = self.offer_update.initial_rate_profile_buffer
if key_in_dict_and_not_none(kwargs, "final_selling_rate"):
- final_selling_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["final_selling_rate"])
+ final_selling_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["final_selling_rate"]
+ )
else:
final_selling_rate = self.offer_update.final_rate_profile_buffer
if key_in_dict_and_not_none(kwargs, "initial_buying_rate"):
- initial_buying_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["initial_buying_rate"])
+ initial_buying_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["initial_buying_rate"]
+ )
else:
initial_buying_rate = self.bid_update.initial_rate_profile_buffer
if key_in_dict_and_not_none(kwargs, "final_buying_rate"):
- final_buying_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- kwargs["final_buying_rate"])
+ final_buying_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, kwargs["final_buying_rate"]
+ )
else:
final_buying_rate = self.bid_update.final_rate_profile_buffer
if key_in_dict_and_not_none(kwargs, "energy_rate_decrease_per_update"):
energy_rate_decrease_per_update = read_arbitrary_profile(
- InputProfileTypes.IDENTITY, kwargs["energy_rate_decrease_per_update"])
+ InputProfileTypes.IDENTITY, kwargs["energy_rate_decrease_per_update"]
+ )
else:
- energy_rate_decrease_per_update = (self.offer_update.
- energy_rate_change_per_update_profile_buffer)
+ energy_rate_decrease_per_update = (
+ self.offer_update.energy_rate_change_per_update_profile_buffer
+ )
if key_in_dict_and_not_none(kwargs, "energy_rate_increase_per_update"):
energy_rate_increase_per_update = read_arbitrary_profile(
- InputProfileTypes.IDENTITY, kwargs["energy_rate_increase_per_update"])
+ InputProfileTypes.IDENTITY, kwargs["energy_rate_increase_per_update"]
+ )
else:
energy_rate_increase_per_update = (
- self.bid_update.energy_rate_change_per_update_profile_buffer)
+ self.bid_update.energy_rate_change_per_update_profile_buffer
+ )
if key_in_dict_and_not_none(kwargs, "fit_to_limit"):
bid_fit_to_limit = kwargs["fit_to_limit"]
offer_fit_to_limit = kwargs["fit_to_limit"]
@@ -191,10 +220,16 @@ def _area_reconfigure_prices(self, **kwargs): # pylint: disable=too-many-branch
update_interval = self.bid_update.update_interval
try:
- self._validate_rates(initial_selling_rate, final_selling_rate,
- initial_buying_rate, final_buying_rate,
- energy_rate_increase_per_update, energy_rate_decrease_per_update,
- bid_fit_to_limit, offer_fit_to_limit)
+ self._validate_rates(
+ initial_selling_rate,
+ final_selling_rate,
+ initial_buying_rate,
+ final_buying_rate,
+ energy_rate_increase_per_update,
+ energy_rate_decrease_per_update,
+ bid_fit_to_limit,
+ offer_fit_to_limit,
+ )
except GSyException as ex:
log.exception("StorageStrategy._area_reconfigure_prices failed. Exception: %s.", ex)
return
@@ -204,14 +239,14 @@ def _area_reconfigure_prices(self, **kwargs): # pylint: disable=too-many-branch
final_rate=final_selling_rate,
energy_rate_change_per_update=energy_rate_decrease_per_update,
fit_to_limit=offer_fit_to_limit,
- update_interval=update_interval
+ update_interval=update_interval,
)
self.bid_update.set_parameters(
initial_rate=initial_buying_rate,
final_rate=final_buying_rate,
energy_rate_change_per_update=energy_rate_increase_per_update,
fit_to_limit=bid_fit_to_limit,
- update_interval=update_interval
+ update_interval=update_interval,
)
def area_reconfigure_event(self, *args, **kwargs):
@@ -220,52 +255,80 @@ def area_reconfigure_event(self, *args, **kwargs):
self._update_profiles_with_default_values()
def _validate_rates( # pylint: disable=too-many-arguments
- self, initial_selling_rate, final_selling_rate,
- initial_buying_rate, final_buying_rate,
- energy_rate_increase_per_update, energy_rate_decrease_per_update,
- bid_fit_to_limit, offer_fit_to_limit):
+ self,
+ initial_selling_rate,
+ final_selling_rate,
+ initial_buying_rate,
+ final_buying_rate,
+ energy_rate_increase_per_update,
+ energy_rate_decrease_per_update,
+ bid_fit_to_limit,
+ offer_fit_to_limit,
+ ):
for time_slot in initial_selling_rate.keys():
- if self.area and \
- self.area.current_market and time_slot < self.area.current_market.time_slot:
+ if (
+ self.area
+ and self.area.current_market
+ and time_slot < self.area.current_market.time_slot
+ ):
continue
- bid_rate_change = (None if bid_fit_to_limit else
- get_from_profile_same_weekday_and_time(
- energy_rate_increase_per_update, time_slot))
- offer_rate_change = (None if offer_fit_to_limit else
- get_from_profile_same_weekday_and_time(
- energy_rate_decrease_per_update, time_slot))
+ bid_rate_change = (
+ None
+ if bid_fit_to_limit
+ else get_from_profile_same_weekday_and_time(
+ energy_rate_increase_per_update, time_slot
+ )
+ )
+ offer_rate_change = (
+ None
+ if offer_fit_to_limit
+ else get_from_profile_same_weekday_and_time(
+ energy_rate_decrease_per_update, time_slot
+ )
+ )
StorageValidator.validate(
initial_selling_rate=initial_selling_rate[time_slot],
final_selling_rate=get_from_profile_same_weekday_and_time(
- final_selling_rate, time_slot),
+ final_selling_rate, time_slot
+ ),
initial_buying_rate=get_from_profile_same_weekday_and_time(
- initial_buying_rate, time_slot),
+ initial_buying_rate, time_slot
+ ),
final_buying_rate=get_from_profile_same_weekday_and_time(
- final_buying_rate, time_slot),
+ final_buying_rate, time_slot
+ ),
energy_rate_increase_per_update=bid_rate_change,
- energy_rate_decrease_per_update=offer_rate_change)
+ energy_rate_decrease_per_update=offer_rate_change,
+ )
def event_on_disabled_area(self):
self.state.check_state(self.area.spot_market.time_slot)
def event_activate_price(self):
"""Validate rates of offers and bids when the ACTIVATE event is triggered."""
- self._validate_rates(self.offer_update.initial_rate_profile_buffer,
- self.offer_update.final_rate_profile_buffer,
- self.bid_update.initial_rate_profile_buffer,
- self.bid_update.final_rate_profile_buffer,
- self.bid_update.energy_rate_change_per_update_profile_buffer,
- self.offer_update.energy_rate_change_per_update_profile_buffer,
- self.bid_update.fit_to_limit, self.offer_update.fit_to_limit)
+ self._validate_rates(
+ self.offer_update.initial_rate_profile_buffer,
+ self.offer_update.final_rate_profile_buffer,
+ self.bid_update.initial_rate_profile_buffer,
+ self.bid_update.final_rate_profile_buffer,
+ self.bid_update.energy_rate_change_per_update_profile_buffer,
+ self.offer_update.energy_rate_change_per_update_profile_buffer,
+ self.bid_update.fit_to_limit,
+ self.offer_update.fit_to_limit,
+ )
def event_activate_energy(self):
"""Set the battery energy for each slot when the ACTIVATE event is triggered."""
self.state.activate(
self.simulation_config.slot_length,
- self.area.current_market.time_slot
- if self.area.current_market else self.area.config.start_date)
+ (
+ self.area.current_market.time_slot
+ if self.area.current_market
+ else self.area.config.start_date
+ ),
+ )
def event_activate(self, **kwargs):
self._update_profiles_with_default_values()
@@ -274,9 +337,16 @@ def event_activate(self, **kwargs):
@staticmethod
def _validate_constructor_arguments( # pylint: disable=too-many-arguments, too-many-branches
- initial_soc=None, min_allowed_soc=None, battery_capacity_kWh=None,
- max_abs_battery_power_kW=None, initial_selling_rate=None, final_selling_rate=None,
- initial_buying_rate=None, final_buying_rate=None, energy_rate_change_per_update=None):
+ initial_soc=None,
+ min_allowed_soc=None,
+ battery_capacity_kWh=None,
+ max_abs_battery_power_kW=None,
+ initial_selling_rate=None,
+ final_selling_rate=None,
+ initial_buying_rate=None,
+ final_buying_rate=None,
+ energy_rate_change_per_update=None,
+ ):
if battery_capacity_kWh is not None and battery_capacity_kWh < 0:
raise ValueError("Battery capacity should be a positive integer")
@@ -286,47 +356,64 @@ def _validate_constructor_arguments( # pylint: disable=too-many-arguments, too-
raise ValueError("initial SOC must be in between 0-100 %")
if min_allowed_soc is not None and 0 < min_allowed_soc > 100:
raise ValueError("initial SOC must be in between 0-100 %")
- if (initial_soc is not None and min_allowed_soc is not None and
- initial_soc < min_allowed_soc):
+ if (
+ initial_soc is not None
+ and min_allowed_soc is not None
+ and initial_soc < min_allowed_soc
+ ):
raise ValueError("Initial charge must be more than the minimum allowed soc.")
if initial_selling_rate is not None and initial_selling_rate < 0:
raise ValueError("Initial selling rate must be greater equal 0.")
if final_selling_rate is not None:
if isinstance(final_selling_rate, float) and final_selling_rate < 0:
raise ValueError("Final selling rate must be greater equal 0.")
- if (isinstance(final_selling_rate, dict) and
- any(rate < 0 for _, rate in final_selling_rate.items())):
+ if isinstance(final_selling_rate, dict) and any(
+ rate < 0 for _, rate in final_selling_rate.items()
+ ):
raise ValueError("Final selling rate must be greater equal 0.")
if initial_selling_rate is not None and final_selling_rate is not None:
- initial_selling_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- initial_selling_rate)
- final_selling_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- final_selling_rate)
- if any(initial_selling_rate[hour] < final_selling_rate[hour]
- for hour, _ in initial_selling_rate.items()):
+ initial_selling_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, initial_selling_rate
+ )
+ final_selling_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, final_selling_rate
+ )
+ if any(
+ initial_selling_rate[hour] < final_selling_rate[hour]
+ for hour, _ in initial_selling_rate.items()
+ ):
raise ValueError("Initial selling rate must be greater than final selling rate.")
if initial_buying_rate is not None and initial_buying_rate < 0:
raise ValueError("Initial buying rate must be greater equal 0.")
if final_buying_rate is not None:
- final_buying_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- final_buying_rate)
+ final_buying_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, final_buying_rate
+ )
if any(rate < 0 for _, rate in final_buying_rate.items()):
raise ValueError("Final buying rate must be greater equal 0.")
if initial_buying_rate is not None and final_buying_rate is not None:
- initial_buying_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- initial_buying_rate)
- final_buying_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- final_buying_rate)
- if any(initial_buying_rate[hour] > final_buying_rate[hour]
- for hour, _ in initial_buying_rate.items()):
+ initial_buying_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, initial_buying_rate
+ )
+ final_buying_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, final_buying_rate
+ )
+ if any(
+ initial_buying_rate[hour] > final_buying_rate[hour]
+ for hour, _ in initial_buying_rate.items()
+ ):
raise ValueError("Initial buying rate must be less than final buying rate.")
if final_selling_rate is not None and final_buying_rate is not None:
- final_selling_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- final_selling_rate)
- final_buying_rate = read_arbitrary_profile(InputProfileTypes.IDENTITY,
- final_buying_rate)
- if any(final_buying_rate[hour] >= final_selling_rate[hour]
- for hour, _ in final_selling_rate.items()):
+ final_selling_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, final_selling_rate
+ )
+ final_buying_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, final_buying_rate
+ )
+ if any(
+ final_buying_rate[hour] >= final_selling_rate[hour]
+ for hour, _ in final_selling_rate.items()
+ ):
raise ValueError("final_buying_rate should be higher than final_selling_rate.")
if energy_rate_change_per_update is not None and energy_rate_change_per_update < 0:
raise ValueError("energy_rate_change_per_update should be a non-negative value.")
@@ -380,8 +467,10 @@ def event_bid_traded(self, *, market_id, bid_trade):
if bid_trade.buyer.name == self.owner.name:
self.state.register_energy_from_bid_trade(
- bid_trade.traded_energy, bid_trade.time_slot,
- self._track_bought_energy_origin(bid_trade.seller.name))
+ bid_trade.traded_energy,
+ bid_trade.time_slot,
+ self._track_bought_energy_origin(bid_trade.seller.name),
+ )
def _cycle_state(self):
current_market = self.area.spot_market
@@ -390,7 +479,7 @@ def _cycle_state(self):
self.state.market_cycle(
past_market.time_slot if past_market else None,
current_market.time_slot,
- self.area.future_market_time_slots
+ self.area.future_market_time_slots,
)
def event_market_cycle(self):
@@ -412,23 +501,24 @@ def event_balancing_market_cycle(self):
current_market = self.area.spot_market
free_storage = self.state.free_storage(current_market.time_slot)
seller_details = TraderDetails(
- self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid)
+ self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
+ )
if free_storage > 0:
charge_energy = self.balancing_energy_ratio.demand * free_storage
charge_price = DeviceRegistry.REGISTRY[self.owner.name][0] * charge_energy
if charge_energy != 0 and charge_price != 0:
# committing to start charging when required
- self.area.get_balancing_market(self.area.now).balancing_offer(charge_price,
- -charge_energy,
- seller_details)
+ self.area.get_balancing_market(self.area.now).balancing_offer(
+ charge_price, -charge_energy, seller_details
+ )
if self.state.used_storage > 0:
discharge_energy = self.balancing_energy_ratio.supply * self.state.used_storage
discharge_price = DeviceRegistry.REGISTRY[self.owner.name][1] * discharge_energy
# committing to start discharging when required
if discharge_energy != 0 and discharge_price != 0:
- self.area.get_balancing_market(self.area.now).balancing_offer(discharge_price,
- discharge_energy,
- seller_details)
+ self.area.get_balancing_market(self.area.now).balancing_offer(
+ discharge_price, discharge_energy, seller_details
+ )
def _try_to_buy_offer(self, offer, market, max_affordable_offer_rate):
if offer.seller.name == self.owner.name:
@@ -445,11 +535,18 @@ def _try_to_buy_offer(self, offer, market, max_affordable_offer_rate):
max_energy = min(offer.energy, max_energy)
if max_energy > FLOATING_POINT_TOLERANCE:
self.state.register_energy_from_one_sided_market_accept_offer(
- max_energy, market.time_slot,
- self._track_bought_energy_origin(offer.seller.name))
- self.accept_offer(market, offer, buyer=TraderDetails(
- self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid),
- energy=max_energy)
+ max_energy,
+ market.time_slot,
+ self._track_bought_energy_origin(offer.seller.name),
+ )
+ self.accept_offer(
+ market,
+ offer,
+ buyer=TraderDetails(
+ self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
+ ),
+ energy=max_energy,
+ )
return None
except MarketException:
# Offer already gone etc., try next one.
@@ -466,8 +563,10 @@ def _buy_energy_one_sided_spot_market(self, market, offer=None):
self._try_to_buy_offer(offer, market, max_affordable_offer_rate)
else:
for market_offer in market.sorted_offers:
- if self._try_to_buy_offer(
- market_offer, market, max_affordable_offer_rate) is False:
+ if (
+ self._try_to_buy_offer(market_offer, market, max_affordable_offer_rate)
+ is False
+ ):
return
def _sell_energy_to_spot_market(self):
@@ -475,9 +574,7 @@ def _sell_energy_to_spot_market(self):
selling_rate = self.calculate_selling_rate(self.area.spot_market)
energy_kWh = self.state.get_available_energy_to_sell_kWh(time_slot)
if energy_kWh > FLOATING_POINT_TOLERANCE:
- offer = self.post_first_offer(
- self.area.spot_market, energy_kWh, selling_rate
- )
+ offer = self.post_first_offer(self.area.spot_market, energy_kWh, selling_rate)
self.state.register_energy_from_posted_offer(offer.energy, time_slot)
def _buy_energy_two_sided_spot_market(self):
@@ -520,27 +617,35 @@ def _capacity_dependant_sell_rate(self, market):
def _update_profiles_with_default_values(self):
self.offer_update.update_and_populate_price_settings(self.area)
self.bid_update.update_and_populate_price_settings(self.area)
- self.state.add_default_values_to_state_profiles([
- self.spot_market_time_slot, *self.area.future_market_time_slots])
+ self.state.add_default_values_to_state_profiles(
+ [self.spot_market_time_slot, *self.area.future_market_time_slots]
+ )
self._future_market_strategy.update_and_populate_price_settings(self)
def event_offer(self, *, market_id, offer):
super().event_offer(market_id=market_id, offer=offer)
- if (ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.ONE_SIDED.value
- and not self.area.is_market_future(market_id)):
+ if (
+ ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.ONE_SIDED.value
+ and not self.area.is_market_future(market_id)
+ ):
market = self.area.get_spot_or_future_market_by_id(market_id)
if not market:
return
# sometimes the offer event arrives earlier than the market_cycle event,
# so the default values have to be written here too:
self._update_profiles_with_default_values()
- if (offer.id in market.offers and offer.seller.name != self.owner.name and
- offer.seller.name != self.area.name):
+ if (
+ offer.id in market.offers
+ and offer.seller.name != self.owner.name
+ and offer.seller.name != self.area.name
+ ):
self._buy_energy_one_sided_spot_market(market, offer)
def _delete_past_state(self):
- if (constants.RETAIN_PAST_MARKET_STRATEGIES_STATE is True or
- self.area.current_market is None):
+ if (
+ constants.RETAIN_PAST_MARKET_STRATEGIES_STATE is True
+ or self.area.current_market is None
+ ):
return
self.offer_update.delete_past_state_values(self.area.current_market.time_slot)
@@ -548,8 +653,7 @@ def _delete_past_state(self):
self.state.delete_past_state_values(self.area.current_market.time_slot)
# Delete the state of the current slot from the future market cache
- self._future_market_strategy.delete_past_state_values(
- self.area.current_market.time_slot)
+ self._future_market_strategy.delete_past_state_values(self.area.current_market.time_slot)
@property
def asset_type(self):