diff --git a/src/gsy_e/constants.py b/src/gsy_e/constants.py
index fefe513b9..6ac0152c7 100644
--- a/src/gsy_e/constants.py
+++ b/src/gsy_e/constants.py
@@ -21,13 +21,6 @@
# pylint: disable=unused-import
import os
-from gsy_framework.constants_limits import DATE_TIME_FORMAT, DATE_TIME_UI_FORMAT, TIME_ZONE # NOQA
-from gsy_framework.constants_limits import TIME_FORMAT, DATE_FORMAT, GlobalConfig # NOQA
-
-# In order to cover conversion and reverse-conversion to 5 decimal points, the tolerance has to be
-# 0.00002. That way off-by-one consecutive rounding errors would not be treated as errors, e.g.
-# when recalculating the original energy rate in trade chains.
-FLOATING_POINT_TOLERANCE = 0.00002
ROUND_TOLERANCE = 5
# Percentual standard deviation relative to the forecast energy, used to compute the (simulated)
diff --git a/src/gsy_e/gsy_e_core/cli.py b/src/gsy_e/gsy_e_core/cli.py
index 4ad2c7194..15d5a14d1 100644
--- a/src/gsy_e/gsy_e_core/cli.py
+++ b/src/gsy_e/gsy_e_core/cli.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 logging
import multiprocessing
import platform
@@ -23,25 +24,40 @@
from click.types import Choice
from click_default_group import DefaultGroup
from colorlog.colorlog import ColoredFormatter
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, DATE_FORMAT, TIME_FORMAT, TIME_ZONE
from gsy_framework.exceptions import GSyException
from gsy_framework.settings_validators import validate_global_settings
from pendulum import today
-import gsy_e.constants
from gsy_e.gsy_e_core.simulation import run_simulation
from gsy_e.gsy_e_core.util import (
- DateType, IntervalType, available_simulation_scenarios, convert_str_to_pause_after_interval,
- read_settings_from_file, update_advanced_settings)
+ DateType,
+ IntervalType,
+ available_simulation_scenarios,
+ convert_str_to_pause_after_interval,
+ read_settings_from_file,
+ update_advanced_settings,
+)
from gsy_e.models.config import SimulationConfig
log = logging.getLogger(__name__)
-@click.group(name="gsy-e", cls=DefaultGroup, default="run", default_if_no_args=True,
- context_settings={"max_content_width": 120})
-@click.option("-l", "--log-level", type=Choice(logging._nameToLevel.keys()), default="INFO",
- show_default=True, help="Log level")
+@click.group(
+ name="gsy-e",
+ cls=DefaultGroup,
+ default="run",
+ default_if_no_args=True,
+ context_settings={"max_content_width": 120},
+)
+@click.option(
+ "-l",
+ "--log-level",
+ type=Choice(logging._nameToLevel.keys()),
+ default="INFO",
+ show_default=True,
+ help="Log level",
+)
def main(log_level):
"""Entrypoint for command-line interface interaction."""
handler = logging.StreamHandler()
@@ -50,7 +66,7 @@ def main(log_level):
ColoredFormatter(
"%(log_color)s%(asctime)s.%(msecs)03d %(levelname)-8s (%(lineno)4d) %(name)-30s: "
"%(message)s%(reset)s",
- datefmt="%H:%M:%S"
+ datefmt="%H:%M:%S",
)
)
root_logger = logging.getLogger()
@@ -62,53 +78,124 @@ def main(log_level):
@main.command()
-@click.option("-d", "--duration", type=IntervalType("D:H"), default="1d", show_default=True,
- help="Duration of simulation")
-@click.option("-t", "--tick-length", type=IntervalType("M:S"), default="1s", show_default=True,
- help="Length of a tick")
-@click.option("-s", "--slot-length", type=IntervalType("M:S"), default="15m", show_default=True,
- help="Length of a market slot")
-@click.option("--slot-length-realtime", type=IntervalType("M:S"), default="0m",
- show_default=True, help="Desired duration of slot in realtime")
-@click.option("--setup", "setup_module_name", default="default_2a",
- help=("Simulation setup module use. "
- f"Available modules: [{', '.join(_setup_modules)}]"))
-@click.option("-g", "--settings-file", default=None,
- help="Settings file path")
+@click.option(
+ "-d",
+ "--duration",
+ type=IntervalType("D:H"),
+ default="1d",
+ show_default=True,
+ help="Duration of simulation",
+)
+@click.option(
+ "-t",
+ "--tick-length",
+ type=IntervalType("M:S"),
+ default="1s",
+ show_default=True,
+ help="Length of a tick",
+)
+@click.option(
+ "-s",
+ "--slot-length",
+ type=IntervalType("M:S"),
+ default="15m",
+ show_default=True,
+ help="Length of a market slot",
+)
+@click.option(
+ "--slot-length-realtime",
+ type=IntervalType("M:S"),
+ default="0m",
+ show_default=True,
+ help="Desired duration of slot in realtime",
+)
+@click.option(
+ "--setup",
+ "setup_module_name",
+ default="default_2a",
+ help=("Simulation setup module use. " f"Available modules: [{', '.join(_setup_modules)}]"),
+)
+@click.option("-g", "--settings-file", default=None, help="Settings file path")
@click.option("--seed", help="Manually specify random seed")
-@click.option("--paused", is_flag=True, default=False, show_default=True,
- help="Start simulation in paused state")
-@click.option("--pause-at", type=str, default=None,
- help="Automatically pause at a certain time. "
- f"Accepted Input formats: ({gsy_e.constants.DATE_FORMAT}, "
- f"{gsy_e.constants.TIME_FORMAT}) [default: disabled]")
-@click.option("--incremental", is_flag=True, default=False, show_default=True,
- help="Pause the simulation at the end of each time slot.")
-@click.option("--repl/--no-repl", default=False, show_default=True,
- help="Start REPL after simulation run.")
+@click.option(
+ "--paused",
+ is_flag=True,
+ default=False,
+ show_default=True,
+ help="Start simulation in paused state",
+)
+@click.option(
+ "--pause-at",
+ type=str,
+ default=None,
+ help="Automatically pause at a certain time. "
+ f"Accepted Input formats: ({DATE_FORMAT}, "
+ f"{TIME_FORMAT}) [default: disabled]",
+)
+@click.option(
+ "--incremental",
+ is_flag=True,
+ default=False,
+ show_default=True,
+ help="Pause the simulation at the end of each time slot.",
+)
+@click.option(
+ "--repl/--no-repl", default=False, show_default=True, help="Start REPL after simulation run."
+)
@click.option("--no-export", is_flag=True, default=False, help="Skip export of simulation data")
-@click.option("--export-path", type=str, default=None, show_default=False,
- help="Specify a path for the csv export files (default: ~/gsy-e-simulation)")
+@click.option(
+ "--export-path",
+ type=str,
+ default=None,
+ show_default=False,
+ help="Specify a path for the csv export files (default: ~/gsy-e-simulation)",
+)
@click.option("--enable-bc", is_flag=True, default=False, help="Run simulation on Blockchain")
-@click.option("--enable-external-connection", is_flag=True, default=False,
- help="External Agents interaction to simulation during runtime")
-@click.option("--start-date", type=DateType(gsy_e.constants.DATE_FORMAT),
- default=today(tz=gsy_e.constants.TIME_ZONE).format(gsy_e.constants.DATE_FORMAT),
- show_default=True,
- help=f"Start date of the Simulation ({gsy_e.constants.DATE_FORMAT})")
-@click.option("--enable-dof/--disable-dof",
- is_flag=True, default=True,
- help=(
- "Enable or disable Degrees of Freedom "
- "(orders can't contain attributes/requirements)."))
-@click.option("-m", "--market-type", type=int,
- default=ConstSettings.MASettings.MARKET_TYPE, show_default=True,
- help="Market type. 1 for one-sided market, 2 for two-sided market, "
- "3 for coefficient-based trading.")
-def run(setup_module_name, settings_file, duration, slot_length, tick_length,
- enable_external_connection, start_date,
- pause_at, incremental, slot_length_realtime, enable_dof: bool,
- market_type: int, **kwargs):
+@click.option(
+ "--enable-external-connection",
+ is_flag=True,
+ default=False,
+ help="External Agents interaction to simulation during runtime",
+)
+@click.option(
+ "--start-date",
+ type=DateType(DATE_FORMAT),
+ default=today(tz=TIME_ZONE).format(DATE_FORMAT),
+ show_default=True,
+ help=f"Start date of the Simulation ({DATE_FORMAT})",
+)
+@click.option(
+ "--enable-dof/--disable-dof",
+ is_flag=True,
+ default=True,
+ help=(
+ "Enable or disable Degrees of Freedom " "(orders can't contain attributes/requirements)."
+ ),
+)
+@click.option(
+ "-m",
+ "--market-type",
+ type=int,
+ default=ConstSettings.MASettings.MARKET_TYPE,
+ show_default=True,
+ help="Market type. 1 for one-sided market, 2 for two-sided market, "
+ "3 for coefficient-based trading.",
+)
+def run(
+ setup_module_name,
+ settings_file,
+ duration,
+ slot_length,
+ tick_length,
+ enable_external_connection,
+ start_date,
+ pause_at,
+ incremental,
+ slot_length_realtime,
+ enable_dof: bool,
+ market_type: int,
+ **kwargs,
+):
"""Configure settings and run a simulation."""
# Force the multiprocessing start method to be 'fork' on macOS.
if platform.system() == "Darwin":
@@ -124,24 +211,31 @@ def run(setup_module_name, settings_file, duration, slot_length, tick_length,
else:
assert 1 <= market_type <= 3, "Market type should be an integer between 1 and 3."
ConstSettings.MASettings.MARKET_TYPE = market_type
- global_settings = {"sim_duration": duration,
- "slot_length": slot_length,
- "tick_length": tick_length,
- "enable_degrees_of_freedom": enable_dof}
+ global_settings = {
+ "sim_duration": duration,
+ "slot_length": slot_length,
+ "tick_length": tick_length,
+ "enable_degrees_of_freedom": enable_dof,
+ }
validate_global_settings(global_settings)
simulation_config = SimulationConfig(
- duration, slot_length, tick_length, start_date=start_date,
+ duration,
+ slot_length,
+ tick_length,
+ start_date=start_date,
external_connection_enabled=enable_external_connection,
- enable_degrees_of_freedom=enable_dof)
+ enable_degrees_of_freedom=enable_dof,
+ )
if incremental:
kwargs["incremental"] = incremental
if pause_at is not None:
kwargs["pause_after"] = convert_str_to_pause_after_interval(start_date, pause_at)
- run_simulation(setup_module_name, simulation_config, None, None, None,
- slot_length_realtime, kwargs)
+ run_simulation(
+ setup_module_name, simulation_config, None, None, None, slot_length_realtime, kwargs
+ )
except GSyException as ex:
log.exception(ex)
diff --git a/src/gsy_e/gsy_e_core/rq_job_handler.py b/src/gsy_e/gsy_e_core/rq_job_handler.py
index 5d749edf4..2d635c8f3 100644
--- a/src/gsy_e/gsy_e_core/rq_job_handler.py
+++ b/src/gsy_e/gsy_e_core/rq_job_handler.py
@@ -5,7 +5,7 @@
from datetime import datetime, date
from typing import Dict, Optional
-from gsy_framework.constants_limits import GlobalConfig, ConstSettings
+from gsy_framework.constants_limits import GlobalConfig, ConstSettings, TIME_ZONE
from gsy_framework.enums import ConfigurationType, SpotMarketTypeEnum, CoefficientAlgorithm
from gsy_framework.settings_validators import validate_global_settings
from pendulum import duration, instance, now
@@ -94,7 +94,7 @@ def launch_simulation_from_rq_job(
if settings.get("type") == ConfigurationType.CANARY_NETWORK.value:
config.start_date = instance(
- datetime.combine(date.today(), datetime.min.time()), tz=gsy_e.constants.TIME_ZONE
+ datetime.combine(date.today(), datetime.min.time()), tz=TIME_ZONE
)
if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.COEFFICIENTS.value:
@@ -220,7 +220,7 @@ def _create_config_settings_object(
"start_date": (
instance(
datetime.combine(settings.get("start_date"), datetime.min.time()),
- tz=gsy_e.constants.TIME_ZONE,
+ tz=TIME_ZONE,
)
if "start_date" in settings
else GlobalConfig.start_date
@@ -292,9 +292,7 @@ def _handle_scm_past_slots_simulation_run(
# Adding 4 hours of extra time to the SCM past slots simulation duration, in order to
# compensate for the runtime of the SCM past slots simulation and to not have any results gaps
# after this simulation run and the following Canary Network launch.
- config.end_date = (
- now(tz=gsy_e.constants.TIME_ZONE).subtract(hours=config.hours_of_delay).add(hours=4)
- )
+ config.end_date = now(tz=TIME_ZONE).subtract(hours=config.hours_of_delay).add(hours=4)
config.sim_duration = config.end_date - config.start_date
GlobalConfig.sim_duration = config.sim_duration
gsy_e.constants.RUN_IN_REALTIME = False
diff --git a/src/gsy_e/gsy_e_core/sim_results/file_export_endpoints.py b/src/gsy_e/gsy_e_core/sim_results/file_export_endpoints.py
index 28b6fd5a3..10bc56248 100644
--- a/src/gsy_e/gsy_e_core/sim_results/file_export_endpoints.py
+++ b/src/gsy_e/gsy_e_core/sim_results/file_export_endpoints.py
@@ -16,6 +16,7 @@
along with this program. If not, see .
"""
+# pylint: disable=too-many-return-statements, broad-exception-raised
from abc import ABC, abstractmethod
from statistics import mean
from typing import TYPE_CHECKING, Dict, List
@@ -179,8 +180,6 @@ def _specific_labels(self):
return [
"unmatched demand [kWh]",
"storage temperature C",
- "temp decrease K",
- "temp increase K",
"COP",
"heat demand J",
]
@@ -189,8 +188,6 @@ def _specific_labels(self):
return [
"unmatched demand [kWh]",
"storage temperature C",
- "temp decrease K",
- "temp increase K",
"COP",
"heat demand J",
"condenser temperature C",
@@ -251,20 +248,24 @@ def _specific_row(self, slot, market):
# pylint: disable=unidiomatic-typecheck
if type(self.area.strategy) == HeatPumpStrategy:
return [
- round(self.area.strategy.state.get_unmatched_demand_kWh(slot), ROUND_TOLERANCE),
- round(self.area.strategy.state.get_storage_temp_C(slot), ROUND_TOLERANCE),
- round(self.area.strategy.state.get_temp_decrease_K(slot), ROUND_TOLERANCE),
- round(self.area.strategy.state.get_temp_increase_K(slot), ROUND_TOLERANCE),
- round(self.area.strategy.state.get_cop(slot), ROUND_TOLERANCE),
- round(self.area.strategy.state.get_heat_demand(slot), ROUND_TOLERANCE),
+ round(
+ self.area.strategy.state.tanks.get_unmatched_demand_kWh(slot),
+ ROUND_TOLERANCE,
+ ),
+ round(
+ self.area.strategy.state.tanks.get_average_tank_temperature(slot),
+ ROUND_TOLERANCE,
+ ),
+ round(self.area.strategy.state.heatpump.get_cop(slot), ROUND_TOLERANCE),
+ round(self.area.strategy.state.heatpump.get_heat_demand(slot), ROUND_TOLERANCE),
]
# pylint: disable=unidiomatic-typecheck
if type(self.area.strategy) == VirtualHeatpumpStrategy:
return [
- round(self.area.strategy.state.get_unmatched_demand_kWh(slot), ROUND_TOLERANCE),
+ round(
+ self.area.strategy.state.tanks.get_unmatched_demand_kWh(slot), ROUND_TOLERANCE
+ ),
round(self.area.strategy.state.get_storage_temp_C(slot), ROUND_TOLERANCE),
- round(self.area.strategy.state.get_temp_decrease_K(slot), ROUND_TOLERANCE),
- round(self.area.strategy.state.get_temp_increase_K(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_cop(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_heat_demand(slot), ROUND_TOLERANCE),
round(self.area.strategy.state.get_condenser_temp(slot), ROUND_TOLERANCE),
diff --git a/src/gsy_e/gsy_e_core/sim_results/plotly_graph.py b/src/gsy_e/gsy_e_core/sim_results/plotly_graph.py
index 857d32f99..2dc2eb664 100644
--- a/src/gsy_e/gsy_e_core/sim_results/plotly_graph.py
+++ b/src/gsy_e/gsy_e_core/sim_results/plotly_graph.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 os
import pendulum
@@ -22,7 +23,7 @@
import plotly.graph_objs as go
from gsy_framework.utils import limit_float_precision
-from gsy_e.constants import TIME_ZONE
+from gsy_framework.constants_limits import TIME_ZONE
from gsy_e.data_classes import PlotDescription
from gsy_e.models.strategy.commercial_producer import CommercialStrategy
from gsy_e.models.strategy.finite_power_plant import FinitePowerPlant
@@ -41,23 +42,27 @@
purple = "rgba(156, 110, 177, alpha)"
blue = "rgba(0,0,200,alpha)"
-DEVICE_PLOT_COLORS = {"trade_energy_kWh": purple,
- "sold_trade_energy_kWh": purple,
- "bought_trade_energy_kWh": purple,
- "trade_price_eur": blue}
-
-DEVICE_YAXIS = {"trade_energy_kWh": "Traded [kWh]",
- "sold_trade_energy_kWh": "Supply/Traded [kWh]",
- "bought_trade_energy_kWh": "Demand/Traded [kWh]",
- "pv_production_kWh": "PV Production [kWh]",
- "energy_consumption_kWh": "Energy Consumption [kWh]",
- "storage_temp_C": "Heatpump Storage Temperature [C]",
- "energy_buffer_kWh": "Energy Buffer [kWh]",
- "production_kWh": "Power Production [kWh]",
- "load_profile_kWh": "Load Profile [kWh]",
- "smart_meter_profile_kWh": "Smart Meter Profile [kWh]",
- "soc_history_%": "State of Charge [%]",
- "trade_price_eur": "Energy Rate [EUR/kWh]"}
+DEVICE_PLOT_COLORS = {
+ "trade_energy_kWh": purple,
+ "sold_trade_energy_kWh": purple,
+ "bought_trade_energy_kWh": purple,
+ "trade_price_eur": blue,
+}
+
+DEVICE_YAXIS = {
+ "trade_energy_kWh": "Traded [kWh]",
+ "sold_trade_energy_kWh": "Supply/Traded [kWh]",
+ "bought_trade_energy_kWh": "Demand/Traded [kWh]",
+ "pv_production_kWh": "PV Production [kWh]",
+ "energy_consumption_kWh": "Energy Consumption [kWh]",
+ "storage_temp_C": "Heatpump Storage Temperature [C]",
+ "energy_buffer_kWh": "Energy Buffer [kWh]",
+ "production_kWh": "Power Production [kWh]",
+ "load_profile_kWh": "Load Profile [kWh]",
+ "smart_meter_profile_kWh": "Smart Meter Profile [kWh]",
+ "soc_history_%": "State of Charge [%]",
+ "trade_price_eur": "Energy Rate [EUR/kWh]",
+}
OPAQUE_ALPHA = 1
TRANSPARENT_ALPHA = 0.4
@@ -93,18 +98,11 @@ def _common_layout(data_desc: PlotDescription, xrange: list, showlegend=True, ho
height=700,
barmode=data_desc.barmode,
title=data_desc.title,
- yaxis=dict(
- title=data_desc.ytitle
- ),
- xaxis=dict(
- title=data_desc.xtitle,
- range=xrange
- ),
- font=dict(
- size=16
- ),
+ yaxis=dict(title=data_desc.ytitle),
+ xaxis=dict(title=data_desc.xtitle, range=xrange),
+ font=dict(size=16),
showlegend=showlegend,
- hovermode=hovermode
+ hovermode=hovermode,
)
def graph_value(self, scale_value=1):
@@ -123,7 +121,8 @@ def graph_value(self, scale_value=1):
self.umHours[self.dataset["slot"][de]] = 0.0
else:
self.umHours[self.dataset["slot"][de]] = (
- round(self.dataset[self.key][de], 5) * scale_value)
+ round(self.dataset[self.key][de], 5) * scale_value
+ )
@staticmethod
def modify_time_axis(plot_desc: PlotDescription):
@@ -144,12 +143,23 @@ def modify_time_axis(plot_desc: PlotDescription):
raise ValueError(f"There is no time information in plot {plot_desc.title}")
start_time = pendulum.datetime(
- day_list[0].year, day_list[0].month, day_list[0].day,
- day_list[0].hour, day_list[0].minute, day_list[0].second, tz=TIME_ZONE
+ day_list[0].year,
+ day_list[0].month,
+ day_list[0].day,
+ day_list[0].hour,
+ day_list[0].minute,
+ day_list[0].second,
+ tz=TIME_ZONE,
)
end_time = pendulum.datetime(
- day_list[-1].year, day_list[-1].month, day_list[-1].day,
- day_list[-1].hour, day_list[-1].minute, day_list[-1].second, tz=TIME_ZONE)
+ day_list[-1].year,
+ day_list[-1].month,
+ day_list[-1].day,
+ day_list[-1].hour,
+ day_list[-1].minute,
+ day_list[-1].second,
+ tz=TIME_ZONE,
+ )
return [start_time, end_time], plot_desc.data
@@ -161,34 +171,52 @@ def plot_slider_graph(cls, fig, stats_plot_dir, area_name, market_slot_data_mapp
for i, _ in enumerate(market_slot_data_mapping):
step = dict(
method="update",
- args=[{"visible": [False] * len(fig.data)},
- {"title": "Slider switched to slot: " + str(i)}], # layout attribute
+ args=[
+ {"visible": [False] * len(fig.data)},
+ {"title": "Slider switched to slot: " + str(i)},
+ ], # layout attribute
)
- for k in range(market_slot_data_mapping[i].start,
- market_slot_data_mapping[i].end):
+ for k in range(market_slot_data_mapping[i].start, market_slot_data_mapping[i].end):
step["args"][0]["visible"][k] = True # Toggle i'th trace to "visible"
steps.append(step)
- sliders = [dict(
- active=0,
- currentvalue={"prefix": "MarketSlot: "},
- pad={"t": len(market_slot_data_mapping)},
- steps=steps
- )]
+ sliders = [
+ dict(
+ active=0,
+ currentvalue={"prefix": "MarketSlot: "},
+ pad={"t": len(market_slot_data_mapping)},
+ steps=steps,
+ )
+ ]
output_file = os.path.join(stats_plot_dir, "offer_bid_trade_history.html")
barmode = "group"
title = f"OFFER BID TRADE AREA: {area_name}"
xtitle = "Time"
ytitle = "Rate [€ cents / kWh]"
- fig.update_layout(autosize=True, barmode=barmode, width=1200, height=700, title=title,
- yaxis=dict(title=ytitle), xaxis=dict(title=xtitle),
- font=dict(size=16), showlegend=False, sliders=sliders)
+ fig.update_layout(
+ autosize=True,
+ barmode=barmode,
+ width=1200,
+ height=700,
+ title=title,
+ yaxis=dict(title=ytitle),
+ xaxis=dict(title=xtitle),
+ font=dict(size=16),
+ showlegend=False,
+ sliders=sliders,
+ )
py.offline.plot(fig, filename=output_file, auto_open=False)
@classmethod
- def plot_bar_graph(cls, plot_desc: PlotDescription, iname: str,
- time_range=None, showlegend=True, hovermode="x"):
+ def plot_bar_graph(
+ cls,
+ plot_desc: PlotDescription,
+ iname: str,
+ time_range=None,
+ showlegend=True,
+ hovermode="x",
+ ):
"""Render bar graph, used by multiple plots."""
# pylint: disable=too-many-arguments
if time_range is None:
@@ -197,9 +225,7 @@ def plot_bar_graph(cls, plot_desc: PlotDescription, iname: str,
except ValueError:
return
- layout = cls._common_layout(
- plot_desc, time_range, showlegend, hovermode=hovermode
- )
+ layout = cls._common_layout(plot_desc, time_range, showlegend, hovermode=hovermode)
fig = go.Figure(data=data, layout=layout)
py.offline.plot(fig, filename=iname, auto_open=False)
@@ -216,12 +242,12 @@ def _plot_line_time_series(cls, device_dict, var_name):
# pylint: disable=too-many-locals
color = _get_color(var_name, OPAQUE_ALPHA)
fill_color = _get_color(var_name, TRANSPARENT_ALPHA)
- time, var_data, longterm_min_var_data, longterm_max_var_data = (
- cls._prepare_input(device_dict, var_name))
+ time, var_data, longterm_min_var_data, longterm_max_var_data = cls._prepare_input(
+ device_dict, var_name
+ )
yaxis = "y3"
connectgaps = True
- line = dict(color=color,
- width=0.8)
+ line = dict(color=color, width=0.8)
time_series = go.Scatter(
x=time,
y=var_data,
@@ -234,7 +260,7 @@ def _plot_line_time_series(cls, device_dict, var_name):
fill=None,
xaxis="x",
yaxis=yaxis,
- connectgaps=connectgaps
+ connectgaps=connectgaps,
)
longterm_max_hover = go.Scatter(
x=time,
@@ -247,7 +273,7 @@ def _plot_line_time_series(cls, device_dict, var_name):
hoverinfo="y+name",
xaxis="x",
yaxis=yaxis,
- connectgaps=connectgaps
+ connectgaps=connectgaps,
)
longterm_min_hover = go.Scatter(
x=time,
@@ -260,7 +286,7 @@ def _plot_line_time_series(cls, device_dict, var_name):
hoverinfo="y+name",
xaxis="x",
yaxis=yaxis,
- connectgaps=connectgaps
+ connectgaps=connectgaps,
)
shade = go.Scatter(
x=time,
@@ -273,7 +299,7 @@ def _plot_line_time_series(cls, device_dict, var_name):
hoverinfo="none",
xaxis="x",
yaxis=yaxis,
- connectgaps=connectgaps
+ connectgaps=connectgaps,
)
hoverinfo_time = go.Scatter(
x=time,
@@ -282,20 +308,22 @@ def _plot_line_time_series(cls, device_dict, var_name):
hoverinfo="x",
xaxis="x",
showlegend=False,
- yaxis=yaxis
+ yaxis=yaxis,
)
# it is not possible to use cls._hoverinfo here because the order matters here:
return [longterm_min_hover, shade, time_series, longterm_max_hover, hoverinfo_time]
@classmethod
- def _plot_bar_time_series_traded(cls, device_dict, traded_varname, yaxis,
- expected_varname=None, invert_y=False):
+ def _plot_bar_time_series_traded(
+ cls, device_dict, traded_varname, yaxis, expected_varname=None, invert_y=False
+ ):
# pylint: disable=too-many-locals,too-many-arguments
color_traded = _get_color(traded_varname, OPAQUE_ALPHA)
fill_color_traded = _get_color(traded_varname, OPAQUE_ALPHA)
- time_traded, energy_traded, min_energy_traded, max_energy_traded = (
- cls._prepare_input(device_dict, traded_varname, invert_y))
+ time_traded, energy_traded, min_energy_traded, max_energy_traded = cls._prepare_input(
+ device_dict, traded_varname, invert_y
+ )
time_series_traded = go.Bar(
x=time_traded,
@@ -304,8 +332,8 @@ def _plot_bar_time_series_traded(cls, device_dict, traded_varname, yaxis,
color=fill_color_traded,
line=dict(
color=color_traded,
- width=1.,
- )
+ width=1.0,
+ ),
),
name=traded_varname,
showlegend=True,
@@ -318,7 +346,8 @@ def _plot_bar_time_series_traded(cls, device_dict, traded_varname, yaxis,
color_expected = _get_color(expected_varname, OPAQUE_ALPHA)
fill_color_expected = _get_color(expected_varname, TRANSPARENT_ALPHA)
time_expected, energy_expected, min_energy_expected, max_energy_expected = (
- cls._prepare_input(device_dict, expected_varname))
+ cls._prepare_input(device_dict, expected_varname)
+ )
time_series_expected = go.Bar(
x=time_expected,
y=energy_expected,
@@ -326,8 +355,8 @@ def _plot_bar_time_series_traded(cls, device_dict, traded_varname, yaxis,
color=fill_color_expected,
line=dict(
color=color_expected,
- width=1.,
- )
+ width=1.0,
+ ),
),
name=expected_varname,
showlegend=True,
@@ -336,10 +365,11 @@ def _plot_bar_time_series_traded(cls, device_dict, traded_varname, yaxis,
yaxis=yaxis,
)
return [time_series_expected, time_series_traded] + cls._hoverinfo(
- time_expected, min_energy_expected, max_energy_expected, yaxis,
- only_time=True)
+ time_expected, min_energy_expected, max_energy_expected, yaxis, only_time=True
+ )
return [time_series_traded] + cls._hoverinfo(
- time_traded, min_energy_traded, max_energy_traded, yaxis, only_time=True)
+ time_traded, min_energy_traded, max_energy_traded, yaxis, only_time=True
+ )
@classmethod
def _hoverinfo(cls, time, longterm_min, longterm_max, yaxis, only_time=False):
@@ -352,7 +382,7 @@ def _hoverinfo(cls, time, longterm_min, longterm_max, yaxis, only_time=False):
hoverinfo="y+name",
xaxis="x",
showlegend=False,
- yaxis=yaxis
+ yaxis=yaxis,
)
hoverinfo_min = go.Scatter(
x=time,
@@ -362,7 +392,7 @@ def _hoverinfo(cls, time, longterm_min, longterm_max, yaxis, only_time=False):
hoverinfo="y+name",
xaxis="x",
showlegend=False,
- yaxis=yaxis
+ yaxis=yaxis,
)
hoverinfo_time = go.Scatter(
x=time,
@@ -371,7 +401,7 @@ def _hoverinfo(cls, time, longterm_min, longterm_max, yaxis, only_time=False):
hoverinfo="x",
xaxis="x",
showlegend=False,
- yaxis=yaxis
+ yaxis=yaxis,
)
if only_time:
return [hoverinfo_time]
@@ -381,7 +411,8 @@ def _hoverinfo(cls, time, longterm_min, longterm_max, yaxis, only_time=False):
def _plot_candlestick_time_series_price(cls, device_dict, var_name, yaxis):
# pylint: disable=too-many-locals
time, trade_rate_list, longterm_min_trade_rate, longterm_max_trade_rate = (
- cls._prepare_input(device_dict, var_name))
+ cls._prepare_input(device_dict, var_name)
+ )
plot_time = []
plot_local_min_trade_rate = []
plot_local_max_trade_rate = []
@@ -393,24 +424,27 @@ def _plot_candlestick_time_series_price(cls, device_dict, var_name, yaxis):
plot_local_min_trade_rate.append(limit_float_precision(min(trade_rate_list[ii])))
plot_local_max_trade_rate.append(limit_float_precision(max(trade_rate_list[ii])))
plot_longterm_min_trade_rate.append(
- limit_float_precision(longterm_min_trade_rate[ii]))
+ limit_float_precision(longterm_min_trade_rate[ii])
+ )
plot_longterm_max_trade_rate.append(
- limit_float_precision(longterm_max_trade_rate[ii]))
+ limit_float_precision(longterm_max_trade_rate[ii])
+ )
color = _get_color(var_name, OPAQUE_ALPHA)
- candle_stick = go.Candlestick(x=plot_time,
- open=plot_local_min_trade_rate,
- high=plot_longterm_max_trade_rate,
- low=plot_longterm_min_trade_rate,
- close=plot_local_max_trade_rate,
- yaxis=yaxis,
- xaxis="x",
- hoverinfo="none",
- name=var_name,
- increasing=dict(line=dict(color=color)),
- decreasing=dict(line=dict(color=color)),
- )
+ candle_stick = go.Candlestick(
+ x=plot_time,
+ open=plot_local_min_trade_rate,
+ high=plot_longterm_max_trade_rate,
+ low=plot_longterm_min_trade_rate,
+ close=plot_local_max_trade_rate,
+ yaxis=yaxis,
+ xaxis="x",
+ hoverinfo="none",
+ name=var_name,
+ increasing=dict(line=dict(color=color)),
+ decreasing=dict(line=dict(color=color)),
+ )
hoverinfo_local_max = go.Scatter(
x=plot_time,
y=plot_local_max_trade_rate,
@@ -419,7 +453,7 @@ def _plot_candlestick_time_series_price(cls, device_dict, var_name, yaxis):
hoverinfo="y+name",
xaxis="x",
showlegend=False,
- yaxis=yaxis
+ yaxis=yaxis,
)
hoverinfo_local_min = go.Scatter(
x=plot_time,
@@ -429,11 +463,12 @@ def _plot_candlestick_time_series_price(cls, device_dict, var_name, yaxis):
hoverinfo="y+name",
xaxis="x",
showlegend=False,
- yaxis=yaxis
+ yaxis=yaxis,
)
return [candle_stick, hoverinfo_local_max, hoverinfo_local_min] + cls._hoverinfo(
- plot_time, plot_longterm_min_trade_rate, plot_longterm_max_trade_rate, yaxis)
+ plot_time, plot_longterm_min_trade_rate, plot_longterm_max_trade_rate, yaxis
+ )
@classmethod
def _prepare_input(cls, device_dict, var_name, invert_y=False):
@@ -473,42 +508,56 @@ def plot_device_profile(cls, device_dict, device_name, output_file, device_strat
y1axis_key = "trade_price_eur"
y2axis_key = trade_energy_var_name
y3axis_key = "soc_history_%"
- yaxis_caption_list = [DEVICE_YAXIS[y1axis_key], DEVICE_YAXIS[y2axis_key],
- DEVICE_YAXIS[y3axis_key]]
+ yaxis_caption_list = [
+ DEVICE_YAXIS[y1axis_key],
+ DEVICE_YAXIS[y2axis_key],
+ DEVICE_YAXIS[y3axis_key],
+ ]
data += cls._plot_candlestick_time_series_price(device_dict, y1axis_key, "y1")
data += cls._plot_bar_time_series_traded(device_dict, y2axis_key, "y2")
data += cls._plot_line_time_series(device_dict, y3axis_key)
- layout = cls._device_plot_layout("overlay", f"{device_name}",
- "Time", yaxis_caption_list)
+ layout = cls._device_plot_layout(
+ "overlay", f"{device_name}", "Time", yaxis_caption_list
+ )
- elif isinstance(device_strategy, (LoadHoursStrategy, SCMLoadHoursStrategy,
- SCMLoadProfileStrategy)):
+ elif isinstance(
+ device_strategy, (LoadHoursStrategy, SCMLoadHoursStrategy, SCMLoadProfileStrategy)
+ ):
y1axis_key = "trade_price_eur"
y2axis_key = trade_energy_var_name
y3axis_key = "load_profile_kWh"
- yaxis_caption_list = [DEVICE_YAXIS[y1axis_key], DEVICE_YAXIS[y2axis_key],
- DEVICE_YAXIS[y3axis_key]]
+ yaxis_caption_list = [
+ DEVICE_YAXIS[y1axis_key],
+ DEVICE_YAXIS[y2axis_key],
+ DEVICE_YAXIS[y3axis_key],
+ ]
data += cls._plot_candlestick_time_series_price(device_dict, y1axis_key, "y1")
- data += cls._plot_bar_time_series_traded(device_dict, y2axis_key, "y2",
- expected_varname=y3axis_key)
+ data += cls._plot_bar_time_series_traded(
+ device_dict, y2axis_key, "y2", expected_varname=y3axis_key
+ )
data += cls._plot_line_time_series(device_dict, y3axis_key)
- layout = cls._device_plot_layout("overlay", f"{device_name}",
- "Time", yaxis_caption_list)
+ layout = cls._device_plot_layout(
+ "overlay", f"{device_name}", "Time", yaxis_caption_list
+ )
elif isinstance(device_strategy, (SmartMeterStrategy, SCMSmartMeterStrategy)):
y1axis_key = "trade_price_eur"
y2axis_key = trade_energy_var_name
y3axis_key = "smart_meter_profile_kWh"
- yaxis_caption_list = [DEVICE_YAXIS[y1axis_key], DEVICE_YAXIS[y2axis_key],
- DEVICE_YAXIS[y3axis_key]]
+ yaxis_caption_list = [
+ DEVICE_YAXIS[y1axis_key],
+ DEVICE_YAXIS[y2axis_key],
+ DEVICE_YAXIS[y3axis_key],
+ ]
data += cls._plot_candlestick_time_series_price(device_dict, y1axis_key, "y1")
data += cls._plot_bar_time_series_traded(
- device_dict, y2axis_key, "y2", expected_varname=y3axis_key)
+ device_dict, y2axis_key, "y2", expected_varname=y3axis_key
+ )
data += cls._plot_line_time_series(device_dict, y3axis_key)
layout = cls._device_plot_layout("overlay", device_name, "Time", yaxis_caption_list)
@@ -516,8 +565,11 @@ def plot_device_profile(cls, device_dict, device_name, output_file, device_strat
y1axis_key = "trade_price_eur"
y2axis_key = trade_energy_var_name
y3axis_key = "pv_production_kWh"
- yaxis_caption_list = [DEVICE_YAXIS[y1axis_key], DEVICE_YAXIS[y2axis_key],
- DEVICE_YAXIS[y3axis_key]]
+ yaxis_caption_list = [
+ DEVICE_YAXIS[y1axis_key],
+ DEVICE_YAXIS[y2axis_key],
+ DEVICE_YAXIS[y3axis_key],
+ ]
data += cls._plot_candlestick_time_series_price(device_dict, y1axis_key, "y1")
data += cls._plot_bar_time_series_traded(
@@ -525,38 +577,49 @@ def plot_device_profile(cls, device_dict, device_name, output_file, device_strat
)
data += cls._plot_line_time_series(device_dict, y3axis_key)
- layout = cls._device_plot_layout("overlay", f"{device_name}",
- "Time", yaxis_caption_list)
+ layout = cls._device_plot_layout(
+ "overlay", f"{device_name}", "Time", yaxis_caption_list
+ )
elif isinstance(device_strategy, HeatPumpStrategy):
y1axis_key = "trade_price_eur"
y2axis_key = trade_energy_var_name
y3axis_key = "storage_temp_C"
- yaxis_caption_list = [DEVICE_YAXIS[y1axis_key], DEVICE_YAXIS[y2axis_key],
- DEVICE_YAXIS[y3axis_key]]
+ yaxis_caption_list = [
+ DEVICE_YAXIS[y1axis_key],
+ DEVICE_YAXIS[y2axis_key],
+ DEVICE_YAXIS[y3axis_key],
+ ]
data += cls._plot_candlestick_time_series_price(device_dict, y1axis_key, "y1")
data += cls._plot_bar_time_series_traded(
- device_dict, y2axis_key, "y2", expected_varname=y2axis_key)
+ device_dict, y2axis_key, "y2", expected_varname=y2axis_key
+ )
data += cls._plot_line_time_series(device_dict, y3axis_key)
- layout = cls._device_plot_layout("overlay", f"{device_name}",
- "Time", yaxis_caption_list)
+ layout = cls._device_plot_layout(
+ "overlay", f"{device_name}", "Time", yaxis_caption_list
+ )
elif type(device_strategy) == FinitePowerPlant:
y1axis_key = "trade_price_eur"
y2axis_key = trade_energy_var_name
y3axis_key = "production_kWh"
- yaxis_caption_list = [DEVICE_YAXIS[y1axis_key], DEVICE_YAXIS[y2axis_key],
- DEVICE_YAXIS[y3axis_key]]
+ yaxis_caption_list = [
+ DEVICE_YAXIS[y1axis_key],
+ DEVICE_YAXIS[y2axis_key],
+ DEVICE_YAXIS[y3axis_key],
+ ]
data += cls._plot_candlestick_time_series_price(device_dict, y1axis_key, "y1")
- data += cls._plot_bar_time_series_traded(device_dict, y2axis_key, "y2",
- expected_varname=y3axis_key, invert_y=True)
+ data += cls._plot_bar_time_series_traded(
+ device_dict, y2axis_key, "y2", expected_varname=y3axis_key, invert_y=True
+ )
data += cls._plot_line_time_series(device_dict, y3axis_key)
- layout = cls._device_plot_layout("overlay", f"{device_name}",
- "Time", yaxis_caption_list)
+ layout = cls._device_plot_layout(
+ "overlay", f"{device_name}", "Time", yaxis_caption_list
+ )
elif type(device_strategy) in [CommercialStrategy, MarketMakerStrategy]:
y1axis_key = "trade_price_eur"
y2axis_key = sold_trade_energy_var_name
@@ -565,22 +628,27 @@ def plot_device_profile(cls, device_dict, device_name, output_file, device_strat
data += cls._plot_candlestick_time_series_price(device_dict, y1axis_key, "y1")
data += cls._plot_bar_time_series_traded(device_dict, y2axis_key, "y2", invert_y=True)
- layout = cls._device_plot_layout("overlay", f"{device_name}",
- "Time", yaxis_caption_list)
+ layout = cls._device_plot_layout(
+ "overlay", f"{device_name}", "Time", yaxis_caption_list
+ )
elif type(device_strategy) == InfiniteBusStrategy:
y1axis_key = "trade_price_eur"
y2axis_key = sold_trade_energy_var_name
y3axis_key = bought_trade_energy_var_name
- yaxis_caption_list = [DEVICE_YAXIS[y1axis_key], DEVICE_YAXIS[y2axis_key],
- DEVICE_YAXIS[y3axis_key]]
+ yaxis_caption_list = [
+ DEVICE_YAXIS[y1axis_key],
+ DEVICE_YAXIS[y2axis_key],
+ DEVICE_YAXIS[y3axis_key],
+ ]
data += cls._plot_candlestick_time_series_price(device_dict, y1axis_key, "y1")
data += cls._plot_bar_time_series_traded(device_dict, y2axis_key, "y2")
data += cls._plot_bar_time_series_traded(device_dict, y3axis_key, "y3")
- layout = cls._device_plot_layout("overlay", f"{device_name}",
- "Time", yaxis_caption_list)
+ layout = cls._device_plot_layout(
+ "overlay", f"{device_name}", "Time", yaxis_caption_list
+ )
else:
return
@@ -600,7 +668,7 @@ def _device_plot_layout(barmode, title, xaxis_caption, yaxis_caption_list):
showgrid=True,
domain=[pointer, pointer + d_domain_diff],
rangemode="tozero",
- autorange=True
+ autorange=True,
)
return go.Layout(
@@ -613,15 +681,10 @@ def _device_plot_layout(barmode, title, xaxis_caption, yaxis_caption_list):
title=xaxis_caption,
showgrid=True,
anchor="y1",
- rangeslider=dict(visible=True,
- thickness=0.075,
- bgcolor="rgba(100,100,100,0.3)"
- )
- ),
- font=dict(
- size=16
+ rangeslider=dict(visible=True, thickness=0.075, bgcolor="rgba(100,100,100,0.3)"),
),
+ font=dict(size=16),
showlegend=True,
legend=dict(x=1.1, y=1),
- **yaxes
+ **yaxes,
)
diff --git a/src/gsy_e/gsy_e_core/simulation/progress_info.py b/src/gsy_e/gsy_e_core/simulation/progress_info.py
index e4db7cfdf..9a0c3fdfc 100644
--- a/src/gsy_e/gsy_e_core/simulation/progress_info.py
+++ b/src/gsy_e/gsy_e_core/simulation/progress_info.py
@@ -19,11 +19,12 @@
from logging import getLogger
from typing import TYPE_CHECKING
-from gsy_framework.utils import format_datetime
from pendulum import DateTime, Duration, duration, now
+from gsy_framework.utils import format_datetime
+from gsy_framework.constants_limits import TIME_ZONE
import gsy_e.constants
-from gsy_e.constants import TIME_ZONE
+
if TYPE_CHECKING:
from gsy_e.gsy_e_core.simulation.time_manager import SimulationTimeManager
@@ -51,16 +52,18 @@ def _get_market_slot_time_str(cls, slot_number: int, config: "SimulationConfig")
@staticmethod
def _get_market_slot_time(slot_number: int, config: "SimulationConfig") -> DateTime:
- return config.start_date.add(
- minutes=config.slot_length.total_minutes() * slot_number
- )
-
- def update(self, slot_no: int, slot_count: int, time_params: "SimulationTimeManager",
- config: "SimulationConfig") -> None:
+ return config.start_date.add(minutes=config.slot_length.total_minutes() * slot_number)
+
+ def update(
+ self,
+ slot_no: int,
+ slot_count: int,
+ time_params: "SimulationTimeManager",
+ config: "SimulationConfig",
+ ) -> None:
"""Update progress info according to the simulation progress."""
run_duration = (
- now(tz=TIME_ZONE) - time_params.start_time -
- duration(seconds=time_params.paused_time)
+ now(tz=TIME_ZONE) - time_params.start_time - duration(seconds=time_params.paused_time)
)
if gsy_e.constants.RUN_IN_REALTIME:
@@ -73,20 +76,25 @@ def update(self, slot_no: int, slot_count: int, time_params: "SimulationTimeMana
self.elapsed_time = run_duration
self.current_slot_str = self._get_market_slot_time_str(slot_no, config)
self.current_slot_time = self._get_market_slot_time(slot_no, config)
- self.next_slot_str = self._get_market_slot_time_str(
- slot_no + 1, config)
+ self.next_slot_str = self._get_market_slot_time_str(slot_no + 1, config)
self.current_slot_number = slot_no
- log.warning("Slot %s of %s - (%.1f %%) %s elapsed, ETA: %s", slot_no+1, slot_count,
- self.percentage_completed, self.elapsed_time,
- self.eta)
+ log.warning(
+ "Slot %s of %s - (%.1f %%) %s elapsed, ETA: %s",
+ slot_no + 1,
+ slot_count,
+ self.percentage_completed,
+ self.elapsed_time,
+ self.eta,
+ )
- def log_simulation_finished(self, paused_duration: Duration, config: "SimulationConfig"
- ) -> None:
+ def log_simulation_finished(
+ self, paused_duration: Duration, config: "SimulationConfig"
+ ) -> None:
"""Log that the simulation has finished."""
log.info(
"Run finished in %s%s / %.2fx real time",
self.elapsed_time,
f" ({paused_duration} paused)" if paused_duration else "",
- config.sim_duration / (self.elapsed_time - paused_duration)
+ config.sim_duration / (self.elapsed_time - paused_duration),
)
diff --git a/src/gsy_e/gsy_e_core/simulation/results_manager.py b/src/gsy_e/gsy_e_core/simulation/results_manager.py
index f9d730100..b8fcb54ad 100644
--- a/src/gsy_e/gsy_e_core/simulation/results_manager.py
+++ b/src/gsy_e/gsy_e_core/simulation/results_manager.py
@@ -19,13 +19,12 @@
from logging import getLogger
from typing import TYPE_CHECKING, Optional
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, DATE_TIME_FORMAT, TIME_ZONE
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.kafka_communication.kafka_producer import kafka_connection_factory
from pendulum import DateTime, now
import gsy_e.constants
-from gsy_e.constants import DATE_TIME_FORMAT, TIME_ZONE
from gsy_e.gsy_e_core.export import CoefficientExportAndPlot, ExportAndPlot
from gsy_e.gsy_e_core.sim_results.endpoint_buffer import (
CoefficientEndpointBuffer,
diff --git a/src/gsy_e/gsy_e_core/simulation/time_manager.py b/src/gsy_e/gsy_e_core/simulation/time_manager.py
index b696f97f6..3d71c3150 100644
--- a/src/gsy_e/gsy_e_core/simulation/time_manager.py
+++ b/src/gsy_e/gsy_e_core/simulation/time_manager.py
@@ -24,12 +24,11 @@
from typing import TYPE_CHECKING, Tuple
import pendulum
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, TIME_ZONE
from gsy_framework.enums import SpotMarketTypeEnum
from pendulum import DateTime, duration, now
import gsy_e.constants
-from gsy_e.constants import TIME_ZONE
if TYPE_CHECKING:
from gsy_e.models.area import Area, AreaBase
@@ -44,7 +43,8 @@ class TimeManagerBase:
@staticmethod
def _sleep_and_wake_up_if_stopped(
- sleep_time_s: float, status: "SimulationStatusManager") -> None:
+ sleep_time_s: float, status: "SimulationStatusManager"
+ ) -> None:
if sleep_time_s > 0:
start_time = time()
while time() - start_time < sleep_time_s and not status.stopped:
@@ -82,8 +82,12 @@ def _set_area_current_tick(self, area: "Area", current_tick: int) -> None:
self._set_area_current_tick(child, current_tick)
def calculate_total_initial_ticks_slots(
- self, config: "SimulationConfig", slot_resume: int, tick_resume: int, area: "AreaBase",
- status: "SimulationStatusManager"
+ self,
+ config: "SimulationConfig",
+ slot_resume: int,
+ tick_resume: int,
+ area: "AreaBase",
+ status: "SimulationStatusManager",
) -> Tuple[int, int, int]:
# pylint: disable = too-many-arguments
"""Calculate the initial slot and tick of the simulation, and the total slot count."""
@@ -109,13 +113,11 @@ def calculate_total_initial_ticks_slots(
self._sleep_and_wake_up_if_stopped(seconds_until_next_tick, status)
if self.slot_length_realtime:
- self.tick_length_realtime_s = (
- self.slot_length_realtime.seconds /
- config.ticks_per_slot)
+ self.tick_length_realtime_s = self.slot_length_realtime.seconds / config.ticks_per_slot
return slot_count, slot_resume, tick_resume
def handle_slowdown_and_realtime(
- self, tick_no: int, config: "SimulationConfig", status: "SimulationStatusManager"
+ self, tick_no: int, config: "SimulationConfig", status: "SimulationStatusManager"
) -> None:
"""
Handle simulation slowdown and simulation realtime mode, and sleep the simulation
@@ -133,8 +135,12 @@ def handle_slowdown_and_realtime(
return
if sleep_time_s > 0:
- log.debug("Tick %s/%s: Sleep time of %s s was applied",
- tick_no + 1, config.ticks_per_slot, sleep_time_s)
+ log.debug(
+ "Tick %s/%s: Sleep time of %s s was applied",
+ tick_no + 1,
+ config.ticks_per_slot,
+ sleep_time_s,
+ )
self.tick_time_counter = time()
@@ -142,6 +148,7 @@ def handle_slowdown_and_realtime(
@dataclass
class SimulationTimeManagerScm(TimeManagerBase):
"""Handles simulation time management."""
+
start_time: DateTime = None
paused_time: int = 0 # Time spent in paused state, in seconds
slot_length_realtime: duration = None
@@ -166,14 +173,19 @@ def reset(self, not_restored_from_state: bool = True) -> None:
self.paused_time = 0
def handle_slowdown_and_realtime_scm(
- self, slot_no: int, slot_count: int,
- config: "SimulationConfig", status: "SimulationStatusManager") -> None:
+ self,
+ slot_no: int,
+ slot_count: int,
+ config: "SimulationConfig",
+ status: "SimulationStatusManager",
+ ) -> None:
"""
Handle simulation slowdown and simulation realtime mode, and sleep the simulation
accordingly for SCM simulations.
"""
slot_length_realtime_s = (
- self.slot_length_realtime.total_seconds() if self.slot_length_realtime else None)
+ self.slot_length_realtime.total_seconds() if self.slot_length_realtime else None
+ )
if gsy_e.constants.RUN_IN_REALTIME:
slot_runtime_s = time() - self.slot_time_counter
@@ -187,8 +199,9 @@ def handle_slowdown_and_realtime_scm(
return
if sleep_time_s > 0:
- log.debug("Slot %s/%s: Sleep time of %s s was applied",
- slot_no, slot_count, sleep_time_s)
+ log.debug(
+ "Slot %s/%s: Sleep time of %s s was applied", slot_no, slot_count, sleep_time_s
+ )
self.slot_time_counter = int(time())
@@ -199,13 +212,13 @@ def get_start_time_on_init(config: "SimulationConfig") -> DateTime:
today = pendulum.today(tz=TIME_ZONE)
seconds_since_midnight = time() - today.int_timestamp
slot_no = int(seconds_since_midnight // config.slot_length.seconds) + 1
- start_time = config.start_date + duration(seconds=slot_no*config.slot_length.seconds)
+ start_time = config.start_date + duration(seconds=slot_no * config.slot_length.seconds)
else:
start_time = config.start_date
return start_time
def calc_resume_slot_and_count_realtime(
- self, config: "SimulationConfig", slot_resume: int, status: "SimulationStatusManager"
+ self, config: "SimulationConfig", slot_resume: int, status: "SimulationStatusManager"
) -> Tuple[int, int]:
"""Calculate total slot count and the slot where to resume the realtime simulation."""
slot_count = int(config.sim_duration / config.slot_length)
@@ -219,8 +232,12 @@ def calc_resume_slot_and_count_realtime(
seconds_elapsed_in_slot = seconds_since_midnight % config.slot_length.seconds
sleep_time_s = config.slot_length.total_seconds() - seconds_elapsed_in_slot
self._sleep_and_wake_up_if_stopped(sleep_time_s, status)
- log.debug("Resume Slot %s/%s: Sleep time of %s s was applied",
- slot_resume, slot_count, sleep_time_s)
+ log.debug(
+ "Resume Slot %s/%s: Sleep time of %s s was applied",
+ slot_resume,
+ slot_count,
+ sleep_time_s,
+ )
return slot_count, slot_resume
@@ -228,6 +245,7 @@ def calc_resume_slot_and_count_realtime(
def simulation_time_manager_factory(slot_length_realtime: duration, hours_of_delay: int):
"""Factory for time manager objects."""
if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.COEFFICIENTS.value:
- return SimulationTimeManagerScm(slot_length_realtime=slot_length_realtime,
- hours_of_delay=hours_of_delay)
+ return SimulationTimeManagerScm(
+ slot_length_realtime=slot_length_realtime, hours_of_delay=hours_of_delay
+ )
return SimulationTimeManager(slot_length_realtime=slot_length_realtime)
diff --git a/src/gsy_e/gsy_e_core/util.py b/src/gsy_e/gsy_e_core/util.py
index 3ef3c9066..768307036 100644
--- a/src/gsy_e/gsy_e_core/util.py
+++ b/src/gsy_e/gsy_e_core/util.py
@@ -29,7 +29,7 @@
from typing import TYPE_CHECKING, Optional
from click.types import ParamType
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig, RangeLimit
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, RangeLimit, DATE_FORMAT
from gsy_framework.enums import BidOfferMatchAlgoEnum
from gsy_framework.exceptions import GSyException
from gsy_framework.utils import (
@@ -103,15 +103,15 @@ class DateType(ParamType):
name = "date"
- def __init__(self, date_type: gsy_e.constants.DATE_FORMAT):
- if date_type == gsy_e.constants.DATE_FORMAT:
- self.allowed_formats = gsy_e.constants.DATE_FORMAT
+ def __init__(self, date_type: DATE_FORMAT):
+ if date_type == DATE_FORMAT:
+ self.allowed_formats = DATE_FORMAT
else:
- raise ValueError(f"Invalid date_type. Choices: {gsy_e.constants.DATE_FORMAT} ")
+ raise ValueError(f"Invalid date_type. Choices: {DATE_FORMAT} ")
def convert(self, value, param, ctx):
try:
- converted_format = from_format(value, gsy_e.constants.DATE_FORMAT)
+ converted_format = from_format(value, DATE_FORMAT)
except ValueError:
self.fail(f"'{value}' is not a valid date. Allowed formats: {self.allowed_formats}")
return converted_format
@@ -410,7 +410,7 @@ def export_default_settings_to_json_file():
"sim_duration": f"{GlobalConfig.DURATION_D * 24}h",
"slot_length": f"{GlobalConfig.SLOT_LENGTH_M}m",
"tick_length": f"{GlobalConfig.TICK_LENGTH_S}s",
- "start_date": instance(GlobalConfig.start_date).format(gsy_e.constants.DATE_FORMAT),
+ "start_date": instance(GlobalConfig.start_date).format(DATE_FORMAT),
}
all_settings = {"basic_settings": base_settings, "advanced_settings": constsettings_to_dict()}
settings_filename = os.path.join(gsye_root_path, "setup", "gsy_e_settings.json")
diff --git a/src/gsy_e/models/area/scm_dataclasses.py b/src/gsy_e/models/area/scm_dataclasses.py
index f009c4a42..a622425f0 100644
--- a/src/gsy_e/models/area/scm_dataclasses.py
+++ b/src/gsy_e/models/area/scm_dataclasses.py
@@ -4,12 +4,13 @@
from typing import Dict, List
from uuid import uuid4
+from pendulum import DateTime
+
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import Trade, TraderDetails
from gsy_framework.sim_results.kpi_calculation_helper import KPICalculationHelper
-from pendulum import DateTime
import gsy_e.constants
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
@dataclass
diff --git a/src/gsy_e/models/area/scm_manager.py b/src/gsy_e/models/area/scm_manager.py
index f14404cc3..c2eb19fe6 100644
--- a/src/gsy_e/models/area/scm_manager.py
+++ b/src/gsy_e/models/area/scm_manager.py
@@ -1,7 +1,7 @@
from math import isclose
from typing import TYPE_CHECKING, Dict, Optional
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.enums import SCMSelfConsumptionType
from pendulum import DateTime
@@ -9,7 +9,6 @@
from gsy_e.constants import (
DEFAULT_SCM_COMMUNITY_NAME,
DEFAULT_SCM_GRID_NAME,
- FLOATING_POINT_TOLERANCE,
)
from gsy_e.gsy_e_core.util import get_slots_per_month
from gsy_e.models.area.scm_dataclasses import (
diff --git a/src/gsy_e/models/config.py b/src/gsy_e/models/config.py
index 2ce61b6a4..f5af82427 100644
--- a/src/gsy_e/models/config.py
+++ b/src/gsy_e/models/config.py
@@ -15,20 +15,21 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import json
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, TIME_ZONE
from gsy_framework.exceptions import GSyException
from gsy_framework.read_user_profile import InputProfileTypes, read_arbitrary_profile
from pendulum import DateTime, Duration, duration, today
-from gsy_e.constants import TIME_ZONE
from gsy_e.gsy_e_core.redis_connections.area_market import external_redis_communicator_factory
from gsy_e.gsy_e_core.util import change_global_config, format_interval
class SimulationConfig:
"""Class defining parameters that describe the behavior of a simulation."""
+
# pylint: disable=too-many-instance-attributes, too-many-arguments
def __init__(
self,
@@ -73,11 +74,13 @@ def __init__(
if self.ticks_per_slot != int(self.ticks_per_slot):
raise GSyException(
f"Non integer ticks per slot ({self.ticks_per_slot}) are not supported. "
- "Adjust simulation parameters.")
+ "Adjust simulation parameters."
+ )
self.ticks_per_slot = int(self.ticks_per_slot)
if self.ticks_per_slot < 10:
raise GSyException(
- f"Too few ticks per slot ({self.ticks_per_slot}). Adjust simulation parameters")
+ f"Too few ticks per slot ({self.ticks_per_slot}). Adjust simulation parameters"
+ )
self.total_ticks = self.sim_duration // self.slot_length * self.ticks_per_slot
self.market_slot_list = []
@@ -88,7 +91,8 @@ def __init__(
self.capacity_kW = capacity_kW or ConstSettings.PVSettings.DEFAULT_CAPACITY_KW
self.external_connection_enabled = external_connection_enabled
self.external_redis_communicator = external_redis_communicator_factory(
- external_connection_enabled)
+ external_connection_enabled
+ )
if aggregator_device_mapping:
self.external_redis_communicator.aggregator.set_aggregator_device_mapping(
aggregator_device_mapping
@@ -100,17 +104,25 @@ def __repr__(self):
def as_dict(self):
"""Return config parameters as dict."""
- fields = {"sim_duration", "slot_length", "tick_length", "ticks_per_slot",
- "total_ticks", "capacity_kW", "grid_fee_type",
- "external_connection_enabled", "enable_degrees_of_freedom", "hours_of_delay"}
+ fields = {
+ "sim_duration",
+ "slot_length",
+ "tick_length",
+ "ticks_per_slot",
+ "total_ticks",
+ "capacity_kW",
+ "grid_fee_type",
+ "external_connection_enabled",
+ "enable_degrees_of_freedom",
+ "hours_of_delay",
+ }
return {
k: format_interval(v) if isinstance(v, Duration) else v
for k, v in self.__dict__.items()
if k in fields
}
- def update_config_parameters(self, *,
- market_maker_rate=None, capacity_kW=None):
+ def update_config_parameters(self, *, market_maker_rate=None, capacity_kW=None):
"""Update provided config parameters."""
if market_maker_rate is not None:
self.set_market_maker_rate(market_maker_rate)
@@ -122,7 +134,8 @@ def set_market_maker_rate(self, market_maker_rate):
Reads market_maker_rate from arbitrary input types
"""
self.market_maker_rate = read_arbitrary_profile(
- InputProfileTypes.IDENTITY, market_maker_rate)
+ InputProfileTypes.IDENTITY, market_maker_rate
+ )
def create_simulation_config_from_global_config():
@@ -141,5 +154,5 @@ def create_simulation_config_from_global_config():
market_maker_rate=GlobalConfig.market_maker_rate,
start_date=GlobalConfig.start_date,
grid_fee_type=GlobalConfig.grid_fee_type,
- enable_degrees_of_freedom=GlobalConfig.enable_degrees_of_freedom
+ enable_degrees_of_freedom=GlobalConfig.enable_degrees_of_freedom,
)
diff --git a/src/gsy_e/models/market/__init__.py b/src/gsy_e/models/market/__init__.py
index 5251b1ad4..395391e52 100644
--- a/src/gsy_e/models/market/__init__.py
+++ b/src/gsy_e/models/market/__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 uuid
from collections import namedtuple
from dataclasses import dataclass
@@ -23,20 +24,26 @@
from threading import RLock
from typing import Dict, List, Union, Optional, Callable, TYPE_CHECKING
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import (
+ ConstSettings,
+ GlobalConfig,
+ FLOATING_POINT_TOLERANCE,
+ DATE_TIME_FORMAT,
+)
from gsy_framework.data_classes import Offer, Trade, Bid
from gsy_framework.enums import SpotMarketTypeEnum
from numpy.random import random
from pendulum import DateTime, duration
-from gsy_e.constants import FLOATING_POINT_TOLERANCE, DATE_TIME_FORMAT
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
from gsy_e.gsy_e_core.util import add_or_create_key, subtract_or_create_key
from gsy_e.models.market.grid_fees.base_model import GridFees
from gsy_e.models.market.grid_fees.constant_grid_fees import ConstantGridFees
from gsy_e.models.market.market_redis_connection import (
- MarketRedisEventSubscriber, MarketRedisEventPublisher,
- TwoSidedMarketRedisEventSubscriber)
+ MarketRedisEventSubscriber,
+ MarketRedisEventPublisher,
+ TwoSidedMarketRedisEventSubscriber,
+)
if TYPE_CHECKING:
from gsy_e.models.config import SimulationConfig
@@ -61,12 +68,14 @@ def wrapper(self, *args, **kwargs):
with lock_object:
return function(self, *args, **kwargs)
+
return wrapper
@dataclass(frozen=True)
class MarketSlotParams:
"""Parameters that describe a market slot."""
+
opening_time: DateTime
closing_time: DateTime
delivery_start_time: DateTime
@@ -87,10 +96,15 @@ class MarketBase: # pylint: disable=too-many-instance-attributes
"""
def __init__( # pylint: disable=too-many-arguments
- self, time_slot: Optional[DateTime] = None, bc=None,
- notification_listener: Optional[Callable] = None, readonly: bool = False,
- grid_fee_type: int = ConstSettings.MASettings.GRID_FEE_TYPE,
- grid_fees: Optional[GridFee] = None, name: Optional[str] = None):
+ self,
+ time_slot: Optional[DateTime] = None,
+ bc=None,
+ notification_listener: Optional[Callable] = None,
+ readonly: bool = False,
+ grid_fee_type: int = ConstSettings.MASettings.GRID_FEE_TYPE,
+ grid_fees: Optional[GridFee] = None,
+ name: Optional[str] = None,
+ ):
self.name = name
self.bc_interface = bc
self.id = str(uuid.uuid4())
@@ -125,7 +139,8 @@ def __init__( # pylint: disable=too-many-arguments
self.redis_api = (
MarketRedisEventSubscriber(self)
if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.ONE_SIDED.value
- else TwoSidedMarketRedisEventSubscriber(self))
+ else TwoSidedMarketRedisEventSubscriber(self)
+ )
setattr(self, RLOCK_MEMBER_NAME, RLock())
self._open_market_slot_parameters: Dict[DateTime, MarketSlotParams] = {}
@@ -145,7 +160,8 @@ def info(self) -> Dict:
"start_time": self.time_slot_str,
"duration_min": GlobalConfig.slot_length.minutes,
"time_slots": [self.time_slot_str], # Use a list for compatibility with future markets
- "type_name": self.type_name}
+ "type_name": self.type_name,
+ }
@property
def type_name(self) -> str:
@@ -173,8 +189,9 @@ def most_affordable_offers(self):
"""Return the offers with the least energy_rate value."""
cheapest_offer = self.sorted_offers[0]
rate = cheapest_offer.energy_rate
- return [o for o in self.sorted_offers if
- abs(o.energy_rate - rate) < FLOATING_POINT_TOLERANCE]
+ return [
+ o for o in self.sorted_offers if abs(o.energy_rate - rate) < FLOATING_POINT_TOLERANCE
+ ]
def _create_fee_handler(self, grid_fee_type: int, grid_fees: GridFee) -> None:
if not grid_fees:
@@ -216,16 +233,15 @@ def _notify_listeners(self, event, **kwargs):
for listener in sorted(self.notification_listeners, key=lambda l: random()):
listener(event, market_id=self.id, **kwargs)
- def _update_stats_after_trade(
- self, trade: Trade, order: Union[Offer, Bid]) -> None:
+ def _update_stats_after_trade(self, trade: Trade, order: Union[Offer, Bid]) -> None:
"""Update the instance state in response to an occurring trade."""
self.trades.append(trade)
self.market_fee += trade.fee_price
self._update_accumulated_trade_price_energy(trade)
- self.traded_energy = add_or_create_key(
- self.traded_energy, trade.seller.name, order.energy)
+ self.traded_energy = add_or_create_key(self.traded_energy, trade.seller.name, order.energy)
self.traded_energy = subtract_or_create_key(
- self.traded_energy, trade.buyer.name, order.energy)
+ self.traded_energy, trade.buyer.name, order.energy
+ )
self._update_min_max_avg_trade_prices(order.energy_rate)
def _update_accumulated_trade_price_energy(self, trade: Trade):
@@ -233,10 +249,12 @@ def _update_accumulated_trade_price_energy(self, trade: Trade):
self.accumulated_trade_energy += trade.traded_energy
def _update_min_max_avg_trade_prices(self, price):
- self.max_trade_price = round(
- max(self.max_trade_price, price), 4) if self.max_trade_price else round(price, 4)
- self.min_trade_price = round(
- min(self.min_trade_price, price), 4) if self.min_trade_price else round(price, 4)
+ self.max_trade_price = (
+ round(max(self.max_trade_price, price), 4) if self.max_trade_price else round(price, 4)
+ )
+ self.min_trade_price = (
+ round(min(self.min_trade_price, price), 4) if self.min_trade_price else round(price, 4)
+ )
self._avg_trade_price = None
def __repr__(self):
@@ -245,19 +263,16 @@ def __repr__(self):
f" (E: {sum(o.energy for o in self.offers.values())} kWh"
f" V: {sum(o.price for o in self.offers.values())})"
f" trades: {len(self.trades)} (E: {self.accumulated_trade_energy} kWh,"
- f" V: {self.accumulated_trade_price})>")
+ f" V: {self.accumulated_trade_price})>"
+ )
@staticmethod
def sorting(offers_bids: Dict, reverse_order=False) -> List[Union[Bid, Offer]]:
"""Sort a list of bids or offers by their energy_rate attribute."""
if reverse_order:
# Sorted bids in descending order
- return list(reversed(sorted(
- offers_bids.values(),
- key=lambda obj: obj.energy_rate)))
- return sorted(offers_bids.values(),
- key=lambda obj: obj.energy_rate,
- reverse=reverse_order)
+ return list(reversed(sorted(offers_bids.values(), key=lambda obj: obj.energy_rate)))
+ return sorted(offers_bids.values(), key=lambda obj: obj.energy_rate, reverse=reverse_order)
def update_clock(self, now: DateTime) -> None:
"""
@@ -302,8 +317,7 @@ def _get_market_slot_duration(config: Optional["SimulationConfig"]) -> duration:
return config.slot_length
return GlobalConfig.slot_length
- def get_market_parameters_for_market_slot(
- self, market_slot: DateTime) -> MarketSlotParams:
+ def get_market_parameters_for_market_slot(self, market_slot: DateTime) -> MarketSlotParams:
"""Retrieve the parameters for the selected market slot."""
return self._open_market_slot_parameters.get(market_slot)
@@ -313,7 +327,8 @@ def open_market_slot_info(self) -> Dict[DateTime, MarketSlotParams]:
return self._open_market_slot_parameters
def set_open_market_slot_parameters(
- self, current_market_slot: DateTime, created_market_slots: Optional[List[DateTime]]):
+ self, current_market_slot: DateTime, created_market_slots: Optional[List[DateTime]]
+ ):
"""Update the parameters of the newly opened market slots."""
for market_slot in created_market_slots:
if market_slot in self._open_market_slot_parameters:
@@ -321,8 +336,8 @@ def set_open_market_slot_parameters(
self._open_market_slot_parameters[market_slot] = MarketSlotParams(
delivery_start_time=self._calculate_closing_time(market_slot),
- delivery_end_time=self._calculate_closing_time(market_slot) +
- self._get_market_slot_duration(None),
+ delivery_end_time=self._calculate_closing_time(market_slot)
+ + self._get_market_slot_duration(None),
opening_time=current_market_slot,
- closing_time=self._calculate_closing_time(market_slot)
+ closing_time=self._calculate_closing_time(market_slot),
)
diff --git a/src/gsy_e/models/market/balancing.py b/src/gsy_e/models/market/balancing.py
index 7060f4fc8..d8634e450 100644
--- a/src/gsy_e/models/market/balancing.py
+++ b/src/gsy_e/models/market/balancing.py
@@ -15,21 +15,30 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import uuid
from logging import getLogger
from typing import Union, Dict, List, Optional # noqa
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import (
- BalancingOffer, BalancingTrade, Offer, TraderDetails, TradeBidOfferInfo)
+ BalancingOffer,
+ BalancingTrade,
+ Offer,
+ TraderDetails,
+ TradeBidOfferInfo,
+)
from pendulum import DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.events.event_structures import MarketEvent
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
from gsy_e.gsy_e_core.exceptions import (
- InvalidOffer, MarketReadOnlyException,
- OfferNotFoundException, InvalidBalancingTradeException, DeviceNotInRegistryError)
+ InvalidOffer,
+ MarketReadOnlyException,
+ OfferNotFoundException,
+ InvalidBalancingTradeException,
+ DeviceNotInRegistryError,
+)
from gsy_e.gsy_e_core.util import short_offer_bid_log_str
from gsy_e.models.market.one_sided import OneSidedMarket
@@ -40,9 +49,16 @@ class BalancingMarket(OneSidedMarket):
"""Market that regulates the supply and demand of energy."""
def __init__( # pylint: disable=too-many-arguments
- self, time_slot=None, bc=None, notification_listener=None, readonly=False,
- grid_fee_type=ConstSettings.MASettings.GRID_FEE_TYPE,
- grid_fees=None, name=None, in_sim_duration=True):
+ self,
+ time_slot=None,
+ bc=None,
+ notification_listener=None,
+ readonly=False,
+ grid_fee_type=ConstSettings.MASettings.GRID_FEE_TYPE,
+ grid_fees=None,
+ name=None,
+ in_sim_duration=True,
+ ):
self.unmatched_energy_upward = 0
self.unmatched_energy_downward = 0
self.accumulated_supply_balancing_trade_price = 0
@@ -50,28 +66,48 @@ def __init__( # pylint: disable=too-many-arguments
self.accumulated_demand_balancing_trade_price = 0
self.accumulated_demand_balancing_trade_energy = 0
- super().__init__(time_slot, bc, notification_listener, readonly, grid_fee_type,
- grid_fees, name, in_sim_duration=in_sim_duration)
+ super().__init__(
+ time_slot,
+ bc,
+ notification_listener,
+ readonly,
+ grid_fee_type,
+ grid_fees,
+ name,
+ in_sim_duration=in_sim_duration,
+ )
def offer( # pylint: disable=too-many-arguments
- self, price: float, energy: float, seller: TraderDetails,
- offer_id: Optional[str] = None,
- original_price: Optional[float] = None,
- dispatch_event: bool = True,
- adapt_price_with_fees: bool = True,
- add_to_history: bool = True,
- time_slot: Optional[DateTime] = None):
+ self,
+ price: float,
+ energy: float,
+ seller: TraderDetails,
+ offer_id: Optional[str] = None,
+ original_price: Optional[float] = None,
+ dispatch_event: bool = True,
+ adapt_price_with_fees: bool = True,
+ add_to_history: bool = True,
+ time_slot: Optional[DateTime] = None,
+ ):
assert False
def balancing_offer( # pylint: disable=too-many-arguments
- self, price: float, energy: float, seller: TraderDetails,
- original_price=None, offer_id=None, from_agent: bool = False,
- adapt_price_with_fees: bool = False, dispatch_event=True) -> BalancingOffer:
+ self,
+ price: float,
+ energy: float,
+ seller: TraderDetails,
+ original_price=None,
+ offer_id=None,
+ from_agent: bool = False,
+ adapt_price_with_fees: bool = False,
+ dispatch_event=True,
+ ) -> BalancingOffer:
"""Create a balancing offer."""
if seller.name not in DeviceRegistry.REGISTRY.keys() and not from_agent:
- raise DeviceNotInRegistryError(f"Device {seller.name} "
- f"not in registry ({DeviceRegistry.REGISTRY}).")
+ raise DeviceNotInRegistryError(
+ f"Device {seller.name} " f"not in registry ({DeviceRegistry.REGISTRY})."
+ )
if self.readonly:
raise MarketReadOnlyException()
if energy == 0:
@@ -85,9 +121,7 @@ def balancing_offer( # pylint: disable=too-many-arguments
if offer_id is None:
offer_id = str(uuid.uuid4())
- offer = BalancingOffer(
- offer_id, self.now, price, energy, seller,
- time_slot=self.time_slot)
+ offer = BalancingOffer(offer_id, self.now, price, energy, seller, time_slot=self.time_slot)
self.offers[offer.id] = offer
self.offer_history.append(offer)
@@ -101,33 +135,41 @@ def split_offer(self, original_offer, energy, orig_offer_price=None):
self.offers.pop(original_offer.id, None)
# same offer id is used for the new accepted_offer
- accepted_offer = self.balancing_offer(offer_id=original_offer.id,
- price=original_offer.price *
- (energy / original_offer.energy),
- energy=energy,
- seller=original_offer.seller,
- dispatch_event=False,
- from_agent=True)
+ accepted_offer = self.balancing_offer(
+ offer_id=original_offer.id,
+ price=original_offer.price * (energy / original_offer.energy),
+ energy=energy,
+ seller=original_offer.seller,
+ dispatch_event=False,
+ from_agent=True,
+ )
residual_price = (1 - energy / original_offer.energy) * original_offer.price
residual_energy = original_offer.energy - energy
if orig_offer_price is None:
orig_offer_price = original_offer.original_price or original_offer.price
original_residual_price = (
- ((original_offer.energy - energy) / original_offer.energy) * orig_offer_price)
-
- residual_offer = self.balancing_offer(price=residual_price,
- energy=residual_energy,
- seller=original_offer.seller,
- original_price=original_residual_price,
- dispatch_event=False,
- adapt_price_with_fees=False,
- from_agent=True)
+ (original_offer.energy - energy) / original_offer.energy
+ ) * orig_offer_price
+
+ residual_offer = self.balancing_offer(
+ price=residual_price,
+ energy=residual_energy,
+ seller=original_offer.seller,
+ original_price=original_residual_price,
+ dispatch_event=False,
+ adapt_price_with_fees=False,
+ from_agent=True,
+ )
log.debug(
"[BALANCING_OFFER][SPLIT][%s, %s] (%s into %s and %s",
- self.time_slot_str, self.name, short_offer_bid_log_str(original_offer),
- short_offer_bid_log_str(accepted_offer), short_offer_bid_log_str(residual_offer))
+ self.time_slot_str,
+ self.name,
+ short_offer_bid_log_str(original_offer),
+ short_offer_bid_log_str(accepted_offer),
+ short_offer_bid_log_str(residual_offer),
+ )
self.bc_interface.change_offer(accepted_offer, original_offer, residual_offer)
@@ -135,20 +177,26 @@ def split_offer(self, original_offer, energy, orig_offer_price=None):
MarketEvent.BALANCING_OFFER_SPLIT,
original_offer=original_offer,
accepted_offer=accepted_offer,
- residual_offer=residual_offer)
+ residual_offer=residual_offer,
+ )
return accepted_offer, residual_offer
def _determine_offer_price(
- self, energy_portion, energy, trade_rate,
- trade_bid_info, orig_offer_price):
+ self, energy_portion, energy, trade_rate, trade_bid_info, orig_offer_price
+ ):
return self._update_offer_fee_and_calculate_final_price(
- energy, trade_rate, energy_portion, orig_offer_price)
+ energy, trade_rate, energy_portion, orig_offer_price
+ )
def accept_offer( # pylint: disable=too-many-locals
- self, offer_or_id: Union[str, BalancingOffer], buyer: TraderDetails, *,
- energy: int = None,
- trade_bid_info: Optional[TradeBidOfferInfo] = None) -> BalancingTrade:
+ self,
+ offer_or_id: Union[str, BalancingOffer],
+ buyer: TraderDetails,
+ *,
+ energy: int = None,
+ trade_bid_info: Optional[TradeBidOfferInfo] = None,
+ ) -> BalancingTrade:
if self.readonly:
raise MarketReadOnlyException()
@@ -159,8 +207,7 @@ def accept_offer( # pylint: disable=too-many-locals
raise OfferNotFoundException()
if (offer.energy > 0 > energy) or (offer.energy < 0 < energy):
- raise InvalidBalancingTradeException("BalancingOffer and energy "
- "are not compatible")
+ raise InvalidBalancingTradeException("BalancingOffer and energy " "are not compatible")
if energy is None:
energy = offer.energy
@@ -183,13 +230,15 @@ def accept_offer( # pylint: disable=too-many-locals
accepted_offer, residual_offer = self.split_offer(offer, energy, orig_offer_price)
fees, trade_price = self._determine_offer_price(
- energy / offer.energy, energy, trade_rate, trade_bid_info, orig_offer_price)
+ energy / offer.energy, energy, trade_rate, trade_bid_info, orig_offer_price
+ )
offer = accepted_offer
offer.update_price(trade_price)
elif abs(energy - offer.energy) > FLOATING_POINT_TOLERANCE:
raise InvalidBalancingTradeException(
- f"Energy ({energy}) can't be greater than offered energy ({offer.energy}).")
+ f"Energy ({energy}) can't be greater than offered energy ({offer.energy})."
+ )
else:
# Requested energy is equal to offer's energy - just proceed normally
fees, trade_price = self._update_offer_fee_and_calculate_final_price(
@@ -206,14 +255,20 @@ def accept_offer( # pylint: disable=too-many-locals
self.offers.pop(offer.id, None)
trade_id, residual_offer = self.bc_interface.handle_blockchain_trade_event(
- offer, buyer, original_offer, residual_offer)
- trade = BalancingTrade(trade_id, self.now, offer.seller,
- buyer=buyer,
- offer=offer,
- traded_energy=energy, trade_price=trade_price,
- residual=residual_offer,
- fee_price=fees,
- time_slot=offer.time_slot)
+ offer, buyer, original_offer, residual_offer
+ )
+ trade = BalancingTrade(
+ trade_id,
+ self.now,
+ offer.seller,
+ buyer=buyer,
+ offer=offer,
+ traded_energy=energy,
+ trade_price=trade_price,
+ residual=residual_offer,
+ fee_price=fees,
+ time_slot=offer.time_slot,
+ )
self.bc_interface.track_trade_event(self.time_slot, trade)
if offer.seller != buyer:
diff --git a/src/gsy_e/models/market/one_sided.py b/src/gsy_e/models/market/one_sided.py
index 743dccaeb..a107367b1 100644
--- a/src/gsy_e/models/market/one_sided.py
+++ b/src/gsy_e/models/market/one_sided.py
@@ -15,22 +15,26 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from copy import deepcopy
from logging import getLogger
from math import isclose
from typing import Union, Dict, Optional, Callable, Tuple
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import Offer, Trade, TradeBidOfferInfo, TraderDetails, Bid
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.utils import limit_float_precision
from pendulum import DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.events.event_structures import MarketEvent
from gsy_e.gsy_e_core.exceptions import (
- MarketReadOnlyException, OfferNotFoundException, InvalidTrade,
- NegativePriceOrdersException, NegativeEnergyOrderException)
+ MarketReadOnlyException,
+ OfferNotFoundException,
+ InvalidTrade,
+ NegativePriceOrdersException,
+ NegativeEnergyOrderException,
+)
from gsy_e.gsy_e_core.util import short_offer_bid_log_str
from gsy_e.models.market import MarketBase, lock_market_action, GridFee
@@ -43,15 +47,22 @@ class OneSidedMarket(MarketBase):
The default market type that D3A simulation uses.
Only devices that supply energy (producers) are able to place offers on the markets.
"""
+
def __init__( # pylint: disable=too-many-arguments
- self, time_slot: Optional[DateTime] = None,
- bc=None, notification_listener: Optional[Callable] = None,
- readonly: bool = False, grid_fee_type=ConstSettings.MASettings.GRID_FEE_TYPE,
- grid_fees: Optional[GridFee] = None, name: Optional[str] = None,
- in_sim_duration: bool = True):
+ self,
+ time_slot: Optional[DateTime] = None,
+ bc=None,
+ notification_listener: Optional[Callable] = None,
+ readonly: bool = False,
+ grid_fee_type=ConstSettings.MASettings.GRID_FEE_TYPE,
+ grid_fees: Optional[GridFee] = None,
+ name: Optional[str] = None,
+ in_sim_duration: bool = True,
+ ):
assert ConstSettings.MASettings.MARKET_TYPE != SpotMarketTypeEnum.COEFFICIENTS.value
- super().__init__(time_slot, bc, notification_listener, readonly, grid_fee_type,
- grid_fees, name)
+ super().__init__(
+ time_slot, bc, notification_listener, readonly, grid_fee_type, grid_fees, name
+ )
# If True, the current market slot is included in the expected duration of the simulation
self.in_sim_duration = in_sim_duration
@@ -62,7 +73,8 @@ def __repr__(self):
f" offers: {len(self.offers)} (E: {sum(o.energy for o in self.offers.values())} kWh"
f" V: {sum(o.price for o in self.offers.values())})"
f" trades: {len(self.trades)} (E: {self.accumulated_trade_energy} kWh,"
- f" V: {self.accumulated_trade_price})>")
+ f" V: {self.accumulated_trade_price})>"
+ )
@property
def _class_name(self) -> str:
@@ -81,9 +93,13 @@ def _update_new_offer_price_with_fee(self, price: float, original_price: float,
:param energy: Energy of the offer
:return: Updated price for the forwarded offer on this market, in cents
"""
- return self.fee_class.update_incoming_offer_with_fee(
- limit_float_precision(price / energy),
- limit_float_precision(original_price / energy)) * energy
+ return (
+ self.fee_class.update_incoming_offer_with_fee(
+ limit_float_precision(price / energy),
+ limit_float_precision(original_price / energy),
+ )
+ * energy
+ )
@lock_market_action
def get_offers(self) -> Dict:
@@ -99,13 +115,17 @@ def get_offers(self) -> Dict:
@lock_market_action
def offer( # pylint: disable=too-many-arguments, too-many-locals
- self, price: float, energy: float, seller: TraderDetails,
- offer_id: Optional[str] = None,
- original_price: Optional[float] = None,
- dispatch_event: bool = True,
- adapt_price_with_fees: bool = True,
- add_to_history: bool = True,
- time_slot: Optional[DateTime] = None) -> Offer:
+ self,
+ price: float,
+ energy: float,
+ seller: TraderDetails,
+ offer_id: Optional[str] = None,
+ original_price: Optional[float] = None,
+ dispatch_event: bool = True,
+ adapt_price_with_fees: bool = True,
+ add_to_history: bool = True,
+ time_slot: Optional[DateTime] = None,
+ ) -> Offer:
"""Post offer inside the market."""
if self.readonly:
@@ -123,21 +143,26 @@ def offer( # pylint: disable=too-many-arguments, too-many-locals
if price < 0.0:
raise NegativePriceOrdersException(
- "Negative price after taxes, offer cannot be posted.")
+ "Negative price after taxes, offer cannot be posted."
+ )
if offer_id is None:
offer_id = self.bc_interface.create_new_offer(energy, price, seller)
- offer = Offer(offer_id, self.now, price, energy,
- seller, original_price,
- time_slot=time_slot)
+ offer = Offer(
+ offer_id, self.now, price, energy, seller, original_price, time_slot=time_slot
+ )
self.offers[offer.id] = offer
if add_to_history is True:
self.offer_history.append(offer)
- log.debug("%s[OFFER][NEW][%s][%s] %s",
- self._debug_log_market_type_identifier, self.name,
- self.time_slot_str or offer.time_slot, offer)
+ log.debug(
+ "%s[OFFER][NEW][%s][%s] %s",
+ self._debug_log_market_type_identifier,
+ self.name,
+ self.time_slot_str or offer.time_slot,
+ offer,
+ )
if dispatch_event is True:
self.dispatch_market_offer_event(offer)
self.no_new_order = False
@@ -161,57 +186,72 @@ def delete_offer(self, offer_or_id: Union[str, Offer]) -> None:
raise OfferNotFoundException()
self.bc_interface.cancel_offer(offer)
- log.debug("%s[OFFER][DEL][%s][%s] %s",
- self._debug_log_market_type_identifier, self.name,
- self.time_slot_str or offer.time_slot, offer)
+ log.debug(
+ "%s[OFFER][DEL][%s][%s] %s",
+ self._debug_log_market_type_identifier,
+ self.name,
+ self.time_slot_str or offer.time_slot,
+ offer,
+ )
self._notify_listeners(MarketEvent.OFFER_DELETED, offer=offer)
- def _update_offer_fee_and_calculate_final_price(self, energy, trade_rate,
- energy_portion, original_price):
+ def _update_offer_fee_and_calculate_final_price(
+ self, energy, trade_rate, energy_portion, original_price
+ ):
if self._is_constant_fees:
fees = self.fee_class.grid_fee_rate * energy
else:
fees = self.fee_class.grid_fee_rate * original_price * energy_portion
return fees, energy * trade_rate
- def split_offer(self, original_offer: Offer, energy: float,
- orig_offer_price: float) -> Tuple[Offer, Offer]:
+ def split_offer(
+ self, original_offer: Offer, energy: float, orig_offer_price: float
+ ) -> Tuple[Offer, Offer]:
"""Split offer into two, one with provided energy, the other with the residual."""
self.offers.pop(original_offer.id, None)
# same offer id is used for the new accepted_offer
original_accepted_price = energy / original_offer.energy * orig_offer_price
- accepted_offer = self.offer(offer_id=original_offer.id,
- price=original_offer.price * (energy / original_offer.energy),
- energy=energy,
- seller=original_offer.seller,
- original_price=original_accepted_price,
- dispatch_event=False,
- adapt_price_with_fees=False,
- add_to_history=False,
- time_slot=original_offer.time_slot)
+ accepted_offer = self.offer(
+ offer_id=original_offer.id,
+ price=original_offer.price * (energy / original_offer.energy),
+ energy=energy,
+ seller=original_offer.seller,
+ original_price=original_accepted_price,
+ dispatch_event=False,
+ adapt_price_with_fees=False,
+ add_to_history=False,
+ time_slot=original_offer.time_slot,
+ )
residual_price = (1 - energy / original_offer.energy) * original_offer.price
residual_energy = original_offer.energy - energy
- original_residual_price = ((original_offer.energy - energy) /
- original_offer.energy) * orig_offer_price
-
- residual_offer = self.offer(price=residual_price,
- energy=residual_energy,
- seller=original_offer.seller,
- original_price=original_residual_price,
- dispatch_event=False,
- adapt_price_with_fees=False,
- add_to_history=True,
- time_slot=original_offer.time_slot)
-
- log.debug("%s[OFFER][SPLIT][%s, %s] (%s into %s and %s",
- self._debug_log_market_type_identifier,
- self.time_slot_str or residual_offer.time_slot, self.name,
- short_offer_bid_log_str(original_offer), short_offer_bid_log_str(accepted_offer),
- short_offer_bid_log_str(residual_offer))
+ original_residual_price = (
+ (original_offer.energy - energy) / original_offer.energy
+ ) * orig_offer_price
+
+ residual_offer = self.offer(
+ price=residual_price,
+ energy=residual_energy,
+ seller=original_offer.seller,
+ original_price=original_residual_price,
+ dispatch_event=False,
+ adapt_price_with_fees=False,
+ add_to_history=True,
+ time_slot=original_offer.time_slot,
+ )
+
+ log.debug(
+ "%s[OFFER][SPLIT][%s, %s] (%s into %s and %s",
+ self._debug_log_market_type_identifier,
+ self.time_slot_str or residual_offer.time_slot,
+ self.name,
+ short_offer_bid_log_str(original_offer),
+ short_offer_bid_log_str(accepted_offer),
+ short_offer_bid_log_str(residual_offer),
+ )
self.bc_interface.change_offer(accepted_offer, original_offer, residual_offer)
@@ -219,13 +259,14 @@ def split_offer(self, original_offer: Offer, energy: float,
MarketEvent.OFFER_SPLIT,
original_offer=original_offer,
accepted_offer=accepted_offer,
- residual_offer=residual_offer)
+ residual_offer=residual_offer,
+ )
return accepted_offer, residual_offer
def _determine_offer_price( # pylint: disable=too-many-arguments
- self, energy_portion, energy, trade_rate,
- trade_bid_info, orig_offer_price):
+ self, energy_portion, energy, trade_rate, trade_bid_info, orig_offer_price
+ ):
if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.ONE_SIDED.value:
return self._update_offer_fee_and_calculate_final_price(
energy, trade_rate, energy_portion, orig_offer_price
@@ -233,19 +274,24 @@ def _determine_offer_price( # pylint: disable=too-many-arguments
if not trade_bid_info:
# If trade bid info is not populated, return zero grid fees
- return 0., energy * trade_rate
+ return 0.0, energy * trade_rate
_, grid_fee_rate, trade_rate_incl_fees = self.fee_class.calculate_trade_price_and_fees(
- trade_bid_info)
+ trade_bid_info
+ )
grid_fee_price = grid_fee_rate * energy
return grid_fee_price, energy * trade_rate_incl_fees
@lock_market_action
def accept_offer( # pylint: disable=too-many-locals
- self, offer_or_id: Union[str, Offer], buyer: TraderDetails, *,
- energy: Optional[float] = None,
- trade_bid_info: Optional[TradeBidOfferInfo] = None,
- bid: Optional[Bid] = None) -> Trade:
+ self,
+ offer_or_id: Union[str, Offer],
+ buyer: TraderDetails,
+ *,
+ energy: Optional[float] = None,
+ trade_bid_info: Optional[TradeBidOfferInfo] = None,
+ bid: Optional[Bid] = None,
+ ) -> Trade:
"""Accept an offer and create a Trade."""
if self.readonly:
@@ -279,20 +325,25 @@ def accept_offer( # pylint: disable=too-many-locals
accepted_offer, residual_offer = self.split_offer(offer, energy, orig_offer_price)
fee_price, trade_price = self._determine_offer_price(
- energy_portion=energy / accepted_offer.energy, energy=energy,
- trade_rate=trade_rate, trade_bid_info=trade_bid_info,
- orig_offer_price=orig_offer_price)
+ energy_portion=energy / accepted_offer.energy,
+ energy=energy,
+ trade_rate=trade_rate,
+ trade_bid_info=trade_bid_info,
+ orig_offer_price=orig_offer_price,
+ )
offer = accepted_offer
offer.update_price(trade_price)
elif (offer.energy - energy) < -FLOATING_POINT_TOLERANCE:
- raise InvalidTrade(f"Energy ({energy}) can't be greater than "
- f"offered energy ({offer.energy})")
+ raise InvalidTrade(
+ f"Energy ({energy}) can't be greater than " f"offered energy ({offer.energy})"
+ )
else:
# Requested energy is equal to offer's energy - just proceed normally
fee_price, trade_price = self._determine_offer_price(
- 1, energy, trade_rate, trade_bid_info, orig_offer_price)
+ 1, energy, trade_rate, trade_bid_info, orig_offer_price
+ )
offer.update_price(trade_price)
except Exception as ex:
# Exception happened - restore offer
@@ -301,26 +352,40 @@ def accept_offer( # pylint: disable=too-many-locals
raise
trade_id, residual_offer = self.bc_interface.handle_blockchain_trade_event(
- offer, buyer, original_offer, residual_offer)
+ offer, buyer, original_offer, residual_offer
+ )
# Delete the accepted offer from self.offers:
self.offers.pop(offer.id, None)
offer_bid_trade_info = self.fee_class.propagate_original_bid_info_on_offer_trade(
- trade_original_info=trade_bid_info)
-
- trade = Trade(trade_id, self.now, offer.seller,
- buyer=buyer,
- offer=offer,
- bid=bid,
- traded_energy=energy, trade_price=trade_price, residual=residual_offer,
- offer_bid_trade_info=offer_bid_trade_info,
- fee_price=fee_price, time_slot=offer.time_slot)
+ trade_original_info=trade_bid_info
+ )
+
+ trade = Trade(
+ trade_id,
+ self.now,
+ offer.seller,
+ buyer=buyer,
+ offer=offer,
+ bid=bid,
+ traded_energy=energy,
+ trade_price=trade_price,
+ residual=residual_offer,
+ offer_bid_trade_info=offer_bid_trade_info,
+ fee_price=fee_price,
+ time_slot=offer.time_slot,
+ )
self.bc_interface.track_trade_event(self.time_slot, trade)
self._update_stats_after_trade(trade, offer)
- log.info("%s[TRADE][OFFER] [%s] [%s] %s",
- self._debug_log_market_type_identifier, self.name, trade.time_slot, trade)
+ log.info(
+ "%s[TRADE][OFFER] [%s] [%s] %s",
+ self._debug_log_market_type_identifier,
+ self.name,
+ trade.time_slot,
+ trade,
+ )
self._notify_listeners(MarketEvent.OFFER_TRADED, trade=trade)
return trade
diff --git a/src/gsy_e/models/market/two_sided.py b/src/gsy_e/models/market/two_sided.py
index c7769bb97..66c37cd45 100644
--- a/src/gsy_e/models/market/two_sided.py
+++ b/src/gsy_e/models/market/two_sided.py
@@ -15,25 +15,36 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import uuid
from copy import deepcopy
from logging import getLogger
from math import isclose
from typing import Dict, List, Union, Tuple, Optional
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import (
- Bid, Offer, Trade, TradeBidOfferInfo, BidOfferMatch, TraderDetails)
+ Bid,
+ Offer,
+ Trade,
+ TradeBidOfferInfo,
+ BidOfferMatch,
+ TraderDetails,
+)
from gsy_framework.enums import BidOfferMatchAlgoEnum
from gsy_framework.matching_algorithms.requirements_validators import RequirementsSatisfiedChecker
from gsy_framework.utils import limit_float_precision
from pendulum import DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.events.event_structures import MarketEvent
from gsy_e.gsy_e_core.exceptions import (
- BidNotFoundException, InvalidBidOfferPairException, InvalidTrade,
- NegativePriceOrdersException, NegativeEnergyOrderException, NegativeEnergyTradeException)
+ BidNotFoundException,
+ InvalidBidOfferPairException,
+ InvalidTrade,
+ NegativePriceOrdersException,
+ NegativeEnergyOrderException,
+ NegativeEnergyTradeException,
+)
from gsy_e.gsy_e_core.util import short_offer_bid_log_str, is_external_matching_enabled
from gsy_e.models.market import lock_market_action
from gsy_e.models.market.one_sided import OneSidedMarket
@@ -51,25 +62,43 @@ class TwoSidedMarket(OneSidedMarket):
the offers and bids are being matched via some matching algorithm.
"""
- def __init__(self, time_slot=None, bc=None, notification_listener=None, readonly=False,
- grid_fee_type=ConstSettings.MASettings.GRID_FEE_TYPE,
- grid_fees=None, name=None, in_sim_duration=True):
+ def __init__(
+ self,
+ time_slot=None,
+ bc=None,
+ notification_listener=None,
+ readonly=False,
+ grid_fee_type=ConstSettings.MASettings.GRID_FEE_TYPE,
+ grid_fees=None,
+ name=None,
+ in_sim_duration=True,
+ ):
# pylint: disable=too-many-arguments
- super().__init__(time_slot, bc, notification_listener, readonly, grid_fee_type,
- grid_fees, name, in_sim_duration=in_sim_duration)
+ super().__init__(
+ time_slot,
+ bc,
+ notification_listener,
+ readonly,
+ grid_fee_type,
+ grid_fees,
+ name,
+ in_sim_duration=in_sim_duration,
+ )
@property
def _debug_log_market_type_identifier(self):
return "[TWO_SIDED]"
def __repr__(self):
- return (f"<{self._class_name} {self.time_slot_str} bids: {len(self.bids)}"
- f" (E: {sum(b.energy for b in self.bids.values())} kWh"
- f" V:{sum(b.price for b in self.bids.values())}) "
- f"offers: {len(self.offers)} (E: {sum(o.energy for o in self.offers.values())} kWh"
- f" V: {sum(o.price for o in self.offers.values())}) "
- f"trades: {len(self.trades)} (E: {self.accumulated_trade_energy} kWh"
- f", V: {self.accumulated_trade_price})>")
+ return (
+ f"<{self._class_name} {self.time_slot_str} bids: {len(self.bids)}"
+ f" (E: {sum(b.energy for b in self.bids.values())} kWh"
+ f" V:{sum(b.price for b in self.bids.values())}) "
+ f"offers: {len(self.offers)} (E: {sum(o.energy for o in self.offers.values())} kWh"
+ f" V: {sum(o.price for o in self.offers.values())}) "
+ f"trades: {len(self.trades)} (E: {self.accumulated_trade_energy} kWh"
+ f", V: {self.accumulated_trade_price})>"
+ )
@lock_market_action
def get_bids(self) -> Dict:
@@ -90,21 +119,28 @@ def _update_requirements_prices(self, bid):
if "price" in updated_requirement:
energy = updated_requirement.get("energy") or bid.energy
original_bid_price = updated_requirement["price"] + bid.accumulated_grid_fees
- updated_price = (self.fee_class.update_incoming_bid_with_fee(
- updated_requirement["price"] / energy,
- original_bid_price / energy)) * energy
+ updated_price = (
+ self.fee_class.update_incoming_bid_with_fee(
+ updated_requirement["price"] / energy, original_bid_price / energy
+ )
+ ) * energy
updated_requirement["price"] = updated_price
requirements.append(updated_requirement)
return requirements
@lock_market_action
- def bid(self, price: float, energy: float, buyer: TraderDetails,
- bid_id: Optional[str] = None,
- original_price: Optional[float] = None,
- adapt_price_with_fees: bool = True,
- add_to_history: bool = True,
- dispatch_event: bool = True,
- time_slot: Optional[DateTime] = None) -> Bid:
+ def bid(
+ self,
+ price: float,
+ energy: float,
+ buyer: TraderDetails,
+ bid_id: Optional[str] = None,
+ original_price: Optional[float] = None,
+ adapt_price_with_fees: bool = True,
+ add_to_history: bool = True,
+ dispatch_event: bool = True,
+ time_slot: Optional[DateTime] = None,
+ ) -> Bid:
"""Create bid object."""
# pylint: disable=too-many-arguments
if energy <= FLOATING_POINT_TOLERANCE:
@@ -117,25 +153,37 @@ def bid(self, price: float, energy: float, buyer: TraderDetails,
original_price = price
if adapt_price_with_fees:
- price = self.fee_class.update_incoming_bid_with_fee(
- price/energy, original_price/energy) * energy
+ price = (
+ self.fee_class.update_incoming_bid_with_fee(
+ price / energy, original_price / energy
+ )
+ * energy
+ )
if price < 0.0:
- raise NegativePriceOrdersException(
- "Negative price after taxes, bid cannot be posted.")
-
- bid = Bid(str(uuid.uuid4()) if bid_id is None else bid_id,
- self.now, price, energy,
- buyer, original_price,
- time_slot=time_slot)
+ raise NegativePriceOrdersException("Negative price after taxes, bid cannot be posted.")
+
+ bid = Bid(
+ str(uuid.uuid4()) if bid_id is None else bid_id,
+ self.now,
+ price,
+ energy,
+ buyer,
+ original_price,
+ time_slot=time_slot,
+ )
self.bids[bid.id] = bid
if add_to_history is True:
self.bid_history.append(bid)
if dispatch_event is True:
self.dispatch_market_bid_event(bid)
- log.debug("%s[BID][NEW][%s] %s", self._debug_log_market_type_identifier,
- self.time_slot_str or bid.time_slot, bid)
+ log.debug(
+ "%s[BID][NEW][%s] %s",
+ self._debug_log_market_type_identifier,
+ self.time_slot_str or bid.time_slot,
+ bid,
+ )
self.no_new_order = False
return bid
@@ -151,8 +199,12 @@ def delete_bid(self, bid_or_id: Union[str, Bid]):
bid = self.bids.pop(bid_or_id, None)
if not bid:
raise BidNotFoundException(bid_or_id)
- log.debug("%s[BID][DEL][%s] %s",
- self._debug_log_market_type_identifier, self.time_slot_str or bid.time_slot, bid)
+ log.debug(
+ "%s[BID][DEL][%s] %s",
+ self._debug_log_market_type_identifier,
+ self.time_slot_str or bid.time_slot,
+ bid,
+ )
self._notify_listeners(MarketEvent.BID_DELETED, bid=bid)
def split_bid(self, original_bid: Bid, energy: float, orig_bid_price: float):
@@ -162,57 +214,72 @@ def split_bid(self, original_bid: Bid, energy: float, orig_bid_price: float):
# same bid id is used for the new accepted_bid
original_accepted_price = energy / original_bid.energy * orig_bid_price
- accepted_bid = self.bid(bid_id=original_bid.id,
- price=original_bid.price * (energy / original_bid.energy),
- energy=energy,
- buyer=original_bid.buyer,
- original_price=original_accepted_price,
- adapt_price_with_fees=False,
- add_to_history=False,
- dispatch_event=False,
- time_slot=original_bid.time_slot)
+ accepted_bid = self.bid(
+ bid_id=original_bid.id,
+ price=original_bid.price * (energy / original_bid.energy),
+ energy=energy,
+ buyer=original_bid.buyer,
+ original_price=original_accepted_price,
+ adapt_price_with_fees=False,
+ add_to_history=False,
+ dispatch_event=False,
+ time_slot=original_bid.time_slot,
+ )
residual_price = (1 - energy / original_bid.energy) * original_bid.price
residual_energy = original_bid.energy - energy
- original_residual_price = ((original_bid.energy - energy) /
- original_bid.energy) * orig_bid_price
-
- residual_bid = self.bid(price=residual_price,
- energy=residual_energy,
- buyer=original_bid.buyer,
- original_price=original_residual_price,
- adapt_price_with_fees=False,
- add_to_history=True,
- dispatch_event=False,
- time_slot=original_bid.time_slot)
-
- log.debug("%s[BID][SPLIT][%s, %s] (%s into %s and %s",
- self._debug_log_market_type_identifier,
- self.time_slot_str or residual_bid.time_slot, self.name,
- short_offer_bid_log_str(original_bid), short_offer_bid_log_str(accepted_bid),
- short_offer_bid_log_str(residual_bid))
-
- self._notify_listeners(MarketEvent.BID_SPLIT,
- original_bid=original_bid,
- accepted_bid=accepted_bid,
- residual_bid=residual_bid)
+ original_residual_price = (
+ (original_bid.energy - energy) / original_bid.energy
+ ) * orig_bid_price
+
+ residual_bid = self.bid(
+ price=residual_price,
+ energy=residual_energy,
+ buyer=original_bid.buyer,
+ original_price=original_residual_price,
+ adapt_price_with_fees=False,
+ add_to_history=True,
+ dispatch_event=False,
+ time_slot=original_bid.time_slot,
+ )
+
+ log.debug(
+ "%s[BID][SPLIT][%s, %s] (%s into %s and %s",
+ self._debug_log_market_type_identifier,
+ self.time_slot_str or residual_bid.time_slot,
+ self.name,
+ short_offer_bid_log_str(original_bid),
+ short_offer_bid_log_str(accepted_bid),
+ short_offer_bid_log_str(residual_bid),
+ )
+
+ self._notify_listeners(
+ MarketEvent.BID_SPLIT,
+ original_bid=original_bid,
+ accepted_bid=accepted_bid,
+ residual_bid=residual_bid,
+ )
return accepted_bid, residual_bid
def _determine_bid_price(self, trade_offer_info, energy):
_, grid_fee_rate, final_trade_rate = self.fee_class.calculate_trade_price_and_fees(
- trade_offer_info)
+ trade_offer_info
+ )
return grid_fee_rate * energy, energy * final_trade_rate
@lock_market_action
- def accept_bid(self, bid: Bid,
- energy: Optional[float] = None,
- seller: Optional[TraderDetails] = None,
- buyer: Optional[TraderDetails] = None,
- trade_offer_info: Optional[TradeBidOfferInfo] = None,
- offer: Offer = None) -> Trade:
+ def accept_bid(
+ self,
+ bid: Bid,
+ energy: Optional[float] = None,
+ seller: Optional[TraderDetails] = None,
+ buyer: Optional[TraderDetails] = None,
+ trade_offer_info: Optional[TradeBidOfferInfo] = None,
+ offer: Offer = None,
+ ) -> Trade:
"""Accept bid and create Trade object."""
# pylint: disable=too-many-arguments, too-many-locals
market_bid = self.bids.pop(bid.id, None)
@@ -230,8 +297,10 @@ def accept_bid(self, bid: Bid,
if energy <= 0:
raise NegativeEnergyTradeException("Energy cannot be negative or zero.")
if market_bid.energy - energy < -FLOATING_POINT_TOLERANCE:
- raise InvalidTrade(f"Traded energy ({energy}) cannot be more than the "
- f"bid energy ({market_bid.energy}).")
+ raise InvalidTrade(
+ f"Traded energy ({energy}) cannot be more than the "
+ f"bid energy ({market_bid.energy})."
+ )
if market_bid.energy - energy > FLOATING_POINT_TOLERANCE:
# partial bid trade
accepted_bid, residual_bid = self.split_bid(market_bid, energy, orig_price)
@@ -242,7 +311,8 @@ def accept_bid(self, bid: Bid,
self.bids.pop(accepted_bid.id)
except KeyError as exception:
raise BidNotFoundException(
- f"Bid {accepted_bid.id} not found in self.bids ({self.name}).") from exception
+ f"Bid {accepted_bid.id} not found in self.bids ({self.name})."
+ ) from exception
else:
# full bid trade, nothing further to do here
pass
@@ -258,46 +328,67 @@ def accept_bid(self, bid: Bid,
trade_offer_info, ignore_fees=True
)
- trade = Trade(str(uuid.uuid4()), self.now,
- seller,
- bid.buyer,
- bid=bid, offer=offer, traded_energy=energy, trade_price=trade_price,
- residual=residual_bid,
- offer_bid_trade_info=updated_bid_trade_info,
- fee_price=fee_price, time_slot=bid.time_slot)
+ trade = Trade(
+ str(uuid.uuid4()),
+ self.now,
+ seller,
+ bid.buyer,
+ bid=bid,
+ offer=offer,
+ traded_energy=energy,
+ trade_price=trade_price,
+ residual=residual_bid,
+ offer_bid_trade_info=updated_bid_trade_info,
+ fee_price=fee_price,
+ time_slot=bid.time_slot,
+ )
if not offer:
# This is a chain trade, therefore needs to be tracked. For the trade on the market
# that the match is performed, the tracking should have already been done by the offer
# trade.
self._update_stats_after_trade(trade, bid)
- log.info("%s[TRADE][BID] [%s] [%s] {%s}",
- self._debug_log_market_type_identifier, self.name, trade.time_slot, trade)
+ log.info(
+ "%s[TRADE][BID] [%s] [%s] {%s}",
+ self._debug_log_market_type_identifier,
+ self.name,
+ trade.time_slot,
+ trade,
+ )
self._notify_listeners(MarketEvent.BID_TRADED, bid_trade=trade)
if residual_bid:
self.dispatch_market_bid_event(residual_bid)
return trade
- def accept_bid_offer_pair(self, bid: Bid, offer: Offer, clearing_rate: float,
- trade_bid_info: TradeBidOfferInfo,
- selected_energy: float) -> Tuple[Trade, Trade]:
+ def accept_bid_offer_pair(
+ self,
+ bid: Bid,
+ offer: Offer,
+ clearing_rate: float,
+ trade_bid_info: TradeBidOfferInfo,
+ selected_energy: float,
+ ) -> Tuple[Trade, Trade]:
"""Accept bid and offers in pair when a trade is happening."""
# pylint: disable=too-many-arguments
assert isclose(clearing_rate, trade_bid_info.trade_rate)
assert bid.buyer.uuid != offer.seller.uuid
- trade = self.accept_offer(offer_or_id=offer,
- buyer=bid.buyer,
- energy=selected_energy,
- trade_bid_info=trade_bid_info,
- bid=bid)
-
- bid_trade = self.accept_bid(bid=bid,
- energy=selected_energy,
- seller=offer.seller,
- buyer=bid.buyer,
- trade_offer_info=trade_bid_info,
- offer=offer)
+ trade = self.accept_offer(
+ offer_or_id=offer,
+ buyer=bid.buyer,
+ energy=selected_energy,
+ trade_bid_info=trade_bid_info,
+ bid=bid,
+ )
+
+ bid_trade = self.accept_bid(
+ bid=bid,
+ energy=selected_energy,
+ seller=offer.seller,
+ buyer=bid.buyer,
+ trade_offer_info=trade_bid_info,
+ offer=offer,
+ )
return bid_trade, trade
def _get_offer_from_seller_origin_id(self, seller_origin_id):
@@ -307,21 +398,30 @@ def _get_offer_from_seller_origin_id(self, seller_origin_id):
# inaccurate.
return None
- return next(iter(
- [offer for offer in self.offers.values()
- if offer.seller.origin_uuid == seller_origin_id]), None)
+ return next(
+ iter(
+ [
+ offer
+ for offer in self.offers.values()
+ if offer.seller.origin_uuid == seller_origin_id
+ ]
+ ),
+ None,
+ )
def _get_bid_from_buyer_origin_id(self, buyer_origin_id):
if buyer_origin_id is None:
# Many bids may have buyer_origin_id=None; Avoid looking for them as it is inaccurate.
return None
- return next(iter(
- [bid for bid in self.bids.values()
- if bid.buyer.origin_uuid == buyer_origin_id]), None)
+ return next(
+ iter([bid for bid in self.bids.values() if bid.buyer.origin_uuid == buyer_origin_id]),
+ None,
+ )
def match_recommendations(
- self, recommendations: List[BidOfferMatch.serializable_dict]) -> bool:
+ self, recommendations: List[BidOfferMatch.serializable_dict]
+ ) -> bool:
"""Match a list of bid/offer pairs, create trades and residual offers/bids.
Returns True if trades were actually performed, False otherwise."""
were_trades_performed = False
@@ -335,7 +435,8 @@ def match_recommendations(
# by seller / buyer.
if not market_offer:
market_offer = self._get_offer_from_seller_origin_id(
- recommended_pair.offer["seller"]["origin_uuid"])
+ recommended_pair.offer["seller"]["origin_uuid"]
+ )
if market_offer is None:
raise InvalidBidOfferPairException("Offer does not exist in the market")
recommended_pair.offer = market_offer.serializable_dict()
@@ -343,7 +444,8 @@ def match_recommendations(
market_bid = self.bids.get(recommended_pair.bid["id"])
if not market_bid:
market_bid = self._get_bid_from_buyer_origin_id(
- recommended_pair.bid["buyer"]["origin_uuid"])
+ recommended_pair.bid["buyer"]["origin_uuid"]
+ )
if market_bid is None:
raise InvalidBidOfferPairException("Bid does not exist in the market")
recommended_pair.bid = market_bid.serializable_dict()
@@ -362,13 +464,17 @@ def match_recommendations(
raise invalid_bop_exception
continue
original_bid_rate = recommended_pair.bid_energy_rate + (
- market_bid.accumulated_grid_fees / recommended_pair.bid_energy)
- if ConstSettings.MASettings.BID_OFFER_MATCH_TYPE == \
- BidOfferMatchAlgoEnum.PAY_AS_BID.value:
+ market_bid.accumulated_grid_fees / recommended_pair.bid_energy
+ )
+ if (
+ ConstSettings.MASettings.BID_OFFER_MATCH_TYPE
+ == BidOfferMatchAlgoEnum.PAY_AS_BID.value
+ ):
trade_rate = original_bid_rate
else:
trade_rate = self.fee_class.calculate_original_trade_rate_from_clearing_rate(
- original_bid_rate, market_bid.energy_rate, recommended_pair.trade_rate)
+ original_bid_rate, market_bid.energy_rate, recommended_pair.trade_rate
+ )
trade_bid_info = TradeBidOfferInfo(
original_bid_rate=original_bid_rate,
propagated_bid_rate=recommended_pair.bid_energy_rate,
@@ -378,19 +484,20 @@ def match_recommendations(
)
bid_trade, offer_trade = self.accept_bid_offer_pair(
- market_bid, market_offer, trade_rate,
- trade_bid_info, min(recommended_pair.selected_energy,
- market_offer.energy, market_bid.energy))
+ market_bid,
+ market_offer,
+ trade_rate,
+ trade_bid_info,
+ min(recommended_pair.selected_energy, market_offer.energy, market_bid.energy),
+ )
were_trades_performed = True
- recommendations = (
- self._replace_offers_bids_with_residual_in_recommendations_list(
- recommendations, offer_trade, bid_trade)
+ recommendations = self._replace_offers_bids_with_residual_in_recommendations_list(
+ recommendations, offer_trade, bid_trade
)
return were_trades_performed
@staticmethod
- def _validate_requirements_satisfied(
- recommendation: BidOfferMatch) -> None:
+ def _validate_requirements_satisfied(recommendation: BidOfferMatch) -> None:
"""Validate if both trade parties satisfy each other's requirements.
:raises:
@@ -400,20 +507,26 @@ def _validate_requirements_satisfied(
if (recommendation.matching_requirements or {}).get("offer_requirement"):
offer_requirement = recommendation.matching_requirements["offer_requirement"]
requirements_satisfied &= RequirementsSatisfiedChecker.is_offer_requirement_satisfied(
- recommendation.offer, recommendation.bid, offer_requirement,
- recommendation.trade_rate, recommendation.selected_energy)
+ recommendation.offer,
+ recommendation.bid,
+ offer_requirement,
+ recommendation.trade_rate,
+ recommendation.selected_energy,
+ )
if (recommendation.matching_requirements or {}).get("bid_requirement"):
bid_requirement = recommendation.matching_requirements["bid_requirement"]
requirements_satisfied &= RequirementsSatisfiedChecker.is_bid_requirement_satisfied(
- recommendation.offer, recommendation.bid, bid_requirement,
- recommendation.trade_rate, recommendation.selected_energy)
+ recommendation.offer,
+ recommendation.bid,
+ bid_requirement,
+ recommendation.trade_rate,
+ recommendation.selected_energy,
+ )
if not requirements_satisfied:
# If requirements are not satisfied
- raise InvalidBidOfferPairException(
- "The requirements failed the validation.")
+ raise InvalidBidOfferPairException("The requirements failed the validation.")
- def validate_bid_offer_match(
- self, recommendation: BidOfferMatch) -> None:
+ def validate_bid_offer_match(self, recommendation: BidOfferMatch) -> None:
"""Basic validation function for a bid against an offer.
Raises:
@@ -431,21 +544,26 @@ def validate_bid_offer_match(
offer_energy = market_offer.energy
if selected_energy <= 0:
raise InvalidBidOfferPairException(
- f"Energy traded {selected_energy} should be more than 0.")
+ f"Energy traded {selected_energy} should be more than 0."
+ )
if selected_energy > bid_energy:
raise InvalidBidOfferPairException(
- f"Energy traded {selected_energy} is higher than bids energy {bid_energy}.")
+ f"Energy traded {selected_energy} is higher than bids energy {bid_energy}."
+ )
if selected_energy > offer_energy:
raise InvalidBidOfferPairException(
- f"Energy traded {selected_energy} is higher than offers energy {offer_energy}.")
+ f"Energy traded {selected_energy} is higher than offers energy {offer_energy}."
+ )
if recommendation.bid_energy_rate + FLOATING_POINT_TOLERANCE < clearing_rate:
raise InvalidBidOfferPairException(
f"Trade rate {clearing_rate} is higher than bid energy rate "
- f"{recommendation.bid_energy_rate}.")
+ f"{recommendation.bid_energy_rate}."
+ )
if market_offer.energy_rate > clearing_rate + FLOATING_POINT_TOLERANCE:
raise InvalidBidOfferPairException(
f"Trade rate {clearing_rate} is lower than offer energy rate "
- f"{market_offer.energy_rate}.")
+ f"{market_offer.energy_rate}."
+ )
self._validate_matching_requirements(recommendation)
self._validate_requirements_satisfied(recommendation)
@@ -467,7 +585,8 @@ def _validate_matching_requirements(recommendation: BidOfferMatch) -> None:
if bid_matching_requirement not in bid_requirements:
raise InvalidBidOfferPairException(
f"Matching requirement {bid_matching_requirement} doesn't exist in the Bid"
- " object.")
+ " object."
+ )
offer_matching_requirement = recommendation.matching_requirements.get("offer_requirement")
if offer_matching_requirement:
@@ -475,11 +594,12 @@ def _validate_matching_requirements(recommendation: BidOfferMatch) -> None:
if offer_matching_requirement not in offer_requirements:
raise InvalidBidOfferPairException(
f"Matching requirement {offer_matching_requirement} doesn't exist in the Offer"
- f" object.")
+ f" object."
+ )
@classmethod
def _replace_offers_bids_with_residual_in_recommendations_list(
- cls, recommendations: List[Dict], offer_trade: Trade, bid_trade: Trade
+ cls, recommendations: List[Dict], offer_trade: Trade, bid_trade: Trade
) -> List[BidOfferMatch.serializable_dict]:
"""
If a trade resulted in a residual offer/bid, upcoming matching list with same offer/bid
@@ -493,29 +613,37 @@ def _replace_offers_bids_with_residual_in_recommendations_list(
def _adapt_matching_requirements_in_residuals(recommendation):
if "energy" in (recommendation.get("matching_requirements") or {}).get(
- "bid_requirement", {}):
+ "bid_requirement", {}
+ ):
for index, requirement in enumerate(recommendation["bid"]["requirements"]):
if requirement == recommendation["matching_requirements"]["bid_requirement"]:
bid_requirement = deepcopy(requirement)
bid_requirement["energy"] -= bid_trade.traded_energy
recommendation["bid"]["requirements"][index] = bid_requirement
recommendation["matching_requirements"][
- "bid_requirement"] = bid_requirement
+ "bid_requirement"
+ ] = bid_requirement
return recommendation
return recommendation
def replace_recommendations_with_residuals(recommendation: Dict):
- if (recommendation["offer"]["id"] == offer_trade.match_details["offer"].id and
- offer_trade.residual is not None):
+ if (
+ recommendation["offer"]["id"] == offer_trade.match_details["offer"].id
+ and offer_trade.residual is not None
+ ):
recommendation["offer"] = offer_trade.residual.serializable_dict()
- if (recommendation["bid"]["id"] == bid_trade.match_details["bid"].id and
- bid_trade.residual is not None):
+ if (
+ recommendation["bid"]["id"] == bid_trade.match_details["bid"].id
+ and bid_trade.residual is not None
+ ):
recommendation["bid"] = bid_trade.residual.serializable_dict()
recommendation = _adapt_matching_requirements_in_residuals(recommendation)
return recommendation
if offer_trade.residual or bid_trade.residual:
- recommendations = [replace_recommendations_with_residuals(recommendation)
- for recommendation in recommendations]
+ recommendations = [
+ replace_recommendations_with_residuals(recommendation)
+ for recommendation in recommendations
+ ]
return recommendations
diff --git a/src/gsy_e/models/strategy/__init__.py b/src/gsy_e/models/strategy/__init__.py
index 93b5cc565..ceb7ed18d 100644
--- a/src/gsy_e/models/strategy/__init__.py
+++ b/src/gsy_e/models/strategy/__init__.py
@@ -26,14 +26,14 @@
from typing import TYPE_CHECKING, Callable, Dict, Generator, List, Optional, Union
from uuid import uuid4
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import Bid, Offer, Trade, TraderDetails
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.utils import limit_float_precision
from pendulum import DateTime
from gsy_e import constants
-from gsy_e.constants import FLOATING_POINT_TOLERANCE, REDIS_PUBLISH_RESPONSE_TIMEOUT
+from gsy_e.constants import REDIS_PUBLISH_RESPONSE_TIMEOUT
from gsy_e.events import EventMixin
from gsy_e.events.event_structures import AreaEvent, MarketEvent
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
diff --git a/src/gsy_e/models/strategy/energy_parameters/energy_params_eb.py b/src/gsy_e/models/strategy/energy_parameters/energy_params_eb.py
index 861f9f0a7..f3f142950 100644
--- a/src/gsy_e/models/strategy/energy_parameters/energy_params_eb.py
+++ b/src/gsy_e/models/strategy/energy_parameters/energy_params_eb.py
@@ -26,12 +26,12 @@
from typing import Optional, TYPE_CHECKING, DefaultDict
import pendulum
-from gsy_framework.constants_limits import GlobalConfig
+from gsy_framework.constants_limits import GlobalConfig, FLOATING_POINT_TOLERANCE
from gsy_framework.enums import AvailableMarketTypes
from gsy_framework.forward_markets.forward_profile import ForwardTradeProfileGenerator
from gsy_framework.utils import convert_kW_to_kWh
-from gsy_e.constants import FLOATING_POINT_TOLERANCE, FORWARD_MARKET_MAX_DURATION_YEARS
+from gsy_e.constants import FORWARD_MARKET_MAX_DURATION_YEARS
from gsy_e.models.strategy.state import LoadState, PVState
if TYPE_CHECKING:
@@ -45,6 +45,7 @@ class _BaseMarketEnergyParams(ABC):
The children of these classes should not be instantiated by classes other than the
ForwardEnergyParams child classes.
"""
+
def __init__(self, posted_energy_kWh: DefaultDict):
self._posted_energy_kWh = posted_energy_kWh
self._area: Optional["AreaBase"] = None
@@ -63,35 +64,35 @@ def get_posted_energy_kWh(self, market_slot: pendulum.DateTime) -> float:
"""Get the already posted energy from the asset for this market slot."""
@abstractmethod
- def increment_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
+ def increment_posted_energy(self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
"""Increment the posted energy of the asset."""
@abstractmethod
- def decrement_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
+ def decrement_posted_energy(self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
"""Decrement the posted energy of the asset."""
@abstractmethod
def get_available_load_energy_kWh(
- self, market_slot: pendulum.DateTime, state: "LoadState",
- peak_energy_kWh: float) -> float:
+ self, market_slot: pendulum.DateTime, state: "LoadState", peak_energy_kWh: float
+ ) -> float:
"""Get the available (not traded) energy of a load asset."""
@abstractmethod
def get_available_pv_energy_kWh(
- self, market_slot: pendulum.DateTime, state: "PVState",
- peak_energy_kWh: float) -> float:
+ self, market_slot: pendulum.DateTime, state: "PVState", peak_energy_kWh: float
+ ) -> float:
"""Get the available (not traded) energy of a PV asset."""
@abstractmethod
def event_load_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime, state: "LoadState"):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, state: "LoadState"
+ ):
"""Trigger actions on a trade event of a load asset."""
@abstractmethod
def event_pv_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime, state: "PVState"):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, state: "PVState"
+ ):
"""Trigger actions on a trade event of a PV asset."""
@@ -100,40 +101,39 @@ class _IntradayEnergyParams(_BaseMarketEnergyParams):
Energy parameters for the intraday market.
Should only be instantiated by the ForwardEnergyParams and its child classes.
"""
+
def get_posted_energy_kWh(self, market_slot: pendulum.DateTime) -> float:
return self._posted_energy_kWh[market_slot]
- def increment_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
+ def increment_posted_energy(self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
self._posted_energy_kWh[market_slot] += posted_energy_kWh
- def decrement_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
+ def decrement_posted_energy(self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
self._posted_energy_kWh[market_slot] -= posted_energy_kWh
def get_available_load_energy_kWh(
- self, market_slot: pendulum.DateTime, state: "LoadState",
- peak_energy_kWh: float) -> float:
+ self, market_slot: pendulum.DateTime, state: "LoadState", peak_energy_kWh: float
+ ) -> float:
return state.get_energy_requirement_Wh(market_slot) / 1000.0
def get_available_pv_energy_kWh(
- self, market_slot: pendulum.DateTime, state: "PVState",
- peak_energy_kWh: float) -> float:
+ self, market_slot: pendulum.DateTime, state: "PVState", peak_energy_kWh: float
+ ) -> float:
return state.get_available_energy_kWh(market_slot)
def event_load_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime, state: "LoadState"):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, state: "LoadState"
+ ):
state.decrement_energy_requirement(
- purchased_energy_Wh=energy_kWh * 1000,
- time_slot=market_slot,
- area_name=self._area.name)
+ purchased_energy_Wh=energy_kWh * 1000, time_slot=market_slot, area_name=self._area.name
+ )
def event_pv_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime, state: "PVState"):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, state: "PVState"
+ ):
state.decrement_available_energy(
- sold_energy_kWh=energy_kWh,
- time_slot=market_slot,
- area_name=self._area.name)
+ sold_energy_kWh=energy_kWh, time_slot=market_slot, area_name=self._area.name
+ )
class _DayForwardEnergyParams(_BaseMarketEnergyParams):
@@ -141,6 +141,7 @@ class _DayForwardEnergyParams(_BaseMarketEnergyParams):
Energy parameters for the day forward market.
Should only be instantiated by the ForwardEnergyParams and its child classes.
"""
+
@staticmethod
def _day_forward_slots(market_slot: pendulum.DateTime):
"""Get the market slots for the day forward market."""
@@ -148,55 +149,52 @@ def _day_forward_slots(market_slot: pendulum.DateTime):
market_slot,
market_slot + pendulum.duration(minutes=15),
market_slot + pendulum.duration(minutes=30),
- market_slot + pendulum.duration(minutes=45)]
+ market_slot + pendulum.duration(minutes=45),
+ ]
def get_posted_energy_kWh(self, market_slot: pendulum.DateTime) -> float:
- return max(
- self._posted_energy_kWh[slot]
- for slot in self._day_forward_slots(market_slot)
- )
+ return max(self._posted_energy_kWh[slot] for slot in self._day_forward_slots(market_slot))
def increment_posted_energy(self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
slots = self._day_forward_slots(market_slot)
for slot in slots:
self._posted_energy_kWh[slot] += posted_energy_kWh
- def decrement_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
+ def decrement_posted_energy(self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
for slot in self._day_forward_slots(market_slot):
self._posted_energy_kWh[slot] -= posted_energy_kWh
def get_available_load_energy_kWh(
- self, market_slot: pendulum.DateTime, state: "LoadState",
- peak_energy_kWh: float) -> float:
+ self, market_slot: pendulum.DateTime, state: "LoadState", peak_energy_kWh: float
+ ) -> float:
return min(
state.get_energy_requirement_Wh(slot) / 1000.0
for slot in self._day_forward_slots(market_slot)
)
def get_available_pv_energy_kWh(
- self, market_slot: pendulum.DateTime, state: "PVState",
- peak_energy_kWh: float) -> float:
+ self, market_slot: pendulum.DateTime, state: "PVState", peak_energy_kWh: float
+ ) -> float:
return min(
state.get_energy_production_forecast_kWh(slot)
for slot in self._day_forward_slots(market_slot)
)
def event_load_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime, state: "LoadState"):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, state: "LoadState"
+ ):
for slot in self._day_forward_slots(market_slot):
state.decrement_energy_requirement(
- purchased_energy_Wh=energy_kWh * 1000,
- time_slot=slot,
- area_name=self._area.name)
+ purchased_energy_Wh=energy_kWh * 1000, time_slot=slot, area_name=self._area.name
+ )
def event_pv_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime, state: "PVState"):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, state: "PVState"
+ ):
for slot in self._day_forward_slots(market_slot):
state.decrement_available_energy(
- sold_energy_kWh=energy_kWh,
- time_slot=slot,
- area_name=self._area.name)
+ sold_energy_kWh=energy_kWh, time_slot=slot, area_name=self._area.name
+ )
class _LongForwardEnergyParameters(_BaseMarketEnergyParams):
@@ -204,24 +202,23 @@ class _LongForwardEnergyParameters(_BaseMarketEnergyParams):
Energy parameters for the week / month / year forward markets.
Should only be instantiated by the ForwardEnergyParams and its child classes.
"""
+
def __init__(self, posted_energy_kWh: DefaultDict, product_type: AvailableMarketTypes):
super().__init__(posted_energy_kWh)
self._product_type = product_type
def get_posted_energy_kWh(self, market_slot: pendulum.DateTime) -> float:
- return 0.
+ return 0.0
- def increment_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
+ def increment_posted_energy(self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
return
- def decrement_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
+ def decrement_posted_energy(self, market_slot: pendulum.DateTime, posted_energy_kWh: float):
return
def get_available_load_energy_kWh(
- self, market_slot: pendulum.DateTime, state: "LoadState",
- peak_energy_kWh: float) -> float:
+ self, market_slot: pendulum.DateTime, state: "LoadState", peak_energy_kWh: float
+ ) -> float:
# This is the part where the week / month / year forward markets' available energy is
# calculated. In order for the available energy for a bid to be calculated, the available
@@ -234,16 +231,16 @@ def get_available_load_energy_kWh(
reference_slot = market_slot.set(hour=12, minute=0)
if state.get_desired_energy_Wh(reference_slot) <= FLOATING_POINT_TOLERANCE:
- scaling_factor = 0.
+ scaling_factor = 0.0
else:
- scaling_factor = (
- state.get_energy_requirement_Wh(reference_slot) /
- state.get_desired_energy_Wh(reference_slot))
+ scaling_factor = state.get_energy_requirement_Wh(
+ reference_slot
+ ) / state.get_desired_energy_Wh(reference_slot)
return abs(scaling_factor) * peak_energy_kWh
def get_available_pv_energy_kWh(
- self, market_slot: pendulum.DateTime, state: "PVState",
- peak_energy_kWh: float) -> float:
+ self, market_slot: pendulum.DateTime, state: "PVState", peak_energy_kWh: float
+ ) -> float:
# This is the part where the week / month / year forward markets' available energy is
# calculated. In order for the available energy for a bid to be calculated, the available
@@ -255,88 +252,100 @@ def get_available_pv_energy_kWh(
# multiplying the scaling factor by the already known peak energy of the asset.
reference_slot = market_slot.set(hour=12, minute=0)
- if state.get_energy_production_forecast_kWh(
- reference_slot) <= FLOATING_POINT_TOLERANCE:
- scaling_factor = 0.
+ if state.get_energy_production_forecast_kWh(reference_slot) <= FLOATING_POINT_TOLERANCE:
+ scaling_factor = 0.0
else:
- scaling_factor = (
- state.get_available_energy_kWh(reference_slot) /
- state.get_energy_production_forecast_kWh(reference_slot))
+ scaling_factor = state.get_available_energy_kWh(
+ reference_slot
+ ) / state.get_energy_production_forecast_kWh(reference_slot)
return abs(scaling_factor) * peak_energy_kWh
def event_load_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime, state: "LoadState"):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, state: "LoadState"
+ ):
trade_profile = self._profile_generator.generate_trade_profile(
- energy_kWh=energy_kWh,
- market_slot=market_slot,
- product_type=self._product_type)
+ energy_kWh=energy_kWh, market_slot=market_slot, product_type=self._product_type
+ )
for time_slot, energy_value_kWh in trade_profile.items():
state.decrement_energy_requirement(
purchased_energy_Wh=energy_value_kWh * 1000,
time_slot=time_slot,
- area_name=self._area.name)
+ area_name=self._area.name,
+ )
def event_pv_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime, state: "PVState"):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, state: "PVState"
+ ):
# Create a new profile that spreads the trade energy across multiple slots. The values
# of this new profile are obtained by scaling the values of the standard solar profile
trade_profile = self._profile_generator.generate_trade_profile(
- energy_kWh=energy_kWh,
- market_slot=market_slot,
- product_type=self._product_type)
+ energy_kWh=energy_kWh, market_slot=market_slot, product_type=self._product_type
+ )
for time_slot, energy_value_kWh in trade_profile.items():
state.decrement_available_energy(
- sold_energy_kWh=energy_value_kWh,
- time_slot=time_slot,
- area_name=self._area.name)
+ sold_energy_kWh=energy_value_kWh, time_slot=time_slot, area_name=self._area.name
+ )
class ForwardEnergyParams(ABC):
"""Common abstract base class for the energy parameters of the forward strategies."""
+
def __init__(self):
- self._posted_energy_kWh: DefaultDict = defaultdict(lambda: 0.)
+ self._posted_energy_kWh: DefaultDict = defaultdict(lambda: 0.0)
self._forward_energy_params = {
AvailableMarketTypes.INTRADAY: _IntradayEnergyParams(self._posted_energy_kWh),
AvailableMarketTypes.DAY_FORWARD: _DayForwardEnergyParams(self._posted_energy_kWh),
AvailableMarketTypes.WEEK_FORWARD: _LongForwardEnergyParameters(
- self._posted_energy_kWh, AvailableMarketTypes.WEEK_FORWARD),
+ self._posted_energy_kWh, AvailableMarketTypes.WEEK_FORWARD
+ ),
AvailableMarketTypes.MONTH_FORWARD: _LongForwardEnergyParameters(
- self._posted_energy_kWh, AvailableMarketTypes.MONTH_FORWARD),
+ self._posted_energy_kWh, AvailableMarketTypes.MONTH_FORWARD
+ ),
AvailableMarketTypes.YEAR_FORWARD: _LongForwardEnergyParameters(
- self._posted_energy_kWh, AvailableMarketTypes.YEAR_FORWARD)
+ self._posted_energy_kWh, AvailableMarketTypes.YEAR_FORWARD
+ ),
}
self._area = None
self._profile_generator: Optional[ForwardTradeProfileGenerator] = None
def get_posted_energy_kWh(
- self, market_slot: pendulum.DateTime, product_type: AvailableMarketTypes) -> float:
+ self, market_slot: pendulum.DateTime, product_type: AvailableMarketTypes
+ ) -> float:
"""Retrieve the already posted energy on this market slot."""
return self._forward_energy_params[product_type].get_posted_energy_kWh(market_slot)
def increment_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float,
- market_type: AvailableMarketTypes):
+ self,
+ market_slot: pendulum.DateTime,
+ posted_energy_kWh: float,
+ market_type: AvailableMarketTypes,
+ ):
"""
Increase the posted energy of the strategy. Needs to handle only intraday and day ahead
since these are the only 2 market types that can operate at the same market slots
concurrently.
"""
self._forward_energy_params[market_type].increment_posted_energy(
- market_slot, posted_energy_kWh)
+ market_slot, posted_energy_kWh
+ )
def decrement_posted_energy(
- self, market_slot: pendulum.DateTime, posted_energy_kWh: float,
- market_type: AvailableMarketTypes):
+ self,
+ market_slot: pendulum.DateTime,
+ posted_energy_kWh: float,
+ market_type: AvailableMarketTypes,
+ ):
"""
Decrease the posted energy of the strategy. Needs to handle only intraday and day ahead
since these are the only 2 market types that can operate at the same market slots
concurrently.
"""
self._forward_energy_params[market_type].decrement_posted_energy(
- market_slot, posted_energy_kWh)
+ market_slot, posted_energy_kWh
+ )
@abstractmethod
def serialize(self):
@@ -344,7 +353,8 @@ def serialize(self):
@abstractmethod
def get_available_energy_kWh(
- self, market_slot: pendulum.DateTime, market_type: AvailableMarketTypes):
+ self, market_slot: pendulum.DateTime, market_type: AvailableMarketTypes
+ ):
"""Get the available offer energy of the PV."""
def event_activate_energy(self, area):
@@ -354,8 +364,8 @@ def event_activate_energy(self, area):
@abstractmethod
def event_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime,
- product_type: AvailableMarketTypes):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, product_type: AvailableMarketTypes
+ ):
"""
When a trade happens, we want to split the energy through the entire year following a
standard solar profile.
@@ -388,9 +398,7 @@ def state(self) -> LoadState:
def serialize(self):
"""Return dict with the current energy parameter values."""
- return {
- "capacity_kW": self.capacity_kW
- }
+ return {"capacity_kW": self.capacity_kW}
@property
def peak_energy_kWh(self):
@@ -398,33 +406,35 @@ def peak_energy_kWh(self):
return convert_kW_to_kWh(self.capacity_kW, self._area.config.slot_length)
def get_available_energy_kWh(
- self, market_slot: pendulum.DateTime, market_type: AvailableMarketTypes) -> float:
+ self, market_slot: pendulum.DateTime, market_type: AvailableMarketTypes
+ ) -> float:
"""
Get the available energy of the Load for one market slot. The available energy is the
energy that the Load can consumed, but has not been traded yet.
"""
return self._forward_energy_params[market_type].get_available_load_energy_kWh(
- market_slot, self.state, self.peak_energy_kWh)
+ market_slot, self.state, self.peak_energy_kWh
+ )
def event_activate_energy(self, area):
"""Initialize values that are required to compute the energy values of the asset."""
self._area = area
- self._profile_generator = ForwardTradeProfileGenerator(
- peak_kWh=self.peak_energy_kWh)
+ self._profile_generator = ForwardTradeProfileGenerator(peak_kWh=self.peak_energy_kWh)
for i in range(FORWARD_MARKET_MAX_DURATION_YEARS + 1):
capacity_profile = self._profile_generator.generate_trade_profile(
energy_kWh=self.peak_energy_kWh,
market_slot=GlobalConfig.start_date.start_of("year").add(years=i),
- product_type=AvailableMarketTypes.YEAR_FORWARD)
+ product_type=AvailableMarketTypes.YEAR_FORWARD,
+ )
for time_slot, energy_kWh in capacity_profile.items():
self.state.set_desired_energy(energy_kWh * 1000, time_slot)
super().event_activate_energy(area)
def event_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime,
- product_type: AvailableMarketTypes):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, product_type: AvailableMarketTypes
+ ):
"""
When a trade happens, we want to split the energy through the entire year following a
standard solar profile.
@@ -437,7 +447,8 @@ def event_traded_energy(
"""
assert self._profile_generator is not None
self._forward_energy_params[product_type].event_load_traded_energy(
- energy_kWh, market_slot, self.state)
+ energy_kWh, market_slot, self.state
+ )
class ProductionStandardProfileEnergyParameters(ForwardEnergyParams):
@@ -458,9 +469,7 @@ def state(self) -> PVState:
def serialize(self):
"""Return dict with the current energy parameter values."""
- return {
- "capacity_kW": self.capacity_kW
- }
+ return {"capacity_kW": self.capacity_kW}
@property
def peak_energy_kWh(self):
@@ -468,33 +477,35 @@ def peak_energy_kWh(self):
return convert_kW_to_kWh(self.capacity_kW, self._area.config.slot_length)
def get_available_energy_kWh(
- self, market_slot: pendulum.DateTime, market_type: AvailableMarketTypes) -> float:
+ self, market_slot: pendulum.DateTime, market_type: AvailableMarketTypes
+ ) -> float:
"""
Get the available energy of the PV for one market slot. The available energy is the energy
that the PV can produce, but has not been traded yet.
"""
return self._forward_energy_params[market_type].get_available_pv_energy_kWh(
- market_slot, self.state, self.peak_energy_kWh)
+ market_slot, self.state, self.peak_energy_kWh
+ )
def event_activate_energy(self, area):
"""Initialize values that are required to compute the energy values of the asset."""
self._area = area
- self._profile_generator = ForwardTradeProfileGenerator(
- peak_kWh=self.peak_energy_kWh)
+ self._profile_generator = ForwardTradeProfileGenerator(peak_kWh=self.peak_energy_kWh)
for i in range(FORWARD_MARKET_MAX_DURATION_YEARS + 1):
capacity_profile = self._profile_generator.generate_trade_profile(
energy_kWh=self.peak_energy_kWh,
market_slot=GlobalConfig.start_date.start_of("year").add(years=i),
- product_type=AvailableMarketTypes.YEAR_FORWARD)
+ product_type=AvailableMarketTypes.YEAR_FORWARD,
+ )
for time_slot, energy_kWh in capacity_profile.items():
self.state.set_available_energy(energy_kWh, time_slot)
super().event_activate_energy(area)
def event_traded_energy(
- self, energy_kWh: float, market_slot: pendulum.DateTime,
- product_type: AvailableMarketTypes):
+ self, energy_kWh: float, market_slot: pendulum.DateTime, product_type: AvailableMarketTypes
+ ):
"""
Spread the traded energy over the market duration following a standard solar profile.
@@ -509,4 +520,5 @@ def event_traded_energy(
"""
assert self._profile_generator is not None
self._forward_energy_params[product_type].event_pv_traded_energy(
- energy_kWh, market_slot, self.state)
+ energy_kWh, market_slot, self.state
+ )
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/__init__.py b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/__init__.py
new file mode 100644
index 000000000..66ff14ff7
--- /dev/null
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/__init__.py
@@ -0,0 +1,7 @@
+from gsy_e.models.strategy.energy_parameters.heatpump.cop_models.cop_models import (
+ COPModelType,
+ cop_model_factory,
+)
+
+
+__all__ = ["COPModelType", "cop_model_factory"]
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/cop_models.py b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/cop_models.py
new file mode 100644
index 000000000..5f3e05805
--- /dev/null
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/cop_models.py
@@ -0,0 +1,141 @@
+import json
+import os
+from abc import abstractmethod
+from enum import Enum
+from typing import Optional
+from logging import getLogger
+
+from gsy_framework.enums import HeatPumpSourceType
+
+log = getLogger(__name__)
+
+
+class COPModelType(Enum):
+ """Selection of supported COP models"""
+
+ UNIVERSAL = 0
+ ELCO_AEROTOP_S09_IR = 1
+ ELCO_AEROTOP_G07_14M = 2
+ HOVAL_ULTRASOURCE_B_COMFORT_C11 = 3
+
+
+MODEL_FILE_DIR = os.path.join(os.path.dirname(__file__), "model_data")
+
+MODEL_TYPE_FILENAME_MAPPING = {
+ COPModelType.ELCO_AEROTOP_S09_IR: "Elco_Aerotop_S09M-IR_model_parameters.json",
+ COPModelType.ELCO_AEROTOP_G07_14M: "Elco_Aerotop_G07-14M_model_parameters.json",
+ COPModelType.HOVAL_ULTRASOURCE_B_COMFORT_C11: "hoval_UltraSource_B_comfort_C_11_model_"
+ "parameters.json",
+}
+
+
+class BaseCOPModel:
+ """Base clas for COP models"""
+
+ @abstractmethod
+ def calc_cop(self, source_temp_C: float, tank_temp_C: float, heat_demand_kW: Optional[float]):
+ """Return COP value for provided inputs"""
+
+
+class IndividualCOPModel(BaseCOPModel):
+ """Handles cop models for specific heat pump models"""
+
+ def __init__(self, model_type: COPModelType):
+ with open(
+ os.path.join(MODEL_FILE_DIR, MODEL_TYPE_FILENAME_MAPPING[model_type]),
+ "r",
+ encoding="utf-8",
+ ) as fp:
+ self._model = json.load(fp)
+ self.model_type = model_type
+
+ def _calc_power(self, source_temp_C: float, tank_temp_C: float, heat_demand_kW: float):
+ CAPFT = (
+ self._model["CAPFT"][0]
+ + self._model["CAPFT"][1] * source_temp_C
+ + self._model["CAPFT"][3] * source_temp_C**2
+ + self._model["CAPFT"][2] * tank_temp_C
+ + self._model["CAPFT"][5] * tank_temp_C**2
+ + self._model["CAPFT"][4] * source_temp_C * tank_temp_C
+ )
+
+ HEIRFT = (
+ self._model["HEIRFT"][0]
+ + self._model["HEIRFT"][1] * source_temp_C
+ + self._model["HEIRFT"][3] * source_temp_C**2
+ + self._model["HEIRFT"][2] * tank_temp_C
+ + self._model["HEIRFT"][5] * tank_temp_C**2
+ + self._model["HEIRFT"][4] * source_temp_C * tank_temp_C
+ )
+
+ # Partial Load Ratio (PLR)
+ PLR = heat_demand_kW / (self._model["Qref"] * CAPFT)
+
+ # HEIRFPLR calculation
+ HEIRFPLR = (
+ self._model["HEIRFPLR"][0]
+ + self._model["HEIRFPLR"][1] * PLR
+ + self._model["HEIRFPLR"][2] * PLR**2
+ )
+
+ # Power consumption (P) calculation
+ return self._model["Pref"] * CAPFT * HEIRFT * HEIRFPLR
+
+ def calc_cop(self, source_temp_C: float, tank_temp_C: float, heat_demand_kW: float):
+ assert heat_demand_kW is not None, "heat demand should be provided"
+ if heat_demand_kW == 0:
+ return 0
+ electrical_power_kW = self._calc_power(source_temp_C, tank_temp_C, heat_demand_kW)
+ if electrical_power_kW <= 0:
+ log.error(
+ "calculated power is negative: "
+ "hp model: %s source_temp: %s, "
+ "tank_temp: %s, heat_demand_kW: %s, calculated power: %s",
+ self.model_type.name,
+ round(source_temp_C, 2),
+ round(tank_temp_C, 2),
+ round(heat_demand_kW, 2),
+ round(electrical_power_kW, 2),
+ )
+ return 0
+ cop = heat_demand_kW / electrical_power_kW
+ if cop > 6:
+ log.error(
+ "calculated COP (%s) is unrealistic: "
+ "hp model: %s source_temp: %s, "
+ "tank_temp: %s, heat_demand_kW: %s, calculated power: %s",
+ round(cop, 2),
+ self.model_type.name,
+ round(source_temp_C, 2),
+ round(tank_temp_C, 2),
+ round(heat_demand_kW, 2),
+ round(electrical_power_kW, 2),
+ )
+ return cop
+
+
+class UniversalCOPModel(BaseCOPModel):
+ """Handle cop calculation independent of the heat pump model"""
+
+ def __init__(self, source_type: int = HeatPumpSourceType.AIR.value):
+ self._source_type = source_type
+
+ def calc_cop(
+ self, source_temp_C: float, tank_temp_C: float, heat_demand_kW: Optional[float]
+ ) -> float:
+ """COP model following https://www.nature.com/articles/s41597-019-0199-y"""
+ delta_temp = tank_temp_C - source_temp_C
+ if self._source_type == HeatPumpSourceType.AIR.value:
+ return 6.08 - 0.09 * delta_temp + 0.0005 * delta_temp**2
+ if self._source_type == HeatPumpSourceType.GROUND.value:
+ return 10.29 - 0.21 * delta_temp + 0.0012 * delta_temp**2
+ assert False, "Source type not supported"
+
+
+def cop_model_factory(
+ model_type: COPModelType, source_type: int = HeatPumpSourceType.AIR.value
+) -> BaseCOPModel:
+ """Return the correct COP model."""
+ if model_type == COPModelType.UNIVERSAL:
+ return UniversalCOPModel(source_type)
+ return IndividualCOPModel(model_type)
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/Elco_Aerotop_G07-14M_model_parameters.json b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/Elco_Aerotop_G07-14M_model_parameters.json
new file mode 100644
index 000000000..d9ce3117b
--- /dev/null
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/Elco_Aerotop_G07-14M_model_parameters.json
@@ -0,0 +1 @@
+{"CAPFT": [1.3674619178648815, 0.03804659465962401, -0.019681855839990714, 0.00035564628176876223, -0.0003621972557350528, 0.00020130672570195518], "HEIRFT": [0.0013367484492077253, -0.005549193693871857, 0.020206838944944794, -6.42720175278999e-05, -0.00023069214578574915, -7.601014281322094e-06], "HEIRFPLR": [-0.194398846570212, 1.468913188968992, -0.2833766224760481], "Qref": 16.18, "Pref": 7.6, "PLR_min": 0.2}
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/Elco_Aerotop_S09M-IR_model_parameters.json b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/Elco_Aerotop_S09M-IR_model_parameters.json
new file mode 100644
index 000000000..50ded42b7
--- /dev/null
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/Elco_Aerotop_S09M-IR_model_parameters.json
@@ -0,0 +1 @@
+{"CAPFT": [0.9300492302752958, 0.03526591169765436, -0.004968669510962087, 0.00041747226581356767, -0.0003022915023160877, 9.773660370027137e-06], "HEIRFT": [0.3556666343811108, -0.02211409627372074, 0.0089215929159423, 5.995243647605175e-05, -3.4550553532408657e-05, 0.00014357037230472436], "HEIRFPLR": [-0.049758215126849414, 1.5407231145753069, -0.4967040777633469], "Qref": 16.2, "Pref": 6.47, "PLR_min": 0.1}
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/hoval_UltraSource_B_comfort_C_11_model_parameters.json b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/hoval_UltraSource_B_comfort_C_11_model_parameters.json
new file mode 100644
index 000000000..e0a522201
--- /dev/null
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/cop_models/model_data/hoval_UltraSource_B_comfort_C_11_model_parameters.json
@@ -0,0 +1 @@
+{"CAPFT": [0.5152305055168107, 0.016858287502234393, 0.04002915190886247, -0.00029916924915052157, 6.765446186174362e-05, -0.000496776482422856], "HEIRFT": [0.8811301356647324, -0.0019346380513800977, -0.024648245650783343, -0.0003083507655603219, -0.0001586422096891705, 0.00039208468135561403], "HEIRFPLR": [-0.3720222858742652, 2.375345746851973, -1.0047114539738535], "Qref": 8.3, "Pref": 5.7, "PLR_min": 0.1}
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/heat_pump.py b/src/gsy_e/models/strategy/energy_parameters/heatpump/heat_pump.py
index 731720949..99757b070 100644
--- a/src/gsy_e/models/strategy/energy_parameters/heatpump/heat_pump.py
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/heat_pump.py
@@ -1,12 +1,15 @@
from abc import ABC, abstractmethod
from typing import Optional, Dict, Union, List
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
-from gsy_framework.enums import HeatPumpSourceType
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, FLOATING_POINT_TOLERANCE
from gsy_framework.read_user_profile import InputProfileTypes
+from gsy_framework.utils import convert_kJ_to_kWh, convert_kWh_to_kJ
from pendulum import DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_e.models.strategy.energy_parameters.heatpump.cop_models import (
+ COPModelType,
+ cop_model_factory,
+)
from gsy_e.models.strategy.energy_parameters.heatpump.tank import (
TankParameters,
AllTanksEnergyParameters,
@@ -177,6 +180,7 @@ def __init__(
consumption_kWh_measurement_uuid: Optional[str] = None,
source_type: int = ConstSettings.HeatPumpSettings.SOURCE_TYPE,
heat_demand_Q_profile: Optional[Union[str, float, Dict]] = None,
+ cop_model_type: COPModelType = COPModelType.UNIVERSAL,
):
super().__init__(maximum_power_rating_kW, tank_parameters)
@@ -210,7 +214,7 @@ def __init__(
None, source_temp_C_measurement_uuid, profile_type=InputProfileTypes.IDENTITY
)
- # self.min_temp_C = min_temp_C # for usage in the strategy
+ self._cop_model = cop_model_factory(cop_model_type, source_type)
def serialize(self):
"""Return dict with the current energy parameter values."""
@@ -233,8 +237,8 @@ def event_traded_energy(self, time_slot: DateTime, energy_kWh: float):
"""React to an event_traded_energy."""
self._decrement_posted_energy(time_slot, energy_kWh)
- traded_heat_energy = self._calc_Q_from_energy_kWh(time_slot, energy_kWh)
- self._state.tanks.increase_tanks_temp_from_heat_energy(traded_heat_energy, time_slot)
+ traded_heat_energy_kJ = self._calc_Q_kJ_from_energy_kWh(time_slot, energy_kWh)
+ self._state.tanks.increase_tanks_temp_from_heat_energy(traded_heat_energy_kJ, time_slot)
self._calculate_and_set_unmatched_demand(time_slot)
@@ -247,6 +251,8 @@ def _rotate_profiles(self, current_time_slot: Optional[DateTime] = None):
def _calc_energy_to_buy_maximum(self, time_slot: DateTime) -> float:
cop = self._state.heatpump.get_cop(time_slot)
+ if cop == 0:
+ return 0
max_energy_consumption_kWh = self._state.tanks.get_max_energy_consumption(cop, time_slot)
assert max_energy_consumption_kWh > -FLOATING_POINT_TOLERANCE
if max_energy_consumption_kWh > self._max_energy_consumption_kWh:
@@ -255,6 +261,8 @@ def _calc_energy_to_buy_maximum(self, time_slot: DateTime) -> float:
def _calc_energy_to_buy_minimum(self, time_slot: DateTime) -> float:
cop = self._state.heatpump.get_cop(time_slot)
+ if cop == 0:
+ return 0
min_energy_consumption_kWh = self._state.tanks.get_min_energy_consumption(cop, time_slot)
if min_energy_consumption_kWh > self._max_energy_consumption_kWh:
return self._max_energy_consumption_kWh
@@ -265,26 +273,29 @@ def _populate_state(self, time_slot: DateTime):
self._state.heatpump.set_cop(time_slot, self._calc_cop(time_slot))
if not self._heat_demand_Q_J:
- produced_heat_energy_KJ = self._calc_Q_from_energy_kWh(
+ produced_heat_energy_kJ = self._calc_Q_kJ_from_energy_kWh(
time_slot, self._consumption_kWh.profile[time_slot]
)
else:
- produced_heat_energy_KJ = self._heat_demand_Q_J.get_value(time_slot) / 1000.0
- energy_demand_kWh = self._calc_energy_kWh_from_Q(time_slot, produced_heat_energy_KJ)
+ produced_heat_energy_kJ = self._heat_demand_Q_J.get_value(time_slot) / 1000.0
+ energy_demand_kWh = self._calc_energy_kWh_from_Q_kJ(time_slot, produced_heat_energy_kJ)
self._consumption_kWh.profile[time_slot] = energy_demand_kWh
- self._state.heatpump.set_heat_demand(time_slot, produced_heat_energy_KJ * 1000)
- self._state.tanks.decrease_tanks_temp_from_heat_energy(produced_heat_energy_KJ, time_slot)
+ self._state.heatpump.set_heat_demand(time_slot, produced_heat_energy_kJ * 1000)
+ self._state.tanks.decrease_tanks_temp_from_heat_energy(produced_heat_energy_kJ, time_slot)
super()._populate_state(time_slot)
self._state.heatpump.set_energy_consumption_kWh(
time_slot, self._consumption_kWh.get_value(time_slot)
)
- def _calc_Q_from_energy_kWh(self, time_slot: DateTime, energy_kWh: float) -> float:
- return self._state.heatpump.get_cop(time_slot) * energy_kWh
+ def _calc_Q_kJ_from_energy_kWh(self, time_slot: DateTime, energy_kWh: float) -> float:
+ return convert_kWh_to_kJ(self._state.heatpump.get_cop(time_slot) * energy_kWh)
- def _calc_energy_kWh_from_Q(self, time_slot: DateTime, Q_energy_KJ: float) -> float:
- return Q_energy_KJ / self._state.heatpump.get_cop(time_slot)
+ def _calc_energy_kWh_from_Q_kJ(self, time_slot: DateTime, Q_energy_kJ: float) -> float:
+ cop = self._state.heatpump.get_cop(time_slot)
+ if cop == 0:
+ return 0
+ return convert_kJ_to_kWh(Q_energy_kJ / self._state.heatpump.get_cop(time_slot))
def _calc_cop(self, time_slot: DateTime) -> float:
"""
@@ -294,20 +305,17 @@ def _calc_cop(self, time_slot: DateTime) -> float:
Generally, the higher the temperature difference between the source and the sink,
the lower the efficiency of the heat pump (the lower COP).
"""
- return self._cop_model(
- self._state.tanks.get_average_tank_temperature(time_slot),
- self._source_temp_C.get_value(time_slot),
+ # 1 J = 1 W s
+ heat_demand_kW = (
+ self._heat_demand_Q_J.get_value(time_slot) / self._slot_length.total_seconds() / 1000
+ if self._heat_demand_Q_J
+ else None
+ )
+ return self._cop_model.calc_cop(
+ source_temp_C=self._source_temp_C.get_value(time_slot),
+ tank_temp_C=self._state.tanks.get_average_tank_temperature(time_slot),
+ heat_demand_kW=heat_demand_kW,
)
-
- def _cop_model(self, temp_current: float, temp_ambient: float) -> float:
- """COP model following https://www.nature.com/articles/s41597-019-0199-y"""
- delta_temp = temp_current - temp_ambient
- if self._source_type == HeatPumpSourceType.AIR.value:
- return 6.08 - 0.09 * delta_temp + 0.0005 * delta_temp**2
- if self._source_type == HeatPumpSourceType.GROUND.value:
- return 10.29 - 0.21 * delta_temp + 0.0012 * delta_temp**2
-
- raise HeatPumpEnergyParametersException("HeatPumpSourceType not supported")
def _calculate_and_set_unmatched_demand(self, time_slot: DateTime) -> None:
unmatched_energy_demand = self._state.tanks.get_unmatched_demand_kWh(time_slot)
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/tank.py b/src/gsy_e/models/strategy/energy_parameters/heatpump/tank.py
index bd2800237..d141390f6 100644
--- a/src/gsy_e/models/strategy/energy_parameters/heatpump/tank.py
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/tank.py
@@ -3,10 +3,11 @@
from statistics import mean
from typing import Dict, Union, List
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
from pendulum import DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, FLOATING_POINT_TOLERANCE
+from gsy_framework.utils import convert_kJ_to_kWh
+
from gsy_e.models.strategy.energy_parameters.heatpump.constants import (
WATER_DENSITY,
SPECIFIC_HEAT_CONST_WATER,
@@ -54,14 +55,14 @@ def get_results_dict(self, current_time_slot: DateTime):
"""Results dict with the results from the tank."""
return self._state.get_results_dict(current_time_slot)
- def increase_tank_temp_from_heat_energy(self, heat_energy: float, time_slot: DateTime):
+ def increase_tank_temp_from_heat_energy(self, heat_energy_kWh: float, time_slot: DateTime):
"""Increase the temperature of the water tank with the provided heat energy."""
- temp_increase_K = self._Q_kWh_to_temp_diff(heat_energy)
+ temp_increase_K = self._Q_kWh_to_temp_diff(heat_energy_kWh)
self._state.update_temp_increase_K(time_slot, temp_increase_K)
- def decrease_tank_temp_from_heat_energy(self, heat_energy: float, time_slot: DateTime):
+ def decrease_tank_temp_from_heat_energy(self, heat_energy_kWh: float, time_slot: DateTime):
"""Decrease the temperature of the water tank with the provided heat energy."""
- temp_decrease_K = self._Q_kWh_to_temp_diff(heat_energy)
+ temp_decrease_K = self._Q_kWh_to_temp_diff(heat_energy_kWh)
self._state.set_temp_decrease_K(time_slot, temp_decrease_K)
def increase_tank_temp_from_temp_delta(self, temp_diff: float, time_slot: DateTime):
@@ -109,18 +110,20 @@ def __init__(self, tank_parameters: List[TankParameters]):
TankEnergyParameters(tank, GlobalConfig.slot_length) for tank in tank_parameters
]
- def increase_tanks_temp_from_heat_energy(self, heat_energy: float, time_slot: DateTime):
+ def increase_tanks_temp_from_heat_energy(self, heat_energy_kJ: float, time_slot: DateTime):
"""Increase the temperature of the water tanks with the provided heat energy."""
# Split heat energy equally across tanks
- heat_energy_per_tank = heat_energy / len(self._tanks_energy_parameters)
+ heat_energy_per_tank_kJ = heat_energy_kJ / len(self._tanks_energy_parameters)
+ heat_energy_per_tank_kWh = convert_kJ_to_kWh(heat_energy_per_tank_kJ)
for tank in self._tanks_energy_parameters:
- tank.increase_tank_temp_from_heat_energy(heat_energy_per_tank, time_slot)
+ tank.increase_tank_temp_from_heat_energy(heat_energy_per_tank_kWh, time_slot)
- def decrease_tanks_temp_from_heat_energy(self, heat_energy: float, time_slot: DateTime):
+ def decrease_tanks_temp_from_heat_energy(self, heat_energy_kJ: float, time_slot: DateTime):
"""Decrease the temperature of the water tanks with the provided heat energy."""
- heat_energy_per_tank = heat_energy / len(self._tanks_energy_parameters)
+ heat_energy_per_tank_kJ = heat_energy_kJ / len(self._tanks_energy_parameters)
+ heat_energy_per_tank_kWh = convert_kJ_to_kWh(heat_energy_per_tank_kJ)
for tank in self._tanks_energy_parameters:
- tank.decrease_tank_temp_from_heat_energy(heat_energy_per_tank, time_slot)
+ tank.decrease_tank_temp_from_heat_energy(heat_energy_per_tank_kWh, time_slot)
def update_tanks_temperature(self, time_slot: DateTime):
"""
@@ -142,6 +145,8 @@ def get_max_energy_consumption(self, cop: float, time_slot: DateTime):
def get_min_energy_consumption(self, cop: float, time_slot: DateTime):
"""Get min energy consumption from all water tanks."""
+ if cop == 0:
+ return 0
min_energy_consumption_kWh = sum(
tank.get_min_energy_consumption(cop, time_slot)
for tank in self._tanks_energy_parameters
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/virtual_heat_pump.py b/src/gsy_e/models/strategy/energy_parameters/heatpump/virtual_heat_pump.py
index af752e9f5..b7c69f9e3 100644
--- a/src/gsy_e/models/strategy/energy_parameters/heatpump/virtual_heat_pump.py
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/virtual_heat_pump.py
@@ -1,11 +1,11 @@
import logging
from typing import Optional, Union, Dict, List
+from pendulum import DateTime
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
from gsy_framework.constants_limits import ConstSettings
from gsy_framework.read_user_profile import InputProfileTypes
-from pendulum import DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.energy_parameters.heatpump.constants import (
WATER_SPECIFIC_HEAT_CAPACITY,
DEFAULT_SOURCE_TEMPERATURE_C,
diff --git a/src/gsy_e/models/strategy/energy_parameters/heatpump/virtual_heatpump_solver.py b/src/gsy_e/models/strategy/energy_parameters/heatpump/virtual_heatpump_solver.py
index 8d8a378ed..4c1f66fa0 100644
--- a/src/gsy_e/models/strategy/energy_parameters/heatpump/virtual_heatpump_solver.py
+++ b/src/gsy_e/models/strategy/energy_parameters/heatpump/virtual_heatpump_solver.py
@@ -6,7 +6,7 @@
from gsy_framework.constants_limits import ConstSettings, GlobalConfig
from gsy_framework.utils import convert_W_to_kWh, convert_kWh_to_W
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.energy_parameters.heatpump.constants import (
WATER_SPECIFIC_HEAT_CAPACITY,
WATER_DENSITY,
diff --git a/src/gsy_e/models/strategy/forward/load.py b/src/gsy_e/models/strategy/forward/load.py
index 11ae3f7ee..02f83e921 100644
--- a/src/gsy_e/models/strategy/forward/load.py
+++ b/src/gsy_e/models/strategy/forward/load.py
@@ -1,12 +1,13 @@
from typing import Dict, TYPE_CHECKING
+from pendulum import DateTime, duration
from gsy_framework.data_classes import TraderDetails
from gsy_framework.enums import AvailableMarketTypes
-from pendulum import DateTime, duration
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.energy_parameters.energy_params_eb import (
- ConsumptionStandardProfileEnergyParameters)
+ ConsumptionStandardProfileEnergyParameters,
+)
from gsy_e.models.strategy.forward import ForwardStrategyBase
from gsy_e.models.strategy.forward.order_updater import ForwardOrderUpdaterParameters
@@ -17,16 +18,17 @@
DEFAULT_LOAD_ORDER_UPDATER_PARAMS = {
- AvailableMarketTypes.INTRADAY: ForwardOrderUpdaterParameters(
- duration(minutes=5), 10, 40, 10),
+ AvailableMarketTypes.INTRADAY: ForwardOrderUpdaterParameters(duration(minutes=5), 10, 40, 10),
AvailableMarketTypes.DAY_FORWARD: ForwardOrderUpdaterParameters(
- duration(minutes=30), 20, 40, 10),
- AvailableMarketTypes.WEEK_FORWARD: ForwardOrderUpdaterParameters(
- duration(days=1), 30, 50, 10),
+ duration(minutes=30), 20, 40, 10
+ ),
+ AvailableMarketTypes.WEEK_FORWARD: ForwardOrderUpdaterParameters(duration(days=1), 30, 50, 10),
AvailableMarketTypes.MONTH_FORWARD: ForwardOrderUpdaterParameters(
- duration(weeks=1), 40, 60, 20),
+ duration(weeks=1), 40, 60, 20
+ ),
AvailableMarketTypes.YEAR_FORWARD: ForwardOrderUpdaterParameters(
- duration(months=1), 50, 70, 50)
+ duration(months=1), 50, 70, 50
+ ),
}
@@ -35,10 +37,12 @@ class ForwardLoadStrategy(ForwardStrategyBase):
Strategy that models a Load that trades with a Standard Solar Profile on the forward
exchanges.
"""
+
def __init__(
- self, capacity_kW: float,
- order_updater_parameters: Dict[
- AvailableMarketTypes, ForwardOrderUpdaterParameters] = None):
+ self,
+ capacity_kW: float,
+ order_updater_parameters: Dict[AvailableMarketTypes, ForwardOrderUpdaterParameters] = None,
+ ):
if not order_updater_parameters:
order_updater_parameters = DEFAULT_LOAD_ORDER_UPDATER_PARAMS
super().__init__(order_updater_parameters)
@@ -52,63 +56,82 @@ def event_activate(self, **kwargs):
self._energy_params.event_activate_energy(self.area)
def remove_order(self, market: "ForwardMarketBase", market_slot: DateTime, order_uuid: str):
- bids = [bid
- for bid in market.slot_bid_mapping[market_slot]
- if bid.buyer.name == self.owner.name and bid.id == order_uuid]
+ bids = [
+ bid
+ for bid in market.slot_bid_mapping[market_slot]
+ if bid.buyer.name == self.owner.name and bid.id == order_uuid
+ ]
if not bids:
- self.log.error("Bid with id %s does not exist on the market %s %s.",
- order_uuid, market.market_type, market_slot)
+ self.log.error(
+ "Bid with id %s does not exist on the market %s %s.",
+ order_uuid,
+ market.market_type,
+ market_slot,
+ )
return
market.delete_bid(bids[0])
def remove_open_orders(self, market: "ForwardMarketBase", market_slot: DateTime):
- bids = [bid
- for bid in market.slot_bid_mapping[market_slot]
- if bid.buyer.name == self.owner.name]
+ bids = [
+ bid
+ for bid in market.slot_bid_mapping[market_slot]
+ if bid.buyer.name == self.owner.name
+ ]
for bid in bids:
market.delete_bid(bid)
def post_order(
- self, market: "ForwardMarketBase", market_slot: DateTime, order_rate: float = None,
- **kwargs):
+ self,
+ market: "ForwardMarketBase",
+ market_slot: DateTime,
+ order_rate: float = None,
+ **kwargs
+ ):
if not order_rate:
- order_rate = self._order_updaters[market][market_slot].get_energy_rate(
- self.area.now)
+ order_rate = self._order_updaters[market][market_slot].get_energy_rate(self.area.now)
capacity_percent = kwargs.get("capacity_percent")
if not capacity_percent:
capacity_percent = self._order_updaters[market][market_slot].capacity_percent / 100.0
max_energy_kWh = self._energy_params.peak_energy_kWh * capacity_percent
available_energy_kWh = self._energy_params.get_available_energy_kWh(
- market_slot, market.market_type)
+ market_slot, market.market_type
+ )
posted_energy_kWh = self._energy_params.get_posted_energy_kWh(
- market_slot, market.market_type)
+ market_slot, market.market_type
+ )
order_energy_kWh = min(available_energy_kWh - posted_energy_kWh, max_energy_kWh)
if order_energy_kWh <= FLOATING_POINT_TOLERANCE:
return
market.bid(
- order_rate * order_energy_kWh, order_energy_kWh,
- buyer=TraderDetails(self.owner.name, self.owner.uuid,
- self.owner.name, self.owner.uuid),
+ order_rate * order_energy_kWh,
+ order_energy_kWh,
+ buyer=TraderDetails(
+ self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid
+ ),
original_price=order_rate * order_energy_kWh,
- time_slot=market_slot)
+ time_slot=market_slot,
+ )
self._energy_params.increment_posted_energy(
- market_slot, order_energy_kWh, market.market_type)
+ market_slot, order_energy_kWh, market.market_type
+ )
def event_bid_traded(self, *, market_id: str, bid_trade: "Trade"):
"""Method triggered by the MarketEvent.BID_TRADED event."""
- market = [market
- for market in self.area.forward_markets.values()
- if market_id == market.id]
+ market = [
+ market for market in self.area.forward_markets.values() if market_id == market.id
+ ]
if not market:
return
if bid_trade.buyer.uuid != self.owner.uuid:
return
- self._energy_params.event_traded_energy(bid_trade.traded_energy,
- bid_trade.time_slot, market[0].market_type)
+ self._energy_params.event_traded_energy(
+ bid_trade.traded_energy, bid_trade.time_slot, market[0].market_type
+ )
self._energy_params.decrement_posted_energy(
- bid_trade.time_slot, bid_trade.traded_energy, market[0].market_type)
+ bid_trade.time_slot, bid_trade.traded_energy, market[0].market_type
+ )
diff --git a/src/gsy_e/models/strategy/forward/pv.py b/src/gsy_e/models/strategy/forward/pv.py
index ef250dd5e..30cff61ea 100644
--- a/src/gsy_e/models/strategy/forward/pv.py
+++ b/src/gsy_e/models/strategy/forward/pv.py
@@ -1,12 +1,13 @@
from typing import Dict, TYPE_CHECKING
+from pendulum import DateTime, duration
from gsy_framework.data_classes import TraderDetails
from gsy_framework.enums import AvailableMarketTypes
-from pendulum import DateTime, duration
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.energy_parameters.energy_params_eb import (
- ProductionStandardProfileEnergyParameters)
+ ProductionStandardProfileEnergyParameters,
+)
from gsy_e.models.strategy.forward import ForwardStrategyBase
from gsy_e.models.strategy.forward.order_updater import ForwardOrderUpdaterParameters
@@ -17,16 +18,17 @@
DEFAULT_PV_ORDER_UPDATER_PARAMS = {
- AvailableMarketTypes.INTRADAY: ForwardOrderUpdaterParameters(
- duration(minutes=5), 10, 10, 10),
+ AvailableMarketTypes.INTRADAY: ForwardOrderUpdaterParameters(duration(minutes=5), 10, 10, 10),
AvailableMarketTypes.DAY_FORWARD: ForwardOrderUpdaterParameters(
- duration(minutes=30), 10, 10, 10),
- AvailableMarketTypes.WEEK_FORWARD: ForwardOrderUpdaterParameters(
- duration(days=1), 10, 10, 10),
+ duration(minutes=30), 10, 10, 10
+ ),
+ AvailableMarketTypes.WEEK_FORWARD: ForwardOrderUpdaterParameters(duration(days=1), 10, 10, 10),
AvailableMarketTypes.MONTH_FORWARD: ForwardOrderUpdaterParameters(
- duration(weeks=1), 10, 10, 20),
+ duration(weeks=1), 10, 10, 20
+ ),
AvailableMarketTypes.YEAR_FORWARD: ForwardOrderUpdaterParameters(
- duration(months=1), 10, 10, 50)
+ duration(months=1), 10, 10, 50
+ ),
}
@@ -35,10 +37,12 @@ class ForwardPVStrategy(ForwardStrategyBase):
Strategy that models a PV that trades with a Standard Solar Profile on the forward
exchanges.
"""
+
def __init__(
- self, capacity_kW: float,
- order_updater_parameters: Dict[
- AvailableMarketTypes, ForwardOrderUpdaterParameters] = None):
+ self,
+ capacity_kW: float,
+ order_updater_parameters: Dict[AvailableMarketTypes, ForwardOrderUpdaterParameters] = None,
+ ):
if not order_updater_parameters:
order_updater_parameters = DEFAULT_PV_ORDER_UPDATER_PARAMS
@@ -53,36 +57,50 @@ def event_activate(self, **kwargs):
self._energy_params.event_activate_energy(self.area)
def remove_open_orders(self, market: "ForwardMarketBase", market_slot: DateTime):
- offers = [offer
- for offer in market.slot_offer_mapping[market_slot]
- if offer.seller.name == self.owner.name]
+ offers = [
+ offer
+ for offer in market.slot_offer_mapping[market_slot]
+ if offer.seller.name == self.owner.name
+ ]
for offer in offers:
market.delete_offer(offer)
def remove_order(self, market: "ForwardMarketBase", market_slot: DateTime, order_uuid: str):
- offers = [offer
- for offer in market.slot_offer_mapping[market_slot]
- if offer.seller.name == self.owner.name and offer.id == order_uuid]
+ offers = [
+ offer
+ for offer in market.slot_offer_mapping[market_slot]
+ if offer.seller.name == self.owner.name and offer.id == order_uuid
+ ]
if not offers:
- self.log.error("Bid with id %s does not exist on the market %s %s.",
- order_uuid, market.market_type, market_slot)
+ self.log.error(
+ "Bid with id %s does not exist on the market %s %s.",
+ order_uuid,
+ market.market_type,
+ market_slot,
+ )
return
market.delete_offer(offers[0])
- def post_order(self, market: "ForwardMarketBase", market_slot: DateTime,
- order_rate: float = None, **kwargs):
+ def post_order(
+ self,
+ market: "ForwardMarketBase",
+ market_slot: DateTime,
+ order_rate: float = None,
+ **kwargs
+ ):
if not order_rate:
- order_rate = self._order_updaters[market][market_slot].get_energy_rate(
- self.area.now)
+ order_rate = self._order_updaters[market][market_slot].get_energy_rate(self.area.now)
capacity_percent = kwargs.get("capacity_percent")
if not capacity_percent:
capacity_percent = self._order_updaters[market][market_slot].capacity_percent / 100.0
max_energy_kWh = self._energy_params.peak_energy_kWh * capacity_percent
available_energy_kWh = self._energy_params.get_available_energy_kWh(
- market_slot, market.market_type)
+ market_slot, market.market_type
+ )
posted_energy_kWh = self._energy_params.get_posted_energy_kWh(
- market_slot, market.market_type)
+ market_slot, market.market_type
+ )
order_energy_kWh = min(available_energy_kWh - posted_energy_kWh, max_energy_kWh)
@@ -90,25 +108,30 @@ def post_order(self, market: "ForwardMarketBase", market_slot: DateTime,
return
market.offer(
- order_rate * order_energy_kWh, order_energy_kWh,
+ order_rate * order_energy_kWh,
+ order_energy_kWh,
TraderDetails(self.owner.name, self.owner.uuid, self.owner.name, self.owner.uuid),
original_price=order_rate * order_energy_kWh,
- time_slot=market_slot)
+ time_slot=market_slot,
+ )
self._energy_params.increment_posted_energy(
- market_slot, order_energy_kWh, market.market_type)
+ market_slot, order_energy_kWh, market.market_type
+ )
def event_traded(self, *, market_id: str, trade: "Trade"):
"""Method triggered by the MarketEvent.OFFER_TRADED event."""
- market = [market
- for market in self.area.forward_markets.values()
- if market_id == market.id]
+ market = [
+ market for market in self.area.forward_markets.values() if market_id == market.id
+ ]
if not market:
return
if trade.seller.uuid != self.owner.uuid:
return
- self._energy_params.event_traded_energy(trade.traded_energy,
- trade.time_slot, market[0].market_type)
+ self._energy_params.event_traded_energy(
+ trade.traded_energy, trade.time_slot, market[0].market_type
+ )
self._energy_params.decrement_posted_energy(
- trade.time_slot, trade.traded_energy, market[0].market_type)
+ trade.time_slot, trade.traded_energy, market[0].market_type
+ )
diff --git a/src/gsy_e/models/strategy/heat_pump.py b/src/gsy_e/models/strategy/heat_pump.py
index c0a760d4d..d1afe4143 100644
--- a/src/gsy_e/models/strategy/heat_pump.py
+++ b/src/gsy_e/models/strategy/heat_pump.py
@@ -9,7 +9,7 @@
from gsy_framework.utils import convert_pendulum_to_str_in_dict
from gsy_framework.validators.heat_pump_validator import HeatPumpValidator
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
from gsy_e.gsy_e_core.util import (
get_market_maker_rate_from_time_slot,
get_feed_in_tariff_rate_from_time_slot,
@@ -17,10 +17,11 @@
from gsy_e.models.strategy.energy_parameters.heatpump.heat_pump import (
HeatPumpEnergyParameters,
TankParameters,
+ CombinedHeatpumpTanksState,
)
from gsy_e.models.strategy.order_updater import OrderUpdaterParameters, OrderUpdater
-from gsy_e.models.strategy.state import HeatPumpState
from gsy_e.models.strategy.trading_strategy_base import TradingStrategyBase
+from gsy_e.models.strategy.energy_parameters.heatpump.cop_models import COPModelType
if TYPE_CHECKING:
from gsy_e.models.market import MarketBase
@@ -60,12 +61,12 @@ def serialize(self):
"update_interval": self.update_interval,
"initial_buying_rate": (
self.initial_rate
- if isinstance(self.initial_rate, (type(None), float))
+ if isinstance(self.initial_rate, (type(None), int, float))
else convert_pendulum_to_str_in_dict(self.initial_rate)
),
"final_buying_rate": (
self.final_rate
- if isinstance(self.final_rate, (type(None), float))
+ if isinstance(self.final_rate, (type(None), int, float))
else convert_pendulum_to_str_in_dict(self.final_rate)
),
"use_market_maker_rate": self.use_market_maker_rate,
@@ -92,6 +93,7 @@ def __init__(
] = None,
preferred_buying_rate: float = ConstSettings.HeatPumpSettings.PREFERRED_BUYING_RATE,
heat_demand_Q_profile: Optional[Union[str, float, Dict]] = None,
+ cop_model_type: COPModelType = COPModelType.UNIVERSAL,
):
assert (
@@ -109,6 +111,7 @@ def __init__(
consumption_kWh_profile_uuid=consumption_kWh_profile_uuid,
source_type=source_type,
heat_demand_Q_profile=heat_demand_Q_profile,
+ cop_model_type=cop_model_type,
)
for tank in tank_parameters:
@@ -123,6 +126,7 @@ def __init__(
consumption_kWh_profile=consumption_kWh_profile,
consumption_kWh_profile_uuid=consumption_kWh_profile_uuid,
source_type=source_type,
+ heat_demand_Q_profile=heat_demand_Q_profile,
)
# needed for profile_handler
@@ -186,7 +190,7 @@ def deserialize_args(constructor_args: Dict) -> Dict:
return constructor_args
@property
- def state(self) -> HeatPumpState:
+ def state(self) -> CombinedHeatpumpTanksState:
return self._energy_params.combined_state
def event_activate(self, **kwargs):
@@ -312,6 +316,7 @@ def __init__(
AvailableMarketTypes, HeatPumpOrderUpdaterParameters
] = None,
preferred_buying_rate: float = ConstSettings.HeatPumpSettings.PREFERRED_BUYING_RATE,
+ cop_model_type: COPModelType = COPModelType.UNIVERSAL,
):
tank_parameters = [
TankParameters(
@@ -333,4 +338,5 @@ def __init__(
source_type,
order_updater_parameters,
preferred_buying_rate,
+ cop_model_type=cop_model_type,
)
diff --git a/src/gsy_e/models/strategy/load_hours.py b/src/gsy_e/models/strategy/load_hours.py
index 2073aec5e..9be80c118 100644
--- a/src/gsy_e/models/strategy/load_hours.py
+++ b/src/gsy_e/models/strategy/load_hours.py
@@ -15,34 +15,36 @@
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
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import Offer, TraderDetails
from gsy_framework.enums import SpotMarketTypeEnum
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
from gsy_e import constants
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
from gsy_e.gsy_e_core.exceptions import MarketException
from gsy_e.models.base import AssetType
from gsy_e.models.market import MarketBase
-from gsy_e.models.strategy.state import LoadState
from gsy_e.models.strategy import BidEnabledStrategy
from gsy_e.models.strategy.energy_parameters.load import LoadHoursEnergyParameters
from gsy_e.models.strategy.future.strategy import future_market_strategy_factory
+from gsy_e.models.strategy.mixins import UseMarketMakerMixin
from gsy_e.models.strategy.settlement.strategy import settlement_market_strategy_factory
+from gsy_e.models.strategy.state import LoadState
from gsy_e.models.strategy.update_frequency import TemplateStrategyBidUpdater
-from gsy_e.models.strategy.mixins import UseMarketMakerMixin
BalancingRatio = namedtuple("BalancingRatio", ("demand", "supply"))
@@ -56,21 +58,29 @@ 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,
+ ):
"""
Constructor of LoadHoursStrategy
:param avg_power_W: Power rating of load device
@@ -89,8 +99,13 @@ def __init__(self, avg_power_W, hrs_of_day=None,
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 +122,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 +219,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 +258,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 +267,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 +279,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 +293,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 +310,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 +328,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 +397,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 +435,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 +462,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 +487,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 +502,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/market_agents/balancing_agent.py b/src/gsy_e/models/strategy/market_agents/balancing_agent.py
index 150ef7a8e..c1c0f3fc5 100644
--- a/src/gsy_e/models/strategy/market_agents/balancing_agent.py
+++ b/src/gsy_e/models/strategy/market_agents/balancing_agent.py
@@ -15,11 +15,11 @@
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 gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import TraderDetails
from numpy.random import random
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.market_agents.one_sided_agent import OneSidedAgent
from gsy_e.models.strategy.market_agents.one_sided_engine import BalancingEngine
@@ -27,22 +27,31 @@
class BalancingAgent(OneSidedAgent):
"""Market agent for balancing market"""
- def __init__(self, owner, higher_market, lower_market,
- min_offer_age=ConstSettings.MASettings.MIN_OFFER_AGE):
+ def __init__(
+ self,
+ owner,
+ higher_market,
+ lower_market,
+ min_offer_age=ConstSettings.MASettings.MIN_OFFER_AGE,
+ ):
self.balancing_spot_trade_ratio = owner.balancing_spot_trade_ratio
- super().__init__(owner=owner,
- higher_market=higher_market,
- lower_market=lower_market,
- min_offer_age=min_offer_age)
+ super().__init__(
+ owner=owner,
+ higher_market=higher_market,
+ lower_market=lower_market,
+ min_offer_age=min_offer_age,
+ )
self.name = self.owner.name
def _create_engines(self):
self.engines = [
- BalancingEngine("High -> Low", self.higher_market, self.lower_market,
- self.min_offer_age, self),
- BalancingEngine("Low -> High", self.lower_market, self.higher_market,
- self.min_offer_age, self),
+ BalancingEngine(
+ "High -> Low", self.higher_market, self.lower_market, self.min_offer_age, self
+ ),
+ BalancingEngine(
+ "Low -> High", self.lower_market, self.higher_market, self.min_offer_age, self
+ ),
]
def __repr__(self):
@@ -50,10 +59,14 @@ def __repr__(self):
def event_tick(self):
super().event_tick()
- if self.lower_market.unmatched_energy_downward > 0.0 or \
- self.lower_market.unmatched_energy_upward > 0.0:
- self._trigger_balancing_trades(self.lower_market.unmatched_energy_upward,
- self.lower_market.unmatched_energy_downward)
+ if (
+ self.lower_market.unmatched_energy_downward > 0.0
+ or self.lower_market.unmatched_energy_upward > 0.0
+ ):
+ self._trigger_balancing_trades(
+ self.lower_market.unmatched_energy_upward,
+ self.lower_market.unmatched_energy_downward,
+ )
def event_offer_traded(self, *, market_id, trade):
market = self.get_market_from_market_id(market_id)
@@ -75,29 +88,30 @@ def event_bid_traded(self, *, market_id, bid_trade):
super().event_bid_traded(market_id=market_id, bid_trade=bid_trade)
def _calculate_and_buy_balancing_energy(self, market, trade):
- if trade.buyer.name != self.owner.name or \
- market.time_slot != self.lower_market.time_slot:
+ if trade.buyer.name != self.owner.name or market.time_slot != self.lower_market.time_slot:
return
- positive_balancing_energy = \
- trade.traded_energy * self.balancing_spot_trade_ratio + \
- self.lower_market.unmatched_energy_upward
- negative_balancing_energy = \
- trade.traded_energy * self.balancing_spot_trade_ratio + \
- self.lower_market.unmatched_energy_downward
+ positive_balancing_energy = (
+ trade.traded_energy * self.balancing_spot_trade_ratio
+ + self.lower_market.unmatched_energy_upward
+ )
+ negative_balancing_energy = (
+ trade.traded_energy * self.balancing_spot_trade_ratio
+ + self.lower_market.unmatched_energy_downward
+ )
self._trigger_balancing_trades(positive_balancing_energy, negative_balancing_energy)
def _trigger_balancing_trades(self, positive_balancing_energy, negative_balancing_energy):
for offer in self.lower_market.sorted_offers:
- if offer.energy > FLOATING_POINT_TOLERANCE and \
- positive_balancing_energy > FLOATING_POINT_TOLERANCE:
- balance_trade = self._balancing_trade(offer,
- positive_balancing_energy)
+ if (
+ offer.energy > FLOATING_POINT_TOLERANCE
+ and positive_balancing_energy > FLOATING_POINT_TOLERANCE
+ ):
+ balance_trade = self._balancing_trade(offer, positive_balancing_energy)
if balance_trade is not None:
positive_balancing_energy -= abs(balance_trade.traded_energy)
elif offer.energy < FLOATING_POINT_TOLERANCE < negative_balancing_energy:
- balance_trade = self._balancing_trade(offer,
- -negative_balancing_energy)
+ balance_trade = self._balancing_trade(offer, -negative_balancing_energy)
if balance_trade is not None:
negative_balancing_energy -= abs(balance_trade.traded_energy)
@@ -107,29 +121,35 @@ def _trigger_balancing_trades(self, positive_balancing_energy, negative_balancin
def _balancing_trade(self, offer, target_energy):
trade = None
buyer = TraderDetails(
- (self.owner.name
- if self.owner.name != offer.seller.name
- else f"{self.owner.name} Reserve"),
- self.owner.uuid)
+ (
+ self.owner.name
+ if self.owner.name != offer.seller.name
+ else f"{self.owner.name} Reserve"
+ ),
+ self.owner.uuid,
+ )
if abs(offer.energy) <= abs(target_energy):
- trade = self.lower_market.accept_offer(offer_or_id=offer,
- buyer=buyer,
- energy=offer.energy)
+ trade = self.lower_market.accept_offer(
+ offer_or_id=offer, buyer=buyer, energy=offer.energy
+ )
elif abs(offer.energy) >= abs(target_energy):
- trade = self.lower_market.accept_offer(offer_or_id=offer,
- buyer=buyer,
- energy=target_energy)
+ trade = self.lower_market.accept_offer(
+ offer_or_id=offer, buyer=buyer, energy=target_energy
+ )
return trade
def event_balancing_trade(self, *, market_id, trade, offer=None):
for engine in sorted(self.engines, key=lambda _: random()):
engine.event_offer_traded(trade=trade)
- def event_balancing_offer_split(self, *, market_id, original_offer, accepted_offer,
- residual_offer):
+ def event_balancing_offer_split(
+ self, *, market_id, original_offer, accepted_offer, residual_offer
+ ):
for engine in sorted(self.engines, key=lambda _: random()):
- engine.event_offer_split(market_id=market_id,
- original_offer=original_offer,
- accepted_offer=accepted_offer,
- residual_offer=residual_offer)
+ engine.event_offer_split(
+ market_id=market_id,
+ original_offer=original_offer,
+ accepted_offer=accepted_offer,
+ residual_offer=residual_offer,
+ )
diff --git a/src/gsy_e/models/strategy/market_agents/market_agent.py b/src/gsy_e/models/strategy/market_agents/market_agent.py
index 3d9cebaa6..2e71f823d 100644
--- a/src/gsy_e/models/strategy/market_agents/market_agent.py
+++ b/src/gsy_e/models/strategy/market_agents/market_agent.py
@@ -15,12 +15,11 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from typing import Optional, TYPE_CHECKING
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, TIME_FORMAT
from numpy.random import random
-
-from gsy_e.constants import TIME_FORMAT
from gsy_e.models.strategy import BaseStrategy, _TradeLookerUpper
if TYPE_CHECKING:
@@ -33,12 +32,20 @@ class MarketAgent(BaseStrategy):
def serialize(self):
return {
- "owner": self.owner, "higher_market": self.higher_market,
- "lower_market": self.lower_market, "min_offer_age": self.min_offer_age
+ "owner": self.owner,
+ "higher_market": self.higher_market,
+ "lower_market": self.lower_market,
+ "min_offer_age": self.min_offer_age,
}
- def __init__(self, *, owner: "Area", higher_market: "MarketBase", lower_market: "MarketBase",
- min_offer_age: int = ConstSettings.MASettings.MIN_OFFER_AGE):
+ def __init__(
+ self,
+ *,
+ owner: "Area",
+ higher_market: "MarketBase",
+ lower_market: "MarketBase",
+ min_offer_age: int = ConstSettings.MASettings.MIN_OFFER_AGE,
+ ):
"""
:param min_offer_age: Minimum age of offer before transferring
"""
@@ -61,8 +68,11 @@ def _create_engines(self):
@property
def time_slot_str(self) -> Optional[str]:
"""Return time_slot of the inter area agent. For future markets it is None."""
- return (self.higher_market.time_slot.format(TIME_FORMAT)
- if self.higher_market.time_slot else None)
+ return (
+ self.higher_market.time_slot.format(TIME_FORMAT)
+ if self.higher_market.time_slot
+ else None
+ )
@staticmethod
def _validate_constructor_arguments(min_offer_age: int):
diff --git a/src/gsy_e/models/strategy/market_agents/one_sided_engine.py b/src/gsy_e/models/strategy/market_agents/one_sided_engine.py
index 7a0acda3c..134bbf8b3 100644
--- a/src/gsy_e/models/strategy/market_agents/one_sided_engine.py
+++ b/src/gsy_e/models/strategy/market_agents/one_sided_engine.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 Dict, Optional # noqa
@@ -23,7 +24,7 @@
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.utils import limit_float_precision
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
from gsy_e.gsy_e_core.exceptions import MarketException, OfferNotFoundException
from gsy_e.gsy_e_core.util import short_offer_bid_log_str
@@ -34,6 +35,7 @@
class MAEngine:
"""Handle forwarding offers to the connected one-sided market."""
+
# pylint: disable=too-many-arguments,too-many-instance-attributes
def __init__(self, name: str, market_1, market_2, min_offer_age: int, owner):
@@ -60,9 +62,12 @@ def _update_offer_requirements_prices(self, offer):
if "price" in updated_requirement:
energy = updated_requirement.get("energy") or offer.energy
original_offer_price = updated_requirement["price"] + offer.accumulated_grid_fees
- updated_price = self.markets.target.fee_class.update_forwarded_offer_with_fee(
- updated_requirement["price"] / energy,
- original_offer_price / energy) * energy
+ updated_price = (
+ self.markets.target.fee_class.update_forwarded_offer_with_fee(
+ updated_requirement["price"] / energy, original_offer_price / energy
+ )
+ * energy
+ )
updated_requirement["price"] = updated_price
requirements.append(updated_requirement)
return requirements
@@ -70,18 +75,20 @@ def _update_offer_requirements_prices(self, offer):
def _offer_in_market(self, offer):
updated_price = limit_float_precision(
self.markets.target.fee_class.update_forwarded_offer_with_fee(
- offer.energy_rate, offer.original_energy_rate) * offer.energy)
+ offer.energy_rate, offer.original_energy_rate
+ )
+ * offer.energy
+ )
kwargs = {
"price": updated_price,
"energy": offer.energy,
"seller": TraderDetails(
- self.owner.name, self.owner.uuid,
- offer.seller.origin, offer.seller.origin_uuid
+ self.owner.name, self.owner.uuid, offer.seller.origin, offer.seller.origin_uuid
),
"original_price": offer.original_price,
"dispatch_event": False,
- "time_slot": offer.time_slot
+ "time_slot": offer.time_slot,
}
return self.owner.post_offer(market=self.markets.target, replace_existing=False, **kwargs)
@@ -96,8 +103,10 @@ def _forward_offer(self, offer: Offer) -> Optional[Offer]:
try:
forwarded_offer = self._offer_in_market(offer)
except MarketException:
- self.owner.log.debug("Offer is not forwarded because grid fees of the target market "
- "lead to a negative offer price.")
+ self.owner.log.debug(
+ "Offer is not forwarded because grid fees of the target market "
+ "lead to a negative offer price."
+ )
return None
self._add_to_forward_offers(offer, forwarded_offer)
@@ -157,8 +166,10 @@ def _propagate_offer(self, current_tick):
forwarded_offer = self._forward_offer(offer)
if forwarded_offer:
- self.owner.log.debug(f"Forwarded offer to {self.markets.source.name} "
- f"{self.owner.name}, {self.name} {forwarded_offer}")
+ self.owner.log.debug(
+ f"Forwarded offer to {self.markets.source.name} "
+ f"{self.owner.name}, {self.name} {forwarded_offer}"
+ )
def event_offer_traded(self, *, trade):
"""Perform actions that need to be done when OFFER_TRADED event is triggered."""
@@ -171,22 +182,30 @@ def event_offer_traded(self, *, trade):
# Offer was accepted in target market - buy in source
source_rate = offer_info.source_offer.energy_rate
target_rate = offer_info.target_offer.energy_rate
- assert abs(source_rate) <= abs(target_rate) + 0.0001, \
- f"offer: source_rate ({source_rate}) is not lower than target_rate ({target_rate})"
+ assert (
+ abs(source_rate) <= abs(target_rate) + 0.0001
+ ), f"offer: source_rate ({source_rate}) is not lower than target_rate ({target_rate})"
- updated_trade_bid_info = \
+ updated_trade_bid_info = (
self.markets.source.fee_class.update_forwarded_offer_trade_original_info(
- trade.offer_bid_trade_info, offer_info.source_offer)
+ trade.offer_bid_trade_info, offer_info.source_offer
+ )
+ )
try:
if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.ONE_SIDED.value:
# One sided market should subtract the fees
- trade_offer_rate = trade.trade_rate - \
- trade.fee_price / trade.traded_energy
+ trade_offer_rate = trade.trade_rate - trade.fee_price / trade.traded_energy
if not updated_trade_bid_info:
updated_trade_bid_info = TradeBidOfferInfo(
- None, None, (
- offer_info.source_offer.original_price /
- offer_info.source_offer.energy), source_rate, 0.)
+ None,
+ None,
+ (
+ offer_info.source_offer.original_price
+ / offer_info.source_offer.energy
+ ),
+ source_rate,
+ 0.0,
+ )
updated_trade_bid_info.trade_rate = trade_offer_rate
trade_source = self.owner.accept_offer(
@@ -194,15 +213,19 @@ def event_offer_traded(self, *, trade):
offer=offer_info.source_offer,
energy=trade.traded_energy,
buyer=TraderDetails(
- self.owner.name, self.owner.uuid,
- trade.buyer.origin, trade.buyer.origin_uuid),
+ self.owner.name,
+ self.owner.uuid,
+ trade.buyer.origin,
+ trade.buyer.origin_uuid,
+ ),
trade_bid_info=updated_trade_bid_info,
)
except OfferNotFoundException as ex:
raise OfferNotFoundException() from ex
self.owner.log.debug(
- f"[{self.markets.source.time_slot_str}] Offer accepted {trade_source}")
+ f"[{self.markets.source.time_slot_str}] Offer accepted {trade_source}"
+ )
self._delete_forwarded_offer_entries(offer_info.source_offer)
self.offer_age.pop(offer_info.source_offer.id, None)
@@ -258,12 +281,15 @@ def event_offer_split(self, *, market_id, original_offer, accepted_offer, residu
# offer was split in target market, also split in source market
local_offer = self.forwarded_offers[original_offer.id].source_offer
- original_price = local_offer.original_price \
- if local_offer.original_price is not None else local_offer.price
+ original_price = (
+ local_offer.original_price
+ if local_offer.original_price is not None
+ else local_offer.price
+ )
- local_split_offer, local_residual_offer = \
- self.markets.source.split_offer(local_offer, accepted_offer.energy,
- original_price)
+ local_split_offer, local_residual_offer = self.markets.source.split_offer(
+ local_offer, accepted_offer.energy, original_price
+ )
# add the new offers to forwarded_offers
self._add_to_forward_offers(local_residual_offer, residual_offer)
@@ -271,18 +297,23 @@ def event_offer_split(self, *, market_id, original_offer, accepted_offer, residu
elif market == self.markets.source and accepted_offer.id in self.forwarded_offers:
# offer was split in source market, also split in target market
- if not self.owner.usable_offer(accepted_offer) or \
- self.owner.name == accepted_offer.seller.name:
+ if (
+ not self.owner.usable_offer(accepted_offer)
+ or self.owner.name == accepted_offer.seller.name
+ ):
return
local_offer = self.forwarded_offers[original_offer.id].source_offer
- original_price = local_offer.original_price \
- if local_offer.original_price is not None else local_offer.price
+ original_price = (
+ local_offer.original_price
+ if local_offer.original_price is not None
+ else local_offer.price
+ )
- local_split_offer, local_residual_offer = \
- self.markets.target.split_offer(local_offer, accepted_offer.energy,
- original_price)
+ local_split_offer, local_residual_offer = self.markets.target.split_offer(
+ local_offer, accepted_offer.energy, original_price
+ )
# add the new offers to forwarded_offers
self._add_to_forward_offers(residual_offer, local_residual_offer)
@@ -294,9 +325,11 @@ def event_offer_split(self, *, market_id, original_offer, accepted_offer, residu
if original_offer.id in self.offer_age:
self.offer_age[residual_offer.id] = self.offer_age.pop(original_offer.id)
- self.owner.log.debug(f"Offer {short_offer_bid_log_str(local_offer)} was split into "
- f"{short_offer_bid_log_str(local_split_offer)} and "
- f"{short_offer_bid_log_str(local_residual_offer)}")
+ self.owner.log.debug(
+ f"Offer {short_offer_bid_log_str(local_offer)} was split into "
+ f"{short_offer_bid_log_str(local_split_offer)} and "
+ f"{short_offer_bid_log_str(local_residual_offer)}"
+ )
def _add_to_forward_offers(self, source_offer, target_offer):
offer_info = OfferInfo(Offer.copy(source_offer), Offer.copy(target_offer))
@@ -305,8 +338,10 @@ def _add_to_forward_offers(self, source_offer, target_offer):
def event_offer(self, offer: Offer) -> None:
"""Perform actions on the event of the creation of a new offer."""
- if (ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.TWO_SIDED.value and
- self.min_offer_age == 0):
+ if (
+ ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.TWO_SIDED.value
+ and self.min_offer_age == 0
+ ):
# Propagate offer immediately if the MIN_OFFER_AGE is set to zero.
if offer.id not in self.offer_age:
self.offer_age[offer.id] = self._current_tick
@@ -330,8 +365,10 @@ def event_offer(self, offer: Offer) -> None:
forwarded_offer = self._forward_offer(offer)
if forwarded_offer:
- self.owner.log.debug(f"Forwarded offer to {self.markets.source.name} "
- f"{self.owner.name}, {self.name} {forwarded_offer}")
+ self.owner.log.debug(
+ f"Forwarded offer to {self.markets.source.name} "
+ f"{self.owner.name}, {self.name} {forwarded_offer}"
+ )
class BalancingEngine(MAEngine):
@@ -339,10 +376,12 @@ class BalancingEngine(MAEngine):
def _forward_offer(self, offer):
forwarded_balancing_offer = self.markets.target.balancing_offer(
- offer.price, offer.energy,
+ offer.price,
+ offer.energy,
TraderDetails(
- self.owner.name, self.owner.uuid, offer.seller.origin, offer.seller.origin_uuid),
- from_agent=True
+ self.owner.name, self.owner.uuid, offer.seller.origin, offer.seller.origin_uuid
+ ),
+ from_agent=True,
)
self._add_to_forward_offers(offer, forwarded_balancing_offer)
self.owner.log.trace(f"Forwarding balancing offer {offer} to {forwarded_balancing_offer}")
diff --git a/src/gsy_e/models/strategy/market_agents/two_sided_engine.py b/src/gsy_e/models/strategy/market_agents/two_sided_engine.py
index b56a84553..59175cb16 100644
--- a/src/gsy_e/models/strategy/market_agents/two_sided_engine.py
+++ b/src/gsy_e/models/strategy/market_agents/two_sided_engine.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 Dict, TYPE_CHECKING
@@ -23,7 +24,7 @@
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.utils import limit_float_precision
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
from gsy_e.gsy_e_core.exceptions import BidNotFoundException, MarketException
from gsy_e.gsy_e_core.util import short_offer_bid_log_str
from gsy_e.models.strategy.market_agents.one_sided_engine import MAEngine
@@ -36,10 +37,18 @@
class TwoSidedEngine(MAEngine):
"""Handle forwarding offers and bids to the connected two-sided market."""
+
# pylint: disable = too-many-arguments
- def __init__(self, name: str, market_1, market_2, min_offer_age: int, min_bid_age: int,
- owner: "MarketAgent"):
+ def __init__(
+ self,
+ name: str,
+ market_1,
+ market_2,
+ min_offer_age: int,
+ min_bid_age: int,
+ owner: "MarketAgent",
+ ):
super().__init__(name, market_1, market_2, min_offer_age, owner)
self.forwarded_bids: Dict[str, BidInfo] = {}
self.bid_trade_residual: Dict[str, Bid] = {}
@@ -47,8 +56,10 @@ def __init__(self, name: str, market_1, market_2, min_offer_age: int, min_bid_ag
self.bid_age: Dict[str, int] = {}
def __repr__(self):
- return "".format(s=self)
+ return (
+ "".format(s=self)
+ )
def _update_requirements_prices(self, bid):
requirements = []
@@ -57,9 +68,12 @@ def _update_requirements_prices(self, bid):
if "price" in updated_requirement:
energy = updated_requirement.get("energy") or bid.energy
original_bid_price = updated_requirement["price"] + bid.accumulated_grid_fees
- updated_price = self.markets.source.fee_class.update_forwarded_bid_with_fee(
- updated_requirement["price"] / energy,
- original_bid_price / energy) * energy
+ updated_price = (
+ self.markets.source.fee_class.update_forwarded_bid_with_fee(
+ updated_requirement["price"] / energy, original_bid_price / energy
+ )
+ * energy
+ )
updated_requirement["price"] = updated_price
requirements.append(updated_requirement)
return requirements
@@ -72,22 +86,30 @@ def _forward_bid(self, bid):
self.owner.log.debug("Bid is not forwarded because price < 0")
return None
try:
- updated_price = limit_float_precision((
- self.markets.source.fee_class.update_forwarded_bid_with_fee(
- bid.energy_rate, bid.original_energy_rate)) * bid.energy)
+ updated_price = limit_float_precision(
+ (
+ self.markets.source.fee_class.update_forwarded_bid_with_fee(
+ bid.energy_rate, bid.original_energy_rate
+ )
+ )
+ * bid.energy
+ )
forwarded_bid = self.markets.target.bid(
price=updated_price,
energy=bid.energy,
buyer=TraderDetails(
- self.owner.name, self.owner.uuid, bid.buyer.origin, bid.buyer.origin_uuid),
+ self.owner.name, self.owner.uuid, bid.buyer.origin, bid.buyer.origin_uuid
+ ),
original_price=bid.original_price,
dispatch_event=False,
- time_slot=bid.time_slot
+ time_slot=bid.time_slot,
)
except MarketException:
- self.owner.log.debug("Bid is not forwarded because grid fees of the target market "
- "lead to a negative bid price.")
+ self.owner.log.debug(
+ "Bid is not forwarded because grid fees of the target market "
+ "lead to a negative bid price."
+ )
return None
self._add_to_forward_bids(bid, forwarded_bid)
@@ -138,8 +160,9 @@ def _delete_forwarded_bids(self, bid_info):
try:
self.markets.target.delete_bid(bid_info.target_bid)
except BidNotFoundException:
- self.owner.log.trace(f"Bid {bid_info.target_bid.id} not "
- f"found in the target market.")
+ self.owner.log.trace(
+ f"Bid {bid_info.target_bid.id} not " f"found in the target market."
+ )
self._delete_forwarded_bid_entries(bid_info.source_bid)
def event_bid_traded(self, *, bid_trade):
@@ -154,36 +177,43 @@ def event_bid_traded(self, *, bid_trade):
if not market_bid:
return
- assert market_bid.energy - bid_trade.traded_energy >= -FLOATING_POINT_TOLERANCE, \
- "Traded bid on target market has more energy than the market bid."
+ assert (
+ market_bid.energy - bid_trade.traded_energy >= -FLOATING_POINT_TOLERANCE
+ ), "Traded bid on target market has more energy than the market bid."
source_rate = bid_info.source_bid.energy_rate
target_rate = bid_info.target_bid.energy_rate
- assert abs(source_rate) + FLOATING_POINT_TOLERANCE >= abs(target_rate), \
- f"bid: source_rate ({source_rate}) is not lower than target_rate ({target_rate})"
+ assert abs(source_rate) + FLOATING_POINT_TOLERANCE >= abs(
+ target_rate
+ ), f"bid: source_rate ({source_rate}) is not lower than target_rate ({target_rate})"
if bid_trade.offer_bid_trade_info is not None:
# Adapt trade_offer_info received by the trade to include source market grid fees,
# which was skipped when accepting the bid during the trade operation.
- updated_trade_offer_info = \
+ updated_trade_offer_info = (
self.markets.source.fee_class.propagate_original_offer_info_on_bid_trade(
bid_trade.offer_bid_trade_info
)
+ )
else:
updated_trade_offer_info = bid_trade.offer_bid_trade_info
- trade_offer_info = \
+ trade_offer_info = (
self.markets.source.fee_class.update_forwarded_bid_trade_original_info(
updated_trade_offer_info, market_bid
)
+ )
self.markets.source.accept_bid(
bid=market_bid,
energy=bid_trade.traded_energy,
seller=TraderDetails(
- self.owner.name, self.owner.uuid,
- bid_trade.seller.origin, bid_trade.seller.origin_uuid),
+ self.owner.name,
+ self.owner.uuid,
+ bid_trade.seller.origin,
+ bid_trade.seller.origin_uuid,
+ ),
trade_offer_info=trade_offer_info,
- offer=None
+ offer=None,
)
self._delete_forwarded_bids(bid_info)
self.bid_age.pop(bid_info.source_bid.id, None)
@@ -198,8 +228,10 @@ def event_bid_traded(self, *, bid_trade):
if bid_trade.residual:
self._forward_bid(bid_trade.residual)
else:
- raise Exception(f"Invalid bid state for MA {self.owner.name}: "
- f"traded bid {bid_trade} was not in offered bids tuple {bid_info}")
+ raise Exception(
+ f"Invalid bid state for MA {self.owner.name}: "
+ f"traded bid {bid_trade} was not in offered bids tuple {bid_info}"
+ )
def event_bid_deleted(self, *, bid):
"""Perform actions that need to be done when BID_DELETED event is triggered."""
@@ -220,8 +252,9 @@ def event_bid_deleted(self, *, bid):
self._delete_forwarded_bid_entries(bid_info.source_bid)
self.bid_age.pop(bid_info.source_bid.id, None)
- 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:
"""Perform actions that need to be done when BID_SPLIT event is triggered."""
market = self.owner.get_market_from_market_id(market_id)
if market is None:
@@ -232,18 +265,23 @@ def event_bid_split(self, *, market_id: str, original_bid: Bid,
# in the source market
local_bid = self.forwarded_bids[original_bid.id].source_bid
- original_price = local_bid.original_price \
- if local_bid.original_price is not None else local_bid.price
+ original_price = (
+ local_bid.original_price
+ if local_bid.original_price is not None
+ else local_bid.price
+ )
- local_split_bid, local_residual_bid = \
- self.markets.source.split_bid(local_bid, accepted_bid.energy, original_price)
+ local_split_bid, local_residual_bid = self.markets.source.split_bid(
+ local_bid, accepted_bid.energy, original_price
+ )
# add the new bids to forwarded_bids
self._add_to_forward_bids(local_residual_bid, residual_bid)
self._add_to_forward_bids(local_split_bid, accepted_bid)
self.bid_age[local_residual_bid.id] = self.bid_age.pop(
- local_bid.id, self._current_tick)
+ local_bid.id, self._current_tick
+ )
elif market == self.markets.source and accepted_bid.id in self.forwarded_bids:
# bid in the source market was split, also split the corresponding forwarded bid
@@ -253,11 +291,15 @@ def event_bid_split(self, *, market_id: str, original_bid: Bid,
local_bid = self.forwarded_bids[original_bid.id].source_bid
- original_price = local_bid.original_price \
- if local_bid.original_price is not None else local_bid.price
+ original_price = (
+ local_bid.original_price
+ if local_bid.original_price is not None
+ else local_bid.price
+ )
- local_split_bid, local_residual_bid = \
- self.markets.target.split_bid(local_bid, accepted_bid.energy, original_price)
+ local_split_bid, local_residual_bid = self.markets.target.split_bid(
+ local_bid, accepted_bid.energy, original_price
+ )
# add the new bids to forwarded_bids
self._add_to_forward_bids(residual_bid, local_residual_bid)
@@ -268,9 +310,11 @@ def event_bid_split(self, *, market_id: str, original_bid: Bid,
else:
return
- self.owner.log.debug(f"Bid {short_offer_bid_log_str(local_bid)} was split into "
- f"{short_offer_bid_log_str(local_split_bid)} and "
- f"{short_offer_bid_log_str(local_residual_bid)}")
+ self.owner.log.debug(
+ f"Bid {short_offer_bid_log_str(local_bid)} was split into "
+ f"{short_offer_bid_log_str(local_split_bid)} and "
+ f"{short_offer_bid_log_str(local_residual_bid)}"
+ )
def _add_to_forward_bids(self, source_bid, target_bid):
bid_info = BidInfo(source_bid, target_bid)
@@ -279,8 +323,10 @@ def _add_to_forward_bids(self, source_bid, target_bid):
def event_bid(self, bid: Bid) -> None:
"""Perform actions on the event of the creation of a new bid."""
- if (ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.TWO_SIDED.value and
- self.min_bid_age == 0):
+ if (
+ ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.TWO_SIDED.value
+ and self.min_bid_age == 0
+ ):
# Propagate bid immediately if the MIN_BID_AGE is set to zero.
source_bid = self.markets.source.bids.get(bid.id)
if not source_bid:
diff --git a/src/gsy_e/models/strategy/smart_meter.py b/src/gsy_e/models/strategy/smart_meter.py
index 4ca699126..0fde6e52c 100644
--- a/src/gsy_e/models/strategy/smart_meter.py
+++ b/src/gsy_e/models/strategy/smart_meter.py
@@ -17,7 +17,7 @@
from pathlib import Path
from typing import Dict, Union
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import Offer, TraderDetails
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.read_user_profile import InputProfileTypes, read_arbitrary_profile
@@ -27,7 +27,6 @@
from pendulum import duration
from gsy_e import constants
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.gsy_e_core.exceptions import GSyException, MarketException
from gsy_e.models.base import AssetType
from gsy_e.models.market import MarketBase
@@ -35,8 +34,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 +53,24 @@ 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,
):
"""
Args:
@@ -98,7 +98,8 @@ def __init__(
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 +112,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 +122,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 +131,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 +154,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 +175,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 +186,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 +247,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 +264,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 +286,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 +322,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 +333,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 +376,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 +387,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 +408,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 +428,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 +445,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 +458,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 +541,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/state/base_states.py b/src/gsy_e/models/strategy/state/base_states.py
index 2381ed6ae..641039eb3 100644
--- a/src/gsy_e/models/strategy/state/base_states.py
+++ b/src/gsy_e/models/strategy/state/base_states.py
@@ -15,15 +15,16 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from abc import ABC, abstractmethod
from math import copysign
from typing import Dict, Optional
-from gsy_framework.utils import (
- convert_pendulum_to_str_in_dict, convert_str_to_pendulum_in_dict)
from pendulum import DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
+from gsy_framework.utils import convert_pendulum_to_str_in_dict, convert_str_to_pendulum_in_dict
+
from gsy_e.gsy_e_core.util import is_time_slot_in_past_markets
@@ -99,7 +100,8 @@ def __init__(self):
# pylint: disable=unused-argument, no-self-use
def _calculate_unsettled_energy_kWh(
- self, measured_energy_kWh: float, time_slot: DateTime) -> float:
+ self, measured_energy_kWh: float, time_slot: DateTime
+ ) -> float:
"""
Calculates the unsettled energy (produced or consumed) in kWh.
Args:
@@ -123,9 +125,11 @@ def set_energy_measurement_kWh(self, energy_kWh: float, time_slot: DateTime) ->
"""
self._energy_measurement_kWh[time_slot] = energy_kWh
self._forecast_measurement_deviation_kWh[time_slot] = self._calculate_unsettled_energy_kWh(
- energy_kWh, time_slot)
- self._unsettled_deviation_kWh[time_slot] = (
- abs(self._forecast_measurement_deviation_kWh[time_slot]))
+ energy_kWh, time_slot
+ )
+ self._unsettled_deviation_kWh[time_slot] = abs(
+ self._forecast_measurement_deviation_kWh[time_slot]
+ )
def get_energy_measurement_kWh(self, time_slot: DateTime) -> float:
"""
@@ -186,8 +190,7 @@ def get_unsettled_deviation_kWh(self, time_slot: DateTime) -> float:
"""
return self._unsettled_deviation_kWh.get(time_slot)
- def get_signed_unsettled_deviation_kWh(
- self, time_slot: DateTime) -> Optional[float]:
+ def get_signed_unsettled_deviation_kWh(self, time_slot: DateTime) -> Optional[float]:
"""
Get the unsettled energy deviation of forecasted energy from measurement by the device
in the given market slot including the correct sign that shows the direction
@@ -205,7 +208,8 @@ def get_signed_unsettled_deviation_kWh(
return None
def decrement_unsettled_deviation(
- self, purchased_energy_kWh: float, time_slot: DateTime) -> None:
+ self, purchased_energy_kWh: float, time_slot: DateTime
+ ) -> None:
"""
Decrease the device unsettled energy in a specific market slot.
Args:
@@ -218,7 +222,8 @@ def decrement_unsettled_deviation(
self._unsettled_deviation_kWh[time_slot] -= purchased_energy_kWh
assert self._unsettled_deviation_kWh[time_slot] >= -FLOATING_POINT_TOLERANCE, (
f"Unsettled energy deviation fell below zero "
- f"({self._unsettled_deviation_kWh[time_slot]}).")
+ f"({self._unsettled_deviation_kWh[time_slot]})."
+ )
class ConsumptionState(ProsumptionInterface):
@@ -237,7 +242,8 @@ def get_state(self) -> Dict:
state = super().get_state()
consumption_state = {
"desired_energy_Wh": convert_pendulum_to_str_in_dict(self._desired_energy_Wh),
- "total_energy_demanded_Wh": self._total_energy_demanded_Wh}
+ "total_energy_demanded_Wh": self._total_energy_demanded_Wh,
+ }
# The inherited state should not have keys with the same name (to avoid overwriting them)
conflicting_keys = state.keys() & consumption_state.keys()
assert not conflicting_keys, f"Conflicting state values found for {conflicting_keys}."
@@ -248,8 +254,9 @@ def get_state(self) -> Dict:
def restore_state(self, state_dict: Dict):
super().restore_state(state_dict)
- self._desired_energy_Wh.update(convert_str_to_pendulum_in_dict(
- state_dict["desired_energy_Wh"]))
+ self._desired_energy_Wh.update(
+ convert_str_to_pendulum_in_dict(state_dict["desired_energy_Wh"])
+ )
self._total_energy_demanded_Wh = state_dict["total_energy_demanded_Wh"]
def get_energy_requirement_Wh(self, time_slot: DateTime, default_value: float = 0.0) -> float:
@@ -265,7 +272,7 @@ def set_desired_energy(self, energy: float, time_slot: DateTime, overwrite=False
def update_total_demanded_energy(self, time_slot: DateTime) -> None:
"""Accumulate the _total_energy_demanded_Wh based on the desired energy per time_slot."""
- self._total_energy_demanded_Wh += self._desired_energy_Wh.get(time_slot, 0.)
+ self._total_energy_demanded_Wh += self._desired_energy_Wh.get(time_slot, 0.0)
def can_buy_more_energy(self, time_slot: DateTime) -> bool:
"""Check whether the consumer can but more energy in the passed time_slot."""
@@ -282,12 +289,14 @@ def calculate_energy_to_accept(self, offer_energy_Wh: float, time_slot: DateTime
return min(offer_energy_Wh, self._energy_requirement_Wh[time_slot])
def decrement_energy_requirement(
- self, purchased_energy_Wh: float, time_slot: DateTime, area_name: str) -> None:
+ self, purchased_energy_Wh: float, time_slot: DateTime, area_name: str
+ ) -> None:
"""Decrease the energy required by the device in a specific market slot."""
self._energy_requirement_Wh[time_slot] -= purchased_energy_Wh
assert self._energy_requirement_Wh[time_slot] >= -FLOATING_POINT_TOLERANCE, (
f"Energy requirement for device {area_name} fell below zero "
- f"({self._energy_requirement_Wh[time_slot]}).")
+ f"({self._energy_requirement_Wh[time_slot]})."
+ )
def delete_past_state_values(self, current_time_slot: DateTime):
"""Delete data regarding energy consumption for past market slots."""
@@ -318,8 +327,10 @@ def get_state(self) -> Dict:
state = super().get_state()
production_state = {
"available_energy_kWh": convert_pendulum_to_str_in_dict(self._available_energy_kWh),
- "energy_production_forecast_kWh":
- convert_pendulum_to_str_in_dict(self._energy_production_forecast_kWh)}
+ "energy_production_forecast_kWh": convert_pendulum_to_str_in_dict(
+ self._energy_production_forecast_kWh
+ ),
+ }
# The inherited state should not have keys with the same name (to avoid overwriting them)
conflicting_keys = state.keys() & production_state.keys()
assert not conflicting_keys, f"Conflicting state values found for {conflicting_keys}."
@@ -332,12 +343,15 @@ def restore_state(self, state_dict: Dict):
super().restore_state(state_dict)
self._available_energy_kWh.update(
- convert_str_to_pendulum_in_dict(state_dict["available_energy_kWh"]))
+ convert_str_to_pendulum_in_dict(state_dict["available_energy_kWh"])
+ )
self._energy_production_forecast_kWh.update(
- convert_str_to_pendulum_in_dict(state_dict["energy_production_forecast_kWh"]))
+ convert_str_to_pendulum_in_dict(state_dict["energy_production_forecast_kWh"])
+ )
- def set_available_energy(self, energy_kWh: float, time_slot: DateTime,
- overwrite: bool = False) -> None:
+ def set_available_energy(
+ self, energy_kWh: float, time_slot: DateTime, overwrite: bool = False
+ ) -> None:
"""Set the available energy in the passed time_slot.
If overwrite is True, set the available energy even if the time_slot is already tracked.
@@ -356,13 +370,15 @@ def get_available_energy_kWh(self, time_slot: DateTime, default_value: float = 0
assert available_energy >= -FLOATING_POINT_TOLERANCE
return available_energy
- def decrement_available_energy(self, sold_energy_kWh: float, time_slot: DateTime,
- area_name: str) -> None:
+ def decrement_available_energy(
+ self, sold_energy_kWh: float, time_slot: DateTime, area_name: str
+ ) -> None:
"""Decrement the available energy after a successful trade."""
self._available_energy_kWh[time_slot] -= sold_energy_kWh
assert self._available_energy_kWh[time_slot] >= -FLOATING_POINT_TOLERANCE, (
f"Available energy for device {area_name} fell below zero "
- f"({self._available_energy_kWh[time_slot]}).")
+ f"({self._available_energy_kWh[time_slot]})."
+ )
def delete_past_state_values(self, current_time_slot: DateTime):
"""Delete data regarding energy production for past market slots."""
diff --git a/src/gsy_e/models/strategy/state/storage_state.py b/src/gsy_e/models/strategy/state/storage_state.py
index 7fda6546d..4d34a2ec2 100644
--- a/src/gsy_e/models/strategy/state/storage_state.py
+++ b/src/gsy_e/models/strategy/state/storage_state.py
@@ -22,7 +22,7 @@
from typing import Dict, List, Optional
from dataclasses import dataclass
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, FLOATING_POINT_TOLERANCE
from gsy_framework.utils import (
convert_pendulum_to_str_in_dict,
convert_str_to_pendulum_in_dict,
@@ -31,7 +31,6 @@
)
from pendulum import DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.gsy_e_core.util import is_time_slot_in_past_markets, write_default_to_dict
from gsy_e.models.strategy.state.base_states import StateInterface
diff --git a/src/gsy_e/models/strategy/storage.py b/src/gsy_e/models/strategy/storage.py
index 841572f1d..64d4c9334 100644
--- a/src/gsy_e/models/strategy/storage.py
+++ b/src/gsy_e/models/strategy/storage.py
@@ -21,7 +21,7 @@
from logging import getLogger
from typing import Union, Optional
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE
from gsy_framework.data_classes import TraderDetails
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.exceptions import GSyException
@@ -33,7 +33,6 @@
from gsy_e import constants
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
from gsy_e.gsy_e_core.exceptions import MarketException
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.models.base import AssetType
from gsy_e.models.strategy.state import ESSEnergyOrigin, StorageState, StorageLosses
from gsy_e.models.strategy import BidEnabledStrategy
diff --git a/src/gsy_e/resources/create_profile.py b/src/gsy_e/resources/create_profile.py
index cdf4f4dda..a63501847 100755
--- a/src/gsy_e/resources/create_profile.py
+++ b/src/gsy_e/resources/create_profile.py
@@ -5,18 +5,18 @@
./create_profile.py -i ./LOAD_DATA_1.csv -o ./LOAD_DATA_1_5d.csv -d 5
"""
-from pendulum import from_format, DateTime
-import csv
import argparse
+import csv
-from gsy_e.constants import TIME_FORMAT, DATE_TIME_FORMAT
+from gsy_framework.constants_limits import TIME_FORMAT, DATE_TIME_FORMAT
+from pendulum import from_format, DateTime
def read_daily_profile_todict(daily_profile_fn, separator):
outdict = {}
header = []
firstline = True
- with open(daily_profile_fn, 'r') as csv_file:
+ with open(daily_profile_fn, "r") as csv_file:
file_reader = csv.reader(csv_file, delimiter=separator)
for row in file_reader:
if firstline:
@@ -29,15 +29,16 @@ def read_daily_profile_todict(daily_profile_fn, separator):
def write_profile_todict(profile_dict, header, outfile, separator):
- with open(outfile, 'w') as csv_file:
+ with open(outfile, "w") as csv_file:
writer = csv.writer(csv_file, delimiter=separator)
writer.writerow(header)
for time, value in profile_dict.items():
writer.writerow([time.format(DATE_TIME_FORMAT), value])
-def create_profile_from_daily_profile(n_days=365, year=2019, daily_profile_fn=None, out_fn=None,
- separator=","):
+def create_profile_from_daily_profile(
+ n_days=365, year=2019, daily_profile_fn=None, out_fn=None, separator=","
+):
if daily_profile_fn is None:
raise ValueError("No daily_profile_fn was provided")
@@ -45,8 +46,9 @@ def create_profile_from_daily_profile(n_days=365, year=2019, daily_profile_fn=No
profile_dict = {}
for day in range(1, n_days):
for time, value in daily_profile_dict.items():
- profile_dict[DateTime(year, 1, 1).add(
- days=day - 1, hours=time.hour, minutes=time.minute)] = value
+ profile_dict[
+ DateTime(year, 1, 1).add(days=day - 1, hours=time.hour, minutes=time.minute)
+ ] = value
if out_fn is None:
out_fn = daily_profile_fn.replace(".csv", "_year.csv")
@@ -54,14 +56,20 @@ def create_profile_from_daily_profile(n_days=365, year=2019, daily_profile_fn=No
if __name__ == "__main__":
- parser = argparse.ArgumentParser(description='Creates a Load/Generation profile for a set'
- 'number of days from a daily profile')
- parser.add_argument('-y', '--year', help='year for timestamp', type=int, default=2019)
- parser.add_argument('-d', '--n-days', help='number of days', type=int, default=365)
- parser.add_argument('-s', '--separator', help='separator of csv colums', type=str, default=";")
- parser.add_argument('-i', '--input-file', help='daily profile file', type=str, required=True)
- parser.add_argument('-o', '--output-file', help='output profile file', type=str)
+ parser = argparse.ArgumentParser(
+ description="Creates a Load/Generation profile for a set"
+ "number of days from a daily profile"
+ )
+ parser.add_argument("-y", "--year", help="year for timestamp", type=int, default=2019)
+ parser.add_argument("-d", "--n-days", help="number of days", type=int, default=365)
+ parser.add_argument("-s", "--separator", help="separator of csv colums", type=str, default=";")
+ parser.add_argument("-i", "--input-file", help="daily profile file", type=str, required=True)
+ parser.add_argument("-o", "--output-file", help="output profile file", type=str)
args = vars(parser.parse_args())
- create_profile_from_daily_profile(n_days=args["n_days"], year=args["year"],
- daily_profile_fn=args["input_file"],
- out_fn=args["output_file"], separator=args["separator"])
+ create_profile_from_daily_profile(
+ n_days=args["n_days"],
+ year=args["year"],
+ daily_profile_fn=args["input_file"],
+ out_fn=args["output_file"],
+ separator=args["separator"],
+ )
diff --git a/tests/area/test_area.py b/tests/area/test_area.py
index b0e861ce3..e69e34fe8 100644
--- a/tests/area/test_area.py
+++ b/tests/area/test_area.py
@@ -15,11 +15,12 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
# pylint: disable=missing-function-docstring, protected-access
from unittest.mock import MagicMock, Mock, call, patch
import pytest
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, TIME_ZONE
from gsy_framework.enums import AvailableMarketTypes, BidOfferMatchAlgoEnum, SpotMarketTypeEnum
from pendulum import duration, today
@@ -43,7 +44,7 @@ def config_fixture():
config.slot_length = duration(minutes=15)
config.tick_length = duration(seconds=15)
config.ticks_per_slot = int(config.slot_length.seconds / config.tick_length.seconds)
- config.start_date = today(tz=constants.TIME_ZONE)
+ config.start_date = today(tz=TIME_ZONE)
config.sim_duration = duration(days=1)
config.grid_fee_type = 1
config.end_date = config.start_date + config.sim_duration
@@ -71,8 +72,9 @@ def teardown_method():
@staticmethod
def test_respective_area_grid_fee_is_applied(config):
config.grid_fee_type = 2
- area = Area(name="Street", children=[Area(name="House")],
- grid_fee_percentage=5, config=config)
+ area = Area(
+ name="Street", children=[Area(name="House")], grid_fee_percentage=5, config=config
+ )
area.parent = Area(name="GRID", config=config)
area.activate()
assert area.spot_market.fee_class.grid_fee_rate == 0.05
@@ -82,46 +84,45 @@ def test_respective_area_grid_fee_is_applied(config):
@staticmethod
def test_delete_past_markets_instead_of_last(config):
constants.RETAIN_PAST_MARKET_STRATEGIES_STATE = False
- area = Area(name="Street", children=[Area(name="House")],
- config=config, grid_fee_percentage=5)
+ area = Area(
+ name="Street", children=[Area(name="House")], config=config, grid_fee_percentage=5
+ )
area.activate()
area._bc = None
area.cycle_markets(False, False, False)
assert len(area.past_markets) == 0
- current_time = today(tz=constants.TIME_ZONE).add(minutes=config.slot_length.minutes)
+ current_time = today(tz=TIME_ZONE).add(minutes=config.slot_length.minutes)
area._markets.rotate_markets(current_time)
assert len(area.past_markets) == 1
- area._markets.create_new_spot_market(
- current_time, AvailableMarketTypes.SPOT, area)
- current_time = today(tz=constants.TIME_ZONE).add(minutes=2 * config.slot_length.minutes)
+ area._markets.create_new_spot_market(current_time, AvailableMarketTypes.SPOT, area)
+ current_time = today(tz=TIME_ZONE).add(minutes=2 * config.slot_length.minutes)
area._markets.rotate_markets(current_time)
assert len(area.past_markets) == 1
- assert (list(area.past_markets)[-1].time_slot ==
- today(tz=constants.TIME_ZONE).add(minutes=config.slot_length.minutes))
+ assert list(area.past_markets)[-1].time_slot == today(tz=TIME_ZONE).add(
+ minutes=config.slot_length.minutes
+ )
@staticmethod
def test_keep_past_markets(config):
constants.RETAIN_PAST_MARKET_STRATEGIES_STATE = True
- area = Area(name="Street", children=[Area(name="House")],
- config=config, grid_fee_percentage=5)
+ area = Area(
+ name="Street", children=[Area(name="House")], config=config, grid_fee_percentage=5
+ )
area.activate()
area._bc = None
area.cycle_markets(False, False, False)
assert len(area.past_markets) == 0
- current_time = today(tz=constants.TIME_ZONE).add(
- minutes=config.slot_length.total_minutes())
+ current_time = today(tz=TIME_ZONE).add(minutes=config.slot_length.total_minutes())
area._markets.rotate_markets(current_time)
assert len(area.past_markets) == 1
- area._markets.create_new_spot_market(
- current_time, AvailableMarketTypes.SPOT, area)
- current_time = today(tz=constants.TIME_ZONE).add(
- minutes=2 * config.slot_length.total_minutes())
+ area._markets.create_new_spot_market(current_time, AvailableMarketTypes.SPOT, area)
+ current_time = today(tz=TIME_ZONE).add(minutes=2 * config.slot_length.total_minutes())
area._markets.rotate_markets(current_time)
assert len(area.past_markets) == 2
@@ -159,22 +160,26 @@ def test_get_state_returns_correct_values():
bat = Area(name="battery", strategy=StorageStrategy())
house = Area(name="House", children=[bat])
- assert house.get_state() == {"current_tick": 0,
- "exported_energy": {},
- "imported_energy": {},
- "rate_stats_market": {}}
- assert bat.get_state() == {"battery_energy_per_slot": 0.0,
- "charge_history": {},
- "charge_history_kWh": {},
- "current_tick": 0,
- "energy_to_buy_dict": {},
- "energy_to_sell_dict": {},
- "offered_buy_kWh": {},
- "offered_history": {},
- "offered_sell_kWh": {},
- "pledged_buy_kWh": {},
- "pledged_sell_kWh": {},
- "used_storage": 0.12}
+ assert house.get_state() == {
+ "current_tick": 0,
+ "exported_energy": {},
+ "imported_energy": {},
+ "rate_stats_market": {},
+ }
+ assert bat.get_state() == {
+ "battery_energy_per_slot": 0.0,
+ "charge_history": {},
+ "charge_history_kWh": {},
+ "current_tick": 0,
+ "energy_to_buy_dict": {},
+ "energy_to_sell_dict": {},
+ "offered_buy_kWh": {},
+ "offered_history": {},
+ "offered_sell_kWh": {},
+ "pledged_buy_kWh": {},
+ "pledged_sell_kWh": {},
+ "used_storage": 0.12,
+ }
@staticmethod
@patch("gsy_e.models.area.Area._consume_commands_from_aggregator", Mock())
@@ -267,9 +272,14 @@ def test_are_dispatches_other_events_only_if_connected_and_enabled():
assert area.dispatcher._should_dispatch_to_strategies(AreaEvent.MARKET_CYCLE)
@staticmethod
- @pytest.mark.parametrize("event_type, area_method", [
- (AreaEvent.MARKET_CYCLE, "cycle_markets"), (AreaEvent.ACTIVATE, "activate"),
- (AreaEvent.TICK, "tick")])
+ @pytest.mark.parametrize(
+ "event_type, area_method",
+ [
+ (AreaEvent.MARKET_CYCLE, "cycle_markets"),
+ (AreaEvent.ACTIVATE, "activate"),
+ (AreaEvent.TICK, "tick"),
+ ],
+ )
def test_event_listener_calls_area_methods_for_area_events(event_type, area_method):
function_mock = MagicMock(name=area_method)
area = Area(name="test_area")
@@ -278,14 +288,18 @@ def test_event_listener_calls_area_methods_for_area_events(event_type, area_meth
assert function_mock.call_count == 1
@staticmethod
- @pytest.mark.parametrize("event_type", [
- (MarketEvent.OFFER,),
- (MarketEvent.BID,),
- (MarketEvent.OFFER_TRADED,),
- (MarketEvent.OFFER_SPLIT,),
- (MarketEvent.BID_TRADED,),
- (MarketEvent.BID_DELETED,),
- (MarketEvent.OFFER_DELETED,)])
+ @pytest.mark.parametrize(
+ "event_type",
+ [
+ (MarketEvent.OFFER,),
+ (MarketEvent.BID,),
+ (MarketEvent.OFFER_TRADED,),
+ (MarketEvent.OFFER_SPLIT,),
+ (MarketEvent.BID_TRADED,),
+ (MarketEvent.BID_DELETED,),
+ (MarketEvent.OFFER_DELETED,),
+ ],
+ )
def test_event_listener_dispatches_to_strategy_if_enabled_connected(event_type, strategy_mock):
area = strategy_mock
area.events.is_enabled = True
@@ -294,14 +308,18 @@ def test_event_listener_dispatches_to_strategy_if_enabled_connected(event_type,
assert area.strategy.event_listener.call_count == 1
@staticmethod
- @pytest.mark.parametrize("event_type", [
- (MarketEvent.OFFER,),
- (MarketEvent.BID,),
- (MarketEvent.OFFER_TRADED,),
- (MarketEvent.OFFER_SPLIT,),
- (MarketEvent.BID_TRADED,),
- (MarketEvent.BID_DELETED,),
- (MarketEvent.OFFER_DELETED,)])
+ @pytest.mark.parametrize(
+ "event_type",
+ [
+ (MarketEvent.OFFER,),
+ (MarketEvent.BID,),
+ (MarketEvent.OFFER_TRADED,),
+ (MarketEvent.OFFER_SPLIT,),
+ (MarketEvent.BID_TRADED,),
+ (MarketEvent.BID_DELETED,),
+ (MarketEvent.OFFER_DELETED,),
+ ],
+ )
def test_event_listener_doesnt_dispatch_to_strategy_if_not_enabled(event_type, strategy_mock):
area = strategy_mock
area.events.is_enabled = False
@@ -310,16 +328,21 @@ def test_event_listener_doesnt_dispatch_to_strategy_if_not_enabled(event_type, s
assert area.strategy.event_listener.call_count == 0
@staticmethod
- @pytest.mark.parametrize("event_type", [
- (MarketEvent.OFFER,),
- (MarketEvent.BID,),
- (MarketEvent.OFFER_TRADED,),
- (MarketEvent.OFFER_SPLIT,),
- (MarketEvent.BID_TRADED,),
- (MarketEvent.BID_DELETED,),
- (MarketEvent.OFFER_DELETED,)])
+ @pytest.mark.parametrize(
+ "event_type",
+ [
+ (MarketEvent.OFFER,),
+ (MarketEvent.BID,),
+ (MarketEvent.OFFER_TRADED,),
+ (MarketEvent.OFFER_SPLIT,),
+ (MarketEvent.BID_TRADED,),
+ (MarketEvent.BID_DELETED,),
+ (MarketEvent.OFFER_DELETED,),
+ ],
+ )
def test_event_listener_doesnt_dispatch_to_strategy_if_not_connected(
- event_type, strategy_mock):
+ event_type, strategy_mock
+ ):
area = strategy_mock
area.events.is_enabled = True
area.events.is_connected = False
@@ -337,14 +360,24 @@ def test_event_on_disabled_area_triggered_for_market_cycle_on_disabled_area(stra
@staticmethod
def test_duplicate_area_in_the_same_parent_append():
- area = Area(name="Street", children=[Area(name="House")], )
+ area = Area(
+ name="Street",
+ children=[Area(name="House")],
+ )
with pytest.raises(Exception) as exception:
- area.children.append(Area(name="House", children=[Area(name="House")], ))
+ area.children.append(
+ Area(
+ name="House",
+ children=[Area(name="House")],
+ )
+ )
assert exception == "Area name should be unique inside the same Parent Area"
@staticmethod
def test_duplicate_area_in_the_same_parent_change_name():
- child = Area(name="Street", )
+ child = Area(
+ name="Street",
+ )
with pytest.raises(Exception) as exception:
child.name = "Street 2"
assert exception == "Area name should be unique inside the same Parent Area"
@@ -355,7 +388,10 @@ class TestFunctions:
@staticmethod
def test_check_area_name_exists_in_parent_area():
- area = Area(name="Street", children=[Area(name="House")], )
+ area = Area(
+ name="Street",
+ children=[Area(name="House")],
+ )
assert check_area_name_exists_in_parent_area(area, "House") is True
assert check_area_name_exists_in_parent_area(area, "House 2") is False
diff --git a/tests/area/test_coefficient_area.py b/tests/area/test_coefficient_area.py
index 8de718d0e..5a774f2c2 100644
--- a/tests/area/test_coefficient_area.py
+++ b/tests/area/test_coefficient_area.py
@@ -21,7 +21,7 @@
from unittest.mock import MagicMock
import pytest
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, FLOATING_POINT_TOLERANCE, TIME_ZONE
from gsy_framework.enums import (
SpotMarketTypeEnum,
CoefficientAlgorithm,
@@ -31,7 +31,6 @@
from pendulum import duration, today
from pendulum import now
-from gsy_e import constants
from gsy_e.models.area import CoefficientArea, CoefficientAreaException
from gsy_e.models.area.scm_manager import (
SCMManager,
@@ -56,7 +55,7 @@ def config_fixture():
config.slot_length = duration(minutes=15)
config.tick_length = duration(seconds=15)
config.ticks_per_slot = int(config.slot_length.seconds / config.tick_length.seconds)
- config.start_date = today(tz=constants.TIME_ZONE)
+ config.start_date = today(tz=TIME_ZONE)
config.sim_duration = duration(days=1)
return config
@@ -309,9 +308,7 @@ def test_trigger_energy_trades(_create_2_house_grid, intracommunity_base_rate):
assert isclose(scm._bills[house2.uuid].gsy_energy_bill, -0.02)
assert isclose(scm._bills[house2.uuid].export_grid_fees, 0.0)
- assert isclose(
- scm._bills[house2.uuid].savings, 0.0, abs_tol=constants.FLOATING_POINT_TOLERANCE
- )
+ assert isclose(scm._bills[house2.uuid].savings, 0.0, abs_tol=FLOATING_POINT_TOLERANCE)
assert isclose(scm._bills[house2.uuid].savings_percent, 0.0)
assert len(scm._home_data[house1.uuid].trades) == 2
trades = scm._home_data[house1.uuid].trades
diff --git a/tests/market/test_market.py b/tests/market/test_market.py
index 44eef9661..2e6081870 100644
--- a/tests/market/test_market.py
+++ b/tests/market/test_market.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 string
from copy import deepcopy
from unittest.mock import MagicMock
@@ -22,7 +23,7 @@
import pytest
from deepdiff import DeepDiff
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, TIME_ZONE
from gsy_framework.data_classes import Bid, Offer, TraderDetails
from gsy_framework.utils import datetime_to_string_incl_seconds
from hypothesis import strategies as st
@@ -30,13 +31,17 @@
from hypothesis.stateful import Bundle, RuleBasedStateMachine, precondition, rule
from pendulum import now
-from gsy_e.constants import TIME_ZONE
from gsy_e.events.event_structures import MarketEvent
from gsy_e.gsy_e_core.blockchain_interface import NonBlockchainInterface
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
-from gsy_e.gsy_e_core.exceptions import (DeviceNotInRegistryError, InvalidBalancingTradeException,
- NegativeEnergyOrderException, InvalidTrade,
- MarketReadOnlyException, OfferNotFoundException)
+from gsy_e.gsy_e_core.exceptions import (
+ DeviceNotInRegistryError,
+ InvalidBalancingTradeException,
+ NegativeEnergyOrderException,
+ InvalidTrade,
+ MarketReadOnlyException,
+ OfferNotFoundException,
+)
from gsy_e.gsy_e_core.util import add_or_create_key, subtract_or_create_key
from gsy_e.models.market.balancing import BalancingMarket
from gsy_e.models.market.one_sided import OneSidedMarket
@@ -74,11 +79,17 @@ def test_device_registry(market=BalancingMarket()):
market.balancing_offer(10, 10, TraderDetails("noone", ""))
-@pytest.mark.parametrize("market, offer", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "balancing_offer"),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
-])
+@pytest.mark.parametrize(
+ "market, offer",
+ [
+ (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "balancing_offer",
+ ),
+ (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
+ ],
+)
def test_market_offer(market, offer):
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True
e_offer = getattr(market, offer)(10, 20, TraderDetails("someone", "", "someone", ""))
@@ -91,10 +102,13 @@ def test_market_offer(market, offer):
assert e_offer.time_slot == market.time_slot
-@pytest.mark.parametrize("market", [
- TwoSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now())
-])
+@pytest.mark.parametrize(
+ "market",
+ [
+ TwoSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ ],
+)
def test_market_bid(market):
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True
bid = market.bid(10, 20, buyer_details)
@@ -112,49 +126,70 @@ def test_market_offer_invalid(market: OneSidedMarket):
market.offer(10, -1, buyer_details)
-@pytest.mark.parametrize("market, offer", [
- (TwoSidedMarket(), "offer"),
- (BalancingMarket(), "balancing_offer"),
- (SettlementMarket(), "offer")
-])
+@pytest.mark.parametrize(
+ "market, offer",
+ [
+ (TwoSidedMarket(), "offer"),
+ (BalancingMarket(), "balancing_offer"),
+ (SettlementMarket(), "offer"),
+ ],
+)
def test_market_offer_readonly(market, offer):
market.readonly = True
with pytest.raises(MarketReadOnlyException):
getattr(market, offer)(10, 10, seller_details)
-@pytest.mark.parametrize("market",
- [OneSidedMarket(bc=MagicMock()),
- BalancingMarket(bc=MagicMock()),
- SettlementMarket(bc=MagicMock())
- ])
+@pytest.mark.parametrize(
+ "market",
+ [
+ OneSidedMarket(bc=MagicMock()),
+ BalancingMarket(bc=MagicMock()),
+ SettlementMarket(bc=MagicMock()),
+ ],
+)
def test_market_offer_delete_missing(market):
with pytest.raises(OfferNotFoundException):
market.delete_offer("no such offer")
-@pytest.mark.parametrize("market",
- [OneSidedMarket(bc=MagicMock()),
- BalancingMarket(bc=MagicMock()),
- SettlementMarket(bc=MagicMock())])
+@pytest.mark.parametrize(
+ "market",
+ [
+ OneSidedMarket(bc=MagicMock()),
+ BalancingMarket(bc=MagicMock()),
+ SettlementMarket(bc=MagicMock()),
+ ],
+)
def test_market_offer_delete_readonly(market):
market.readonly = True
with pytest.raises(MarketReadOnlyException):
market.delete_offer("no such offer")
-@pytest.mark.parametrize("market, offer, accept_offer", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now(tz=TIME_ZONE)),
- "offer", "accept_offer"),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now(tz=TIME_ZONE)),
- "balancing_offer", "accept_offer"),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now(tz=TIME_ZONE)),
- "offer", "accept_offer")
-])
+@pytest.mark.parametrize(
+ "market, offer, accept_offer",
+ [
+ (
+ OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now(tz=TIME_ZONE)),
+ "offer",
+ "accept_offer",
+ ),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now(tz=TIME_ZONE)),
+ "balancing_offer",
+ "accept_offer",
+ ),
+ (
+ SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now(tz=TIME_ZONE)),
+ "offer",
+ "accept_offer",
+ ),
+ ],
+)
def test_market_trade(market, offer, accept_offer):
e_offer = getattr(market, offer)(20, 10, seller_details)
- trade = getattr(market, accept_offer)(offer_or_id=e_offer, buyer=buyer_details,
- energy=10)
+ trade = getattr(market, accept_offer)(offer_or_id=e_offer, buyer=buyer_details, energy=10)
assert trade
assert trade == market.trades[0]
assert trade.id
@@ -169,44 +204,56 @@ def test_orders_per_slot(market):
"""Test whether the orders_per_slot method returns order in format format."""
creation_time = now()
market.bids = {"bid1": Bid("bid1", creation_time, 10, 10, TraderDetails("buyer", ""))}
- market.offers = {"offer1": Offer(
- "offer1", creation_time, 10, 10, TraderDetails("seller", ""))}
- order_dict_diff = DeepDiff(market.orders_per_slot(), {
- market.time_slot_str: {"bids": [{"buyer": {
- "name": "buyer",
- "uuid": "",
- "origin": None,
- "origin_uuid": None,
- },
- "energy": 10,
- "price": 10,
- "energy_rate": 1.0,
- "id": "bid1",
- "original_price": 10,
- "time_slot": "",
- "creation_time": datetime_to_string_incl_seconds(
- creation_time),
- "type": "Bid"}],
- "offers": [{"energy": 10,
- "price": 10,
- "energy_rate": 1.0,
- "id": "offer1",
- "original_price": 10,
- "seller": {
- "name": "seller",
- "uuid": "",
- "origin": None,
- "origin_uuid": None,
- },
- "time_slot": "",
- "creation_time": datetime_to_string_incl_seconds(
- creation_time),
- "type": "Offer"}]}})
+ market.offers = {"offer1": Offer("offer1", creation_time, 10, 10, TraderDetails("seller", ""))}
+ order_dict_diff = DeepDiff(
+ market.orders_per_slot(),
+ {
+ market.time_slot_str: {
+ "bids": [
+ {
+ "buyer": {
+ "name": "buyer",
+ "uuid": "",
+ "origin": None,
+ "origin_uuid": None,
+ },
+ "energy": 10,
+ "price": 10,
+ "energy_rate": 1.0,
+ "id": "bid1",
+ "original_price": 10,
+ "time_slot": "",
+ "creation_time": datetime_to_string_incl_seconds(creation_time),
+ "type": "Bid",
+ }
+ ],
+ "offers": [
+ {
+ "energy": 10,
+ "price": 10,
+ "energy_rate": 1.0,
+ "id": "offer1",
+ "original_price": 10,
+ "seller": {
+ "name": "seller",
+ "uuid": "",
+ "origin": None,
+ "origin_uuid": None,
+ },
+ "time_slot": "",
+ "creation_time": datetime_to_string_incl_seconds(creation_time),
+ "type": "Offer",
+ }
+ ],
+ }
+ },
+ )
assert len(order_dict_diff) == 0
-def test_balancing_market_negative_offer_trade(market=BalancingMarket(
- bc=NonBlockchainInterface(str(uuid4())))): # NOQA
+def test_balancing_market_negative_offer_trade(
+ market=BalancingMarket(bc=NonBlockchainInterface(str(uuid4()))),
+): # NOQA
offer = market.balancing_offer(20, -10, seller_details)
trade = market.accept_offer(offer, buyer_details, energy=-10)
assert trade
@@ -219,28 +266,40 @@ def test_balancing_market_negative_offer_trade(market=BalancingMarket(
assert trade.buyer.name == buyer_details.name
-@pytest.mark.parametrize("market, offer, accept_offer", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "offer", "accept_offer"),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "balancing_offer", "accept_offer"),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "offer", "accept_offer")
-])
+@pytest.mark.parametrize(
+ "market, offer, accept_offer",
+ [
+ (
+ OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ ),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "balancing_offer",
+ "accept_offer",
+ ),
+ (
+ SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ ),
+ ],
+)
def test_market_trade_by_id(market, offer, accept_offer):
e_offer = getattr(market, offer)(20, 10, seller_details)
trade = getattr(market, accept_offer)(offer_or_id=e_offer.id, buyer=buyer_details, energy=10)
assert trade
-@pytest.mark.parametrize("market, offer, accept_offer", [
- (OneSidedMarket(bc=MagicMock(), time_slot=now()),
- "offer", "accept_offer"),
- (BalancingMarket(bc=MagicMock(), time_slot=now()),
- "balancing_offer", "accept_offer"),
- (SettlementMarket(bc=MagicMock(), time_slot=now()),
- "offer", "accept_offer")
-])
+@pytest.mark.parametrize(
+ "market, offer, accept_offer",
+ [
+ (OneSidedMarket(bc=MagicMock(), time_slot=now()), "offer", "accept_offer"),
+ (BalancingMarket(bc=MagicMock(), time_slot=now()), "balancing_offer", "accept_offer"),
+ (SettlementMarket(bc=MagicMock(), time_slot=now()), "offer", "accept_offer"),
+ ],
+)
def test_market_trade_readonly(market, offer, accept_offer):
e_offer = getattr(market, offer)(20, 10, seller_details)
market.readonly = True
@@ -248,14 +307,26 @@ def test_market_trade_readonly(market, offer, accept_offer):
getattr(market, accept_offer)(e_offer, buyer_details)
-@pytest.mark.parametrize("market, offer, accept_offer", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "offer", "accept_offer"),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "balancing_offer", "accept_offer"),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "offer", "accept_offer")
-])
+@pytest.mark.parametrize(
+ "market, offer, accept_offer",
+ [
+ (
+ OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ ),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "balancing_offer",
+ "accept_offer",
+ ),
+ (
+ SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ ),
+ ],
+)
def test_market_trade_not_found(market, offer, accept_offer):
e_offer = getattr(market, offer)(20, 10, seller_details)
@@ -264,14 +335,26 @@ def test_market_trade_not_found(market, offer, accept_offer):
getattr(market, accept_offer)(offer_or_id=e_offer, buyer=buyer_details, energy=10)
-@pytest.mark.parametrize("market, offer, accept_offer", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "offer", "accept_offer"),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "balancing_offer", "accept_offer"),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "offer", "accept_offer")
-])
+@pytest.mark.parametrize(
+ "market, offer, accept_offer",
+ [
+ (
+ OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ ),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "balancing_offer",
+ "accept_offer",
+ ),
+ (
+ SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ ),
+ ],
+)
def test_market_trade_partial(market, offer, accept_offer):
e_offer = getattr(market, offer)(20, 20, seller_details)
@@ -294,31 +377,62 @@ def test_market_trade_partial(market, offer, accept_offer):
assert new_offer.id != e_offer.id
-@pytest.mark.parametrize("market, offer, accept_offer, energy, exception", [
- (OneSidedMarket(bc=MagicMock(), time_slot=now()),
- "offer", "accept_offer", 0, InvalidTrade),
- (OneSidedMarket(bc=MagicMock(), time_slot=now()),
- "offer", "accept_offer", 21, InvalidTrade),
- (BalancingMarket(bc=MagicMock(), time_slot=now()),
- "balancing_offer", "accept_offer", 0,
- InvalidBalancingTradeException),
- (BalancingMarket(bc=MagicMock(), time_slot=now()),
- "balancing_offer", "accept_offer", 21,
- InvalidBalancingTradeException),
- (SettlementMarket(bc=MagicMock(), time_slot=now()),
- "offer", "accept_offer", 0, InvalidTrade),
- (SettlementMarket(bc=MagicMock(), time_slot=now()),
- "offer", "accept_offer", 21, InvalidTrade),
-])
+@pytest.mark.parametrize(
+ "market, offer, accept_offer, energy, exception",
+ [
+ (
+ OneSidedMarket(bc=MagicMock(), time_slot=now()),
+ "offer",
+ "accept_offer",
+ 0,
+ InvalidTrade,
+ ),
+ (
+ OneSidedMarket(bc=MagicMock(), time_slot=now()),
+ "offer",
+ "accept_offer",
+ 21,
+ InvalidTrade,
+ ),
+ (
+ BalancingMarket(bc=MagicMock(), time_slot=now()),
+ "balancing_offer",
+ "accept_offer",
+ 0,
+ InvalidBalancingTradeException,
+ ),
+ (
+ BalancingMarket(bc=MagicMock(), time_slot=now()),
+ "balancing_offer",
+ "accept_offer",
+ 21,
+ InvalidBalancingTradeException,
+ ),
+ (
+ SettlementMarket(bc=MagicMock(), time_slot=now()),
+ "offer",
+ "accept_offer",
+ 0,
+ InvalidTrade,
+ ),
+ (
+ SettlementMarket(bc=MagicMock(), time_slot=now()),
+ "offer",
+ "accept_offer",
+ 21,
+ InvalidTrade,
+ ),
+ ],
+)
def test_market_trade_partial_invalid(market, offer, accept_offer, energy, exception):
e_offer = getattr(market, offer)(20, 20, seller_details)
with pytest.raises(exception):
- getattr(market, accept_offer)(
- offer_or_id=e_offer, buyer=buyer_details.name, energy=energy)
+ getattr(market, accept_offer)(offer_or_id=e_offer, buyer=buyer_details.name, energy=energy)
-def test_market_acct_simple(market=OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())),
- time_slot=now())):
+def test_market_acct_simple(
+ market=OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now())
+):
offer = market.offer(20, 10, TraderDetails("A", "", "A", ""))
market.accept_offer(offer, TraderDetails("B", "", "B", ""))
@@ -330,8 +444,9 @@ def test_market_acct_simple(market=OneSidedMarket(bc=NonBlockchainInterface(str(
assert market.sold_energy("B") == 0
-def test_market_acct_multiple(market=OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())),
- time_slot=now())):
+def test_market_acct_multiple(
+ market=OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now())
+):
offer1 = market.offer(10, 20, TraderDetails("A", "", "A", ""))
offer2 = market.offer(10, 10, TraderDetails("A", "", "A", ""))
market.accept_offer(offer1, TraderDetails("B", "", "B", ""))
@@ -346,11 +461,17 @@ def test_market_acct_multiple(market=OneSidedMarket(bc=NonBlockchainInterface(st
assert market.bought_energy("C") == offer2.energy == 10
-@pytest.mark.parametrize("market, offer", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "balancing_offer"),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer")
-])
+@pytest.mark.parametrize(
+ "market, offer",
+ [
+ (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "balancing_offer",
+ ),
+ (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
+ ],
+)
def test_market_sorted_offers(market, offer):
getattr(market, offer)(5, 1, seller_details)
getattr(market, offer)(3, 1, seller_details)
@@ -361,11 +482,17 @@ def test_market_sorted_offers(market, offer):
assert [o.price for o in market.sorted_offers] == [1, 2, 3, 4, 5]
-@pytest.mark.parametrize("market, offer", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "balancing_offer"),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer")
-])
+@pytest.mark.parametrize(
+ "market, offer",
+ [
+ (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "balancing_offer",
+ ),
+ (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer"),
+ ],
+)
def test_market_most_affordable_offers(market, offer):
getattr(market, offer)(5, 1, seller_details)
getattr(market, offer)(3, 1, seller_details)
@@ -379,22 +506,24 @@ def test_market_most_affordable_offers(market, offer):
assert {o.price for o in market.most_affordable_offers} == {1, 10, 20, 20000}
-@pytest.mark.parametrize("market, offer", [
- (OneSidedMarket, "offer"),
- (BalancingMarket, "balancing_offer"),
- (SettlementMarket, "offer")
-])
+@pytest.mark.parametrize(
+ "market, offer",
+ [(OneSidedMarket, "offer"), (BalancingMarket, "balancing_offer"), (SettlementMarket, "offer")],
+)
def test_market_listeners_init(market, offer, called):
markt = market(bc=MagicMock(), time_slot=now(), notification_listener=called)
getattr(markt, offer)(10, 20, seller_details)
assert len(called.calls) == 1
-@pytest.mark.parametrize("market, offer, add_listener", [
- (OneSidedMarket(bc=MagicMock(), time_slot=now()), "offer", "add_listener"),
- (BalancingMarket(bc=MagicMock(), time_slot=now()), "balancing_offer", "add_listener"),
- (SettlementMarket(bc=MagicMock(), time_slot=now()), "offer", "add_listener")
-])
+@pytest.mark.parametrize(
+ "market, offer, add_listener",
+ [
+ (OneSidedMarket(bc=MagicMock(), time_slot=now()), "offer", "add_listener"),
+ (BalancingMarket(bc=MagicMock(), time_slot=now()), "balancing_offer", "add_listener"),
+ (SettlementMarket(bc=MagicMock(), time_slot=now()), "offer", "add_listener"),
+ ],
+)
def test_market_listeners_add(market, offer, add_listener, called):
getattr(market, add_listener)(called)
getattr(market, offer)(10, 20, seller_details)
@@ -402,14 +531,29 @@ def test_market_listeners_add(market, offer, add_listener, called):
assert len(called.calls) == 1
-@pytest.mark.parametrize("market, offer, add_listener, event", [
- (OneSidedMarket(bc=MagicMock(), time_slot=now()),
- "offer", "add_listener", MarketEvent.OFFER),
- (BalancingMarket(bc=MagicMock(), time_slot=now()),
- "balancing_offer", "add_listener", MarketEvent.BALANCING_OFFER),
- (SettlementMarket(bc=MagicMock(), time_slot=now()),
- "offer", "add_listener", MarketEvent.OFFER),
-])
+@pytest.mark.parametrize(
+ "market, offer, add_listener, event",
+ [
+ (
+ OneSidedMarket(bc=MagicMock(), time_slot=now()),
+ "offer",
+ "add_listener",
+ MarketEvent.OFFER,
+ ),
+ (
+ BalancingMarket(bc=MagicMock(), time_slot=now()),
+ "balancing_offer",
+ "add_listener",
+ MarketEvent.BALANCING_OFFER,
+ ),
+ (
+ SettlementMarket(bc=MagicMock(), time_slot=now()),
+ "offer",
+ "add_listener",
+ MarketEvent.OFFER,
+ ),
+ ],
+)
def test_market_listeners_offer(market, offer, add_listener, event, called):
getattr(market, add_listener)(called)
e_offer = getattr(market, offer)(10, 20, seller_details)
@@ -418,22 +562,37 @@ def test_market_listeners_offer(market, offer, add_listener, event, called):
assert called.calls[0][1] == {"offer": repr(e_offer), "market_id": repr(market.id)}
-@pytest.mark.parametrize("market, offer, accept_offer, add_listener, event", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "offer", "accept_offer", "add_listener",
- MarketEvent.OFFER_SPLIT),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "balancing_offer", "accept_offer", "add_listener",
- MarketEvent.BALANCING_OFFER_SPLIT),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
- "offer", "accept_offer", "add_listener",
- MarketEvent.OFFER_SPLIT),
-])
+@pytest.mark.parametrize(
+ "market, offer, accept_offer, add_listener, event",
+ [
+ (
+ OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ "add_listener",
+ MarketEvent.OFFER_SPLIT,
+ ),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "balancing_offer",
+ "accept_offer",
+ "add_listener",
+ MarketEvent.BALANCING_OFFER_SPLIT,
+ ),
+ (
+ SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ "add_listener",
+ MarketEvent.OFFER_SPLIT,
+ ),
+ ],
+)
def test_market_listeners_offer_split(market, offer, accept_offer, add_listener, event, called):
# pylint: disable=too-many-arguments
getattr(market, add_listener)(called)
- e_offer = getattr(market, offer)(10., 20, seller_details)
- getattr(market, accept_offer)(e_offer, buyer_details, energy=3.)
+ e_offer = getattr(market, offer)(10.0, 20, seller_details)
+ getattr(market, accept_offer)(e_offer, buyer_details, energy=3.0)
assert len(called.calls) == 3
assert called.calls[1][0] == (repr(event),)
call_kwargs = called.calls[1][1]
@@ -444,21 +603,36 @@ def test_market_listeners_offer_split(market, offer, accept_offer, add_listener,
assert call_kwargs == {
"original_offer": repr(e_offer),
"accepted_offer": repr(a_offer),
- "residual_offer": repr(list(market.offers.values())[0])
+ "residual_offer": repr(list(market.offers.values())[0]),
}
-@pytest.mark.parametrize("market, offer, delete_offer, add_listener, event", [
- (OneSidedMarket(bc=MagicMock(), time_slot=now()),
- "offer", "delete_offer",
- "add_listener", MarketEvent.OFFER_DELETED),
- (BalancingMarket(bc=MagicMock(), time_slot=now()),
- "balancing_offer", "delete_balancing_offer",
- "add_listener", MarketEvent.BALANCING_OFFER_DELETED),
- (SettlementMarket(bc=MagicMock(), time_slot=now()),
- "offer", "delete_offer",
- "add_listener", MarketEvent.OFFER_DELETED),
-])
+@pytest.mark.parametrize(
+ "market, offer, delete_offer, add_listener, event",
+ [
+ (
+ OneSidedMarket(bc=MagicMock(), time_slot=now()),
+ "offer",
+ "delete_offer",
+ "add_listener",
+ MarketEvent.OFFER_DELETED,
+ ),
+ (
+ BalancingMarket(bc=MagicMock(), time_slot=now()),
+ "balancing_offer",
+ "delete_balancing_offer",
+ "add_listener",
+ MarketEvent.BALANCING_OFFER_DELETED,
+ ),
+ (
+ SettlementMarket(bc=MagicMock(), time_slot=now()),
+ "offer",
+ "delete_offer",
+ "add_listener",
+ MarketEvent.OFFER_DELETED,
+ ),
+ ],
+)
def test_market_listeners_offer_deleted(market, offer, delete_offer, add_listener, event, called):
# pylint: disable=too-many-arguments
getattr(market, add_listener)(called)
@@ -470,14 +644,7 @@ def test_market_listeners_offer_deleted(market, offer, delete_offer, add_listene
assert called.calls[1][1] == {"offer": repr(e_offer), "market_id": repr(market.id)}
-@pytest.mark.parametrize(
- ("last_offer_size", "traded_energy"),
- (
- (20, 10),
- (30, 0),
- (40, -10)
- )
-)
+@pytest.mark.parametrize(("last_offer_size", "traded_energy"), ((20, 10), (30, 0), (40, -10)))
def test_market_issuance_acct_reverse(last_offer_size, traded_energy):
market = OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now())
offer1 = market.offer(10, 20, TraderDetails("A", "", "A", ""))
@@ -490,25 +657,40 @@ def test_market_issuance_acct_reverse(last_offer_size, traded_energy):
assert market.traded_energy["A"] == traded_energy
-@pytest.mark.parametrize("market, offer, accept_offer", [
- (OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer",
- "accept_offer"),
- (BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "balancing_offer",
- "accept_offer"),
- (SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()), "offer",
- "accept_offer")
-])
+@pytest.mark.parametrize(
+ "market, offer, accept_offer",
+ [
+ (
+ OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ ),
+ (
+ BalancingMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "balancing_offer",
+ "accept_offer",
+ ),
+ (
+ SettlementMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now()),
+ "offer",
+ "accept_offer",
+ ),
+ ],
+)
def test_market_accept_offer_yields_partial_trade(market, offer, accept_offer):
"""Test market accept offer returns partial trade."""
e_offer = getattr(market, offer)(2.0, 4, seller_details)
trade = getattr(market, accept_offer)(e_offer, buyer_details, energy=1)
- assert (trade.match_details["offer"].id == e_offer.id
- and trade.traded_energy == 1
- and trade.residual.energy == 3)
+ assert (
+ trade.match_details["offer"].id == e_offer.id
+ and trade.traded_energy == 1
+ and trade.residual.energy == 3
+ )
class MarketStateMachine(RuleBasedStateMachine):
"""State machine for Market"""
+
# pylint: disable=missing-function-docstring
offers = Bundle("Offers")
actors = Bundle("Actors")
@@ -517,14 +699,20 @@ def __init__(self):
self.market = OneSidedMarket(bc=NonBlockchainInterface(str(uuid4())), time_slot=now())
super().__init__()
- @rule(target=actors, actor=st.text(min_size=1, max_size=3,
- alphabet=string.ascii_letters + string.digits))
+ @rule(
+ target=actors,
+ actor=st.text(min_size=1, max_size=3, alphabet=string.ascii_letters + string.digits),
+ )
def new_actor(self, actor):
# pylint: disable=no-self-use
return actor
- @rule(target=offers, seller=actors, energy=st.integers(min_value=1),
- price=st.integers(min_value=0))
+ @rule(
+ target=offers,
+ seller=actors,
+ energy=st.integers(min_value=1),
+ price=st.integers(min_value=0),
+ )
def offer(self, seller, energy, price):
return self.market.offer(price, energy, TraderDetails(seller, ""))
diff --git a/tests/strategies/energy_parameters/test_heat_pump.py b/tests/strategies/energy_parameters/test_heat_pump.py
index 73f587ddf..2d0169678 100644
--- a/tests/strategies/energy_parameters/test_heat_pump.py
+++ b/tests/strategies/energy_parameters/test_heat_pump.py
@@ -11,6 +11,7 @@
HeatPumpEnergyParameters,
TankParameters,
)
+from gsy_e.models.strategy.energy_parameters.heatpump.cop_models import COPModelType
CURRENT_MARKET_SLOT = today(tz=TIME_ZONE)
@@ -49,6 +50,41 @@ def fixture_heatpump_energy_params() -> HeatPumpEnergyParameters:
GlobalConfig.slot_length = original_slot_length
+@pytest.fixture(name="energy_params_heat_profile")
+def fixture_heatpump_energy_params_heat_profile() -> HeatPumpEnergyParameters:
+ original_start_date = GlobalConfig.start_date
+ original_sim_duration = GlobalConfig.sim_duration
+ original_slot_length = GlobalConfig.slot_length
+ GlobalConfig.start_date = CURRENT_MARKET_SLOT
+ GlobalConfig.sim_duration = duration(days=1)
+ GlobalConfig.slot_length = duration(minutes=60)
+
+ source_temp_profile = {
+ timestamp: 12 for timestamp in generate_market_slot_list(CURRENT_MARKET_SLOT)
+ }
+ heat_demand_profile = {
+ timestamp: 9000000 for timestamp in generate_market_slot_list(CURRENT_MARKET_SLOT)
+ }
+ energy_params = HeatPumpEnergyParameters(
+ maximum_power_rating_kW=30,
+ tank_parameters=[
+ TankParameters(
+ min_temp_C=10,
+ max_temp_C=60,
+ initial_temp_C=45,
+ tank_volume_L=500,
+ )
+ ],
+ source_temp_C_profile=source_temp_profile,
+ heat_demand_Q_profile=heat_demand_profile,
+ cop_model_type=COPModelType.HOVAL_ULTRASOURCE_B_COMFORT_C11,
+ )
+ yield energy_params
+ GlobalConfig.start_date = original_start_date
+ GlobalConfig.sim_duration = original_sim_duration
+ GlobalConfig.slot_length = original_slot_length
+
+
class TestHeatPumpEnergyParameters:
@staticmethod
@@ -142,3 +178,13 @@ def test_if_profiles_are_rotated_on_market_cycle(energy_params):
energy_params._consumption_kWh.read_or_rotate_profiles.assert_called_once()
energy_params._source_temp_C.read_or_rotate_profiles.assert_called_once()
energy_params._populate_state.assert_called_once()
+
+ @staticmethod
+ def test_cop_model_is_correctly_selected(energy_params_heat_profile):
+ energy_params_heat_profile._source_temp_C.read_or_rotate_profiles = Mock()
+ energy_params_heat_profile.event_market_cycle(CURRENT_MARKET_SLOT)
+ assert isclose(
+ energy_params_heat_profile._state.heatpump.get_cop(CURRENT_MARKET_SLOT),
+ 4.8941,
+ abs_tol=0.001,
+ )
diff --git a/tests/strategies/energy_parameters/test_tank_energy_parameters.py b/tests/strategies/energy_parameters/test_tank_energy_parameters.py
index 43b1a3aef..c970ea8a7 100644
--- a/tests/strategies/energy_parameters/test_tank_energy_parameters.py
+++ b/tests/strategies/energy_parameters/test_tank_energy_parameters.py
@@ -28,59 +28,59 @@ def setup_method(self):
self._datetime = datetime(2023, 1, 1, 0, 0)
def test_increase_tanks_temp_from_heat_energy(self):
- self._tanks.increase_tanks_temp_from_heat_energy(1, self._datetime)
+ self._tanks.increase_tanks_temp_from_heat_energy(5000, self._datetime)
energy_params = self._tanks._tanks_energy_parameters
assert isclose(
- energy_params[0]._state._temp_increase_K[self._datetime], 0.5747, rel_tol=0.0001
+ energy_params[0]._state._temp_increase_K[self._datetime], 0.7982, rel_tol=0.0001
)
assert isclose(
- energy_params[1]._state._temp_increase_K[self._datetime], 0.3592, rel_tol=0.0001
+ energy_params[1]._state._temp_increase_K[self._datetime], 0.49888, rel_tol=0.0001
)
assert isclose(
- energy_params[2]._state._temp_increase_K[self._datetime], 0.28735, rel_tol=0.0001
+ energy_params[2]._state._temp_increase_K[self._datetime], 0.399106, rel_tol=0.0001
)
def test_decrease_tanks_temp_from_heat_energy(self):
- self._tanks.decrease_tanks_temp_from_heat_energy(1, self._datetime)
+ self._tanks.decrease_tanks_temp_from_heat_energy(5000, self._datetime)
energy_params = self._tanks._tanks_energy_parameters
assert isclose(
- energy_params[0]._state._temp_decrease_K[self._datetime], 0.5747, rel_tol=0.0001
+ energy_params[0]._state._temp_decrease_K[self._datetime], 0.7982, rel_tol=0.0001
)
assert isclose(
- energy_params[1]._state._temp_decrease_K[self._datetime], 0.3592, rel_tol=0.0001
+ energy_params[1]._state._temp_decrease_K[self._datetime], 0.49888, rel_tol=0.0001
)
assert isclose(
- energy_params[2]._state._temp_decrease_K[self._datetime], 0.28735, rel_tol=0.0001
+ energy_params[2]._state._temp_decrease_K[self._datetime], 0.399106, rel_tol=0.0001
)
def test_update_tanks_temperature(self):
- self._tanks.increase_tanks_temp_from_heat_energy(2, self._datetime)
- self._tanks.decrease_tanks_temp_from_heat_energy(1, self._datetime)
+ self._tanks.increase_tanks_temp_from_heat_energy(2000, self._datetime)
+ self._tanks.decrease_tanks_temp_from_heat_energy(1000, self._datetime)
self._tanks.update_tanks_temperature(self._datetime)
energy_params = self._tanks._tanks_energy_parameters
assert isclose(
- energy_params[0]._state._temp_decrease_K[self._datetime], 0.5747, rel_tol=0.0001
+ energy_params[0]._state._temp_decrease_K[self._datetime], 0.159642, rel_tol=0.0001
)
assert isclose(
- energy_params[1]._state._temp_decrease_K[self._datetime], 0.3592, rel_tol=0.0001
+ energy_params[1]._state._temp_decrease_K[self._datetime], 0.09977, rel_tol=0.0001
)
assert isclose(
- energy_params[2]._state._temp_decrease_K[self._datetime], 0.28735, rel_tol=0.0001
+ energy_params[2]._state._temp_decrease_K[self._datetime], 0.079821, rel_tol=0.0001
)
@pytest.mark.parametrize("cop", (1, 4, 10, 0.5, 12))
def test_get_max_energy_consumption(self, cop):
- energy_decrease = 1
- self._tanks.decrease_tanks_temp_from_heat_energy(energy_decrease, self._datetime)
+ energy_decrease_kJ = 1000
+ self._tanks.decrease_tanks_temp_from_heat_energy(energy_decrease_kJ, self._datetime)
max_energy_consumption = self._tanks.get_max_energy_consumption(cop, self._datetime)
- assert isclose(max_energy_consumption, (33.06 + energy_decrease) / cop, rel_tol=0.0001)
+ assert isclose(max_energy_consumption, 33.3377 / cop, rel_tol=0.0001)
@pytest.mark.parametrize("cop", (1, 4, 10, 0.5, 12))
def test_get_min_energy_consumption(self, cop):
- self._tanks.decrease_tanks_temp_from_heat_energy(2, self._datetime)
+ self._tanks.decrease_tanks_temp_from_heat_energy(2000, self._datetime)
min_energy_consumption = self._tanks.get_min_energy_consumption(cop, self._datetime)
- assert isclose(min_energy_consumption, 2.0 / cop, rel_tol=0.0001)
+ assert isclose(min_energy_consumption, 0.55555 / cop, rel_tol=0.0001)
def test_get_average_tank_temperature(self):
self._tanks._tanks_energy_parameters[0]._state._storage_temp_C[self._datetime] = 30
@@ -89,9 +89,9 @@ def test_get_average_tank_temperature(self):
assert self._tanks.get_average_tank_temperature(self._datetime) == 35
def test_get_unmatched_demand_kWh(self):
- self._tanks.decrease_tanks_temp_from_heat_energy(10, self._datetime)
- self._tanks.increase_tanks_temp_from_heat_energy(1, self._datetime)
- assert self._tanks.get_unmatched_demand_kWh(self._datetime) == 9
+ self._tanks.decrease_tanks_temp_from_heat_energy(10000, self._datetime)
+ self._tanks.increase_tanks_temp_from_heat_energy(1000, self._datetime)
+ assert self._tanks.get_unmatched_demand_kWh(self._datetime) == 2.5
def test_serialize(self):
tanks_dict = self._tanks.serialize()
diff --git a/tests/strategies/external/test_init.py b/tests/strategies/external/test_init.py
index dbfed3950..3750be20b 100644
--- a/tests/strategies/external/test_init.py
+++ b/tests/strategies/external/test_init.py
@@ -23,7 +23,7 @@
from unittest.mock import MagicMock, Mock, PropertyMock, patch
import pytest
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, DATE_TIME_FORMAT
from gsy_framework.data_classes import Bid, Offer, Trade, TraderDetails
from gsy_framework.utils import format_datetime
from parameterized import parameterized
@@ -31,7 +31,6 @@
from redis.exceptions import RedisError
import gsy_e.constants
-import gsy_e.gsy_e_core.util
from gsy_e.gsy_e_core.global_objects_singleton import global_objects
from gsy_e.models.area import Area, CoefficientArea
from gsy_e.models.strategy import BidEnabledStrategy
@@ -121,7 +120,7 @@ def teardown_method() -> None:
def test_dispatch_tick_frequency_gets_calculated_correctly(self):
self.external_strategy = LoadHoursExternalStrategy(100)
self._create_and_activate_strategy_area(self.external_strategy)
- gsy_e.gsy_e_core.util.gsy_e.constants.DISPATCH_EVENT_TICK_FREQUENCY_PERCENT = 20
+ gsy_e.constants.DISPATCH_EVENT_TICK_FREQUENCY_PERCENT = 20
self.config.ticks_per_slot = 90
global_objects.external_global_stats(self.area, self.config.ticks_per_slot)
assert (
@@ -146,7 +145,7 @@ def test_dispatch_tick_frequency_gets_calculated_correctly(self):
global_objects.external_global_stats.external_tick_counter._dispatch_tick_frequency
== 19
)
- gsy_e.gsy_e_core.util.gsy_e.constants.DISPATCH_EVENT_TICK_FREQUENCY_PERCENT = 50
+ gsy_e.constants.DISPATCH_EVENT_TICK_FREQUENCY_PERCENT = 50
self.config.ticks_per_slot = 90
global_objects.external_global_stats(self.area, self.config.ticks_per_slot)
assert (
@@ -180,7 +179,7 @@ def test_dispatch_tick_frequency_gets_calculated_correctly(self):
]
)
def test_dispatch_event_tick_to_external_aggregator(self, strategy):
- gsy_e.gsy_e_core.util.gsy_e.constants.DISPATCH_EVENT_TICK_FREQUENCY_PERCENT = 20
+ gsy_e.constants.DISPATCH_EVENT_TICK_FREQUENCY_PERCENT = 20
self._create_and_activate_strategy_area(strategy)
strategy.redis.aggregator.is_controlling_device = lambda _: True
self.config.ticks_per_slot = 90
@@ -204,7 +203,7 @@ def test_dispatch_event_tick_to_external_aggregator(self, strategy):
)
result = strategy.redis.aggregator.add_batch_tick_event.call_args_list[0][0][1]
assert result == {
- "market_slot": GlobalConfig.start_date.format(gsy_e.constants.DATE_TIME_FORMAT),
+ "market_slot": GlobalConfig.start_date.format(DATE_TIME_FORMAT),
"slot_completion": "20%",
}
strategy.redis.reset_mock()
@@ -221,7 +220,7 @@ def test_dispatch_event_tick_to_external_aggregator(self, strategy):
)
result = strategy.redis.aggregator.add_batch_tick_event.call_args_list[0][0][1]
assert result == {
- "market_slot": GlobalConfig.start_date.format(gsy_e.constants.DATE_TIME_FORMAT),
+ "market_slot": GlobalConfig.start_date.format(DATE_TIME_FORMAT),
"slot_completion": "40%",
}
@@ -233,7 +232,7 @@ def test_dispatch_event_tick_to_external_aggregator(self, strategy):
]
)
def test_dispatch_event_tick_to_external_agent(self, strategy):
- gsy_e.gsy_e_core.util.gsy_e.constants.DISPATCH_EVENT_TICK_FREQUENCY_PERCENT = 20
+ gsy_e.constants.DISPATCH_EVENT_TICK_FREQUENCY_PERCENT = 20
self._create_and_activate_strategy_area(strategy)
strategy.redis.aggregator.is_controlling_device = lambda _: False
self.config.ticks_per_slot = 90
@@ -256,7 +255,7 @@ def test_dispatch_event_tick_to_external_agent(self, strategy):
result.pop("area_uuid")
assert result == {
"slot_completion": "20%",
- "market_slot": GlobalConfig.start_date.format(gsy_e.constants.DATE_TIME_FORMAT),
+ "market_slot": GlobalConfig.start_date.format(DATE_TIME_FORMAT),
"event": "tick",
"device_info": strategy._device_info_dict,
}
@@ -274,7 +273,7 @@ def test_dispatch_event_tick_to_external_agent(self, strategy):
result.pop("area_uuid")
assert result == {
"slot_completion": "40%",
- "market_slot": GlobalConfig.start_date.format(gsy_e.constants.DATE_TIME_FORMAT),
+ "market_slot": GlobalConfig.start_date.format(DATE_TIME_FORMAT),
"event": "tick",
"device_info": strategy._device_info_dict,
}
@@ -717,7 +716,7 @@ class TestForecastRelatedFeatures:
def test_set_energy_forecast_succeeds(ext_strategy_fixture):
arguments = {
"transaction_id": transaction_id,
- "energy_forecast": {now().format(gsy_e.constants.DATE_TIME_FORMAT): 1},
+ "energy_forecast": {now().format(DATE_TIME_FORMAT): 1},
}
payload = {"data": json.dumps(arguments)}
assert ext_strategy_fixture.pending_requests == deque([])
@@ -772,7 +771,7 @@ def test_set_energy_forecast_fails_for_wrong_payload(ext_strategy_fixture):
def test_set_energy_measurement_succeeds(ext_strategy_fixture):
arguments = {
"transaction_id": transaction_id,
- "energy_measurement": {now().format(gsy_e.constants.DATE_TIME_FORMAT): 1},
+ "energy_measurement": {now().format(DATE_TIME_FORMAT): 1},
}
payload = {"data": json.dumps(arguments)}
assert ext_strategy_fixture.pending_requests == deque([])
@@ -826,7 +825,7 @@ def test_set_energy_forecast_impl_succeeds(self, ext_strategy_fixture):
ext_strategy_fixture.redis.publish_json = Mock()
arguments = {
"transaction_id": transaction_id,
- "energy_forecast": {now().format(gsy_e.constants.DATE_TIME_FORMAT): 1},
+ "energy_forecast": {now().format(DATE_TIME_FORMAT): 1},
}
response_channel = "response_channel"
ext_strategy_fixture._set_energy_forecast_impl(arguments, response_channel)
@@ -881,7 +880,7 @@ def test_set_energy_forecast_impl_fails_for_negative_energy(self, ext_strategy_f
response_channel = "response_channel"
arguments = {
"transaction_id": transaction_id,
- "energy_forecast": {now().format(gsy_e.constants.DATE_TIME_FORMAT): -1},
+ "energy_forecast": {now().format(DATE_TIME_FORMAT): -1},
}
ext_strategy_fixture._set_energy_forecast_impl(arguments, response_channel)
error_message = (
@@ -912,7 +911,7 @@ def test_set_energy_measurement_impl_succeeds(self, ext_strategy_fixture):
ext_strategy_fixture.redis.publish_json = Mock()
arguments = {
"transaction_id": transaction_id,
- "energy_measurement": {now().format(gsy_e.constants.DATE_TIME_FORMAT): 1},
+ "energy_measurement": {now().format(DATE_TIME_FORMAT): 1},
}
response_channel = "response_channel"
ext_strategy_fixture._set_energy_measurement_impl(arguments, response_channel)
@@ -970,7 +969,7 @@ def test_set_energy_measurement_impl_fails_for_negative_energy(self, ext_strateg
ext_strategy_fixture.redis.publish_json.reset_mock()
arguments = {
"transaction_id": transaction_id,
- "energy_measurement": {now().format(gsy_e.constants.DATE_TIME_FORMAT): -1},
+ "energy_measurement": {now().format(DATE_TIME_FORMAT): -1},
}
ext_strategy_fixture._set_energy_measurement_impl(arguments, response_channel)
error_message = (
diff --git a/tests/strategies/future/test_future_strategy.py b/tests/strategies/future/test_future_strategy.py
index 7f7e23ed2..64cabeed7 100644
--- a/tests/strategies/future/test_future_strategy.py
+++ b/tests/strategies/future/test_future_strategy.py
@@ -15,16 +15,17 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import uuid
from typing import TYPE_CHECKING
from unittest.mock import Mock, MagicMock
import pytest
-from gsy_framework.constants_limits import GlobalConfig, ConstSettings
+from gsy_framework.constants_limits import GlobalConfig, ConstSettings, TIME_ZONE
from gsy_framework.data_classes import TraderDetails
from pendulum import today, duration
-from gsy_e.constants import TIME_ZONE, FutureTemplateStrategiesConstants
+from gsy_e.constants import FutureTemplateStrategiesConstants
from gsy_e.models.market.future import FutureMarkets
from gsy_e.models.strategy.future.strategy import FutureMarketStrategy
from gsy_e.models.strategy.load_hours import LoadHoursStrategy
@@ -37,12 +38,14 @@
class TestFutureMarketStrategy:
"""Test the FutureMarketStrategy class."""
+
# pylint: disable = attribute-defined-outside-init, too-many-instance-attributes
def setup_method(self) -> None:
"""Preparation for the tests execution"""
self._original_future_markets_duration = (
- ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS)
+ ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS
+ )
ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS = 24
self.time_slot = today(tz=TIME_ZONE).at(hour=12, minute=0, second=0)
self.area_mock = Mock()
@@ -56,17 +59,20 @@ def setup_method(self) -> None:
self._original_initial_buying_rate = FutureTemplateStrategiesConstants.INITIAL_BUYING_RATE
self._original_final_buying_rate = FutureTemplateStrategiesConstants.FINAL_BUYING_RATE
self._original_initial_selling_rate = (
- FutureTemplateStrategiesConstants.INITIAL_SELLING_RATE)
+ FutureTemplateStrategiesConstants.INITIAL_SELLING_RATE
+ )
self._original_final_selling_rate = FutureTemplateStrategiesConstants.FINAL_SELLING_RATE
def teardown_method(self) -> None:
"""Test cleanup"""
ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS = (
- self._original_future_markets_duration)
+ self._original_future_markets_duration
+ )
FutureTemplateStrategiesConstants.INITIAL_BUYING_RATE = self._original_initial_buying_rate
FutureTemplateStrategiesConstants.FINAL_BUYING_RATE = self._original_final_buying_rate
FutureTemplateStrategiesConstants.INITIAL_SELLING_RATE = (
- self._original_initial_selling_rate)
+ self._original_initial_selling_rate
+ )
FutureTemplateStrategiesConstants.FINAL_SELLING_RATE = self._original_final_selling_rate
def _setup_strategy_fixture(self, future_strategy_fixture: "BaseStrategy") -> None:
@@ -87,11 +93,13 @@ def test_event_market_cycle_posts_bids_load(self) -> None:
load_strategy_fixture.state.set_desired_energy(1234.0, self.time_slot)
future_strategy.event_market_cycle(load_strategy_fixture)
self.future_markets.bid.assert_called_once_with(
- 10.0 * 1.234, 1.234, TraderDetails(
- self.area_mock.name, self.area_mock.uuid,
- self.area_mock.name, self.area_mock.uuid),
+ 10.0 * 1.234,
+ 1.234,
+ TraderDetails(
+ self.area_mock.name, self.area_mock.uuid, self.area_mock.name, self.area_mock.uuid
+ ),
original_price=10.0 * 1.234,
- time_slot=self.time_slot
+ time_slot=self.time_slot,
)
def test_event_market_cycle_posts_offers_pv(self) -> None:
@@ -102,10 +110,12 @@ def test_event_market_cycle_posts_offers_pv(self) -> None:
pv_strategy_fixture.state.set_available_energy(321.3, self.time_slot)
future_strategy.event_market_cycle(pv_strategy_fixture)
self.future_markets.offer.assert_called_once_with(
- price=50.0 * 321.3, energy=321.3, seller=TraderDetails(
- self.area_mock.name, self.area_mock.uuid,
- self.area_mock.name, self.area_mock.uuid),
- time_slot=self.time_slot
+ price=50.0 * 321.3,
+ energy=321.3,
+ seller=TraderDetails(
+ self.area_mock.name, self.area_mock.uuid, self.area_mock.name, self.area_mock.uuid
+ ),
+ time_slot=self.time_slot,
)
def test_event_market_cycle_posts_bids_and_offers_storage(self) -> None:
@@ -114,37 +124,44 @@ def test_event_market_cycle_posts_bids_and_offers_storage(self) -> None:
future_strategy = FutureMarketStrategy(storage_strategy_fixture.asset_type, 10, 50, 50, 20)
self._setup_strategy_fixture(storage_strategy_fixture)
storage_strategy_fixture.state.activate(duration(minutes=15), self.time_slot)
- storage_strategy_fixture.state.offered_sell_kWh[self.time_slot] = 0.
- storage_strategy_fixture.state.offered_buy_kWh[self.time_slot] = 0.
- storage_strategy_fixture.state.pledged_sell_kWh[self.time_slot] = 0.
- storage_strategy_fixture.state.pledged_buy_kWh[self.time_slot] = 0.
+ storage_strategy_fixture.state.offered_sell_kWh[self.time_slot] = 0.0
+ storage_strategy_fixture.state.offered_buy_kWh[self.time_slot] = 0.0
+ storage_strategy_fixture.state.pledged_sell_kWh[self.time_slot] = 0.0
+ storage_strategy_fixture.state.pledged_buy_kWh[self.time_slot] = 0.0
storage_strategy_fixture.state.get_available_energy_to_buy_kWh = Mock(return_value=3)
storage_strategy_fixture.state.get_available_energy_to_sell_kWh = Mock(return_value=2)
storage_strategy_fixture.state.register_energy_from_posted_offer = Mock()
storage_strategy_fixture.state.register_energy_from_posted_bid = Mock()
future_strategy.event_market_cycle(storage_strategy_fixture)
self.future_markets.offer.assert_called_once_with(
- price=50.0 * 2, energy=2, seller=TraderDetails(
- self.area_mock.name, self.area_mock.uuid,
- self.area_mock.name, self.area_mock.uuid),
- time_slot=self.time_slot)
+ price=50.0 * 2,
+ energy=2,
+ seller=TraderDetails(
+ self.area_mock.name, self.area_mock.uuid, self.area_mock.name, self.area_mock.uuid
+ ),
+ time_slot=self.time_slot,
+ )
storage_strategy_fixture.state.register_energy_from_posted_offer.assert_called_once()
self.future_markets.bid.assert_called_once_with(
- 10.0 * 3, 3, TraderDetails(
- self.area_mock.name, self.area_mock.uuid,
- self.area_mock.name, self.area_mock.uuid
- ), original_price=10.0 * 3,
- time_slot=self.time_slot
+ 10.0 * 3,
+ 3,
+ TraderDetails(
+ self.area_mock.name, self.area_mock.uuid, self.area_mock.name, self.area_mock.uuid
+ ),
+ original_price=10.0 * 3,
+ time_slot=self.time_slot,
)
storage_strategy_fixture.state.register_energy_from_posted_bid.assert_called_once()
@pytest.mark.parametrize(
- "future_strategy_fixture", [LoadHoursStrategy(100), PVStrategy(),
- StorageStrategy(initial_soc=50)])
+ "future_strategy_fixture",
+ [LoadHoursStrategy(100), PVStrategy(), StorageStrategy(initial_soc=50)],
+ )
def test_event_tick_updates_bids_and_offers(
- self, future_strategy_fixture: "BaseStrategy") -> None:
+ self, future_strategy_fixture: "BaseStrategy"
+ ) -> None:
"""Validate that tick event updates existing bids and offers to the expected energy
rate"""
future_strategy = FutureMarketStrategy(future_strategy_fixture.asset_type, 10, 50, 50, 20)
@@ -160,10 +177,10 @@ def test_event_tick_updates_bids_and_offers(
future_strategy_fixture.state.activate(duration(minutes=15), self.time_slot)
future_strategy_fixture.get_available_energy_to_buy_kWh = Mock(return_value=3)
future_strategy_fixture.get_available_energy_to_sell_kWh = Mock(return_value=2)
- future_strategy_fixture.state.offered_sell_kWh[self.time_slot] = 0.
- future_strategy_fixture.state.offered_buy_kWh[self.time_slot] = 0.
- future_strategy_fixture.state.pledged_sell_kWh[self.time_slot] = 0.
- future_strategy_fixture.state.pledged_buy_kWh[self.time_slot] = 0.
+ future_strategy_fixture.state.offered_sell_kWh[self.time_slot] = 0.0
+ future_strategy_fixture.state.offered_buy_kWh[self.time_slot] = 0.0
+ future_strategy_fixture.state.pledged_sell_kWh[self.time_slot] = 0.0
+ future_strategy_fixture.state.pledged_buy_kWh[self.time_slot] = 0.0
future_strategy_fixture.state.register_energy_from_posted_offer = Mock()
future_strategy_fixture.state.register_energy_from_posted_bid = Mock()
future_strategy_fixture.area.current_tick = 0
@@ -179,21 +196,27 @@ def test_event_tick_updates_bids_and_offers(
future_strategy_fixture.area.current_tick = ticks_for_update
future_strategy.event_tick(future_strategy_fixture)
number_of_updates = (
- (ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS * 60 /
- FutureTemplateStrategiesConstants.UPDATE_INTERVAL_MIN) - 1)
+ ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS
+ * 60
+ / FutureTemplateStrategiesConstants.UPDATE_INTERVAL_MIN
+ ) - 1
bid_energy_rate = (50 - 10) / number_of_updates
offer_energy_rate = (50 - 20) / number_of_updates
if isinstance(future_strategy_fixture, LoadHoursStrategy):
future_strategy_fixture.update_bid_rates.assert_called_once_with(
- self.future_markets, 10 + bid_energy_rate, self.time_slot)
+ self.future_markets, 10 + bid_energy_rate, self.time_slot
+ )
if isinstance(future_strategy_fixture, PVStrategy):
future_strategy_fixture.update_offer_rates.assert_called_once_with(
- self.future_markets, 50 - offer_energy_rate, self.time_slot)
+ self.future_markets, 50 - offer_energy_rate, self.time_slot
+ )
if isinstance(future_strategy_fixture, StorageStrategy):
future_strategy_fixture.update_bid_rates.assert_called_once_with(
- self.future_markets, 10 + bid_energy_rate, self.time_slot)
+ self.future_markets, 10 + bid_energy_rate, self.time_slot
+ )
future_strategy_fixture.update_offer_rates.assert_called_once_with(
- self.future_markets, 50 - offer_energy_rate, self.time_slot)
+ self.future_markets, 50 - offer_energy_rate, self.time_slot
+ )
@staticmethod
def test_future_template_strategies_constants() -> None:
diff --git a/tests/strategies/settlement/test_settlement_strategy.py b/tests/strategies/settlement/test_settlement_strategy.py
index 14729fa59..b3c5cab0a 100644
--- a/tests/strategies/settlement/test_settlement_strategy.py
+++ b/tests/strategies/settlement/test_settlement_strategy.py
@@ -15,16 +15,16 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
import uuid
from unittest.mock import Mock, MagicMock
import pytest
-from gsy_framework.constants_limits import ConstSettings
+from gsy_framework.constants_limits import ConstSettings, TIME_ZONE
from gsy_framework.data_classes import Bid, Offer, Trade, TraderDetails
from gsy_framework.utils import format_datetime
from pendulum import today, duration
-from gsy_e.constants import TIME_ZONE
from gsy_e.models.market.two_sided import TwoSidedMarket
from gsy_e.models.strategy.load_hours import LoadHoursStrategy
from gsy_e.models.strategy.pv import PVStrategy
@@ -41,8 +41,9 @@ def setup_method(self):
self.market_mock.time_slot = self.time_slot
self.market_mock.id = str(uuid.uuid4())
self.test_bid = Bid("123", self.time_slot, 10, 1, buyer=TraderDetails("test_name", ""))
- self.test_offer = Offer("234", self.time_slot, 50, 1,
- seller=TraderDetails("test_name", ""))
+ self.test_offer = Offer(
+ "234", self.time_slot, 50, 1, seller=TraderDetails("test_name", "")
+ )
self.market_mock.bid = MagicMock(return_value=self.test_bid)
self.market_mock.offer = MagicMock(return_value=self.test_offer)
self.market_mock.bids = {self.test_bid.id: self.test_bid}
@@ -50,18 +51,19 @@ def setup_method(self):
self.area_mock.name = "test_name"
self.area_mock.uuid = str(uuid.uuid4())
self._area_trader_details = TraderDetails(self.area_mock.name, self.area_mock.uuid)
- self.settlement_markets = {
- self.time_slot: self.market_mock
- }
+ self.settlement_markets = {self.time_slot: self.market_mock}
def _setup_strategy_fixture(
- self, strategy_fixture, can_post_settlement_bid, can_post_settlement_offer):
+ self, strategy_fixture, can_post_settlement_bid, can_post_settlement_offer
+ ):
strategy_fixture.owner = self.area_mock
strategy_fixture.state.set_energy_measurement_kWh(1, self.time_slot)
strategy_fixture.state.can_post_settlement_bid = MagicMock(
- return_value=can_post_settlement_bid)
+ return_value=can_post_settlement_bid
+ )
strategy_fixture.state.can_post_settlement_offer = MagicMock(
- return_value=can_post_settlement_offer)
+ return_value=can_post_settlement_offer
+ )
strategy_fixture.area = Mock()
strategy_fixture.area.settlement_markets = self.settlement_markets
strategy_fixture.get_market_from_id = MagicMock(return_value=self.market_mock)
@@ -77,38 +79,51 @@ def _setup_strategy_fixture(
def teardown_method():
ConstSettings.SettlementMarketSettings.ENABLE_SETTLEMENT_MARKETS = False
- @pytest.mark.parametrize(
- "strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
+ @pytest.mark.parametrize("strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
@pytest.mark.parametrize("can_post_settlement_bid", [True, False])
@pytest.mark.parametrize("can_post_settlement_offer", [True, False])
def test_event_market_cycle_posts_bids_and_offers(
- self, strategy_fixture, can_post_settlement_bid, can_post_settlement_offer):
+ self, strategy_fixture, can_post_settlement_bid, can_post_settlement_offer
+ ):
self._setup_strategy_fixture(
- strategy_fixture, can_post_settlement_bid, can_post_settlement_offer)
+ strategy_fixture, can_post_settlement_bid, can_post_settlement_offer
+ )
self.settlement_strategy.event_market_cycle(strategy_fixture)
if can_post_settlement_bid:
self.market_mock.bid.assert_called_once_with(
- 10.0, 1.0, TraderDetails(
- self.area_mock.name, self.area_mock.uuid,
- self.area_mock.name, self.area_mock.uuid), original_price=10.0,
- time_slot=self.time_slot
+ 10.0,
+ 1.0,
+ TraderDetails(
+ self.area_mock.name,
+ self.area_mock.uuid,
+ self.area_mock.name,
+ self.area_mock.uuid,
+ ),
+ original_price=10.0,
+ time_slot=self.time_slot,
)
if can_post_settlement_offer:
self.market_mock.offer.assert_called_once_with(
- price=50.0, energy=1.0, seller=TraderDetails(
- self.area_mock.name, self.area_mock.uuid,
- self.area_mock.name, self.area_mock.uuid),
- time_slot=self.time_slot
+ price=50.0,
+ energy=1.0,
+ seller=TraderDetails(
+ self.area_mock.name,
+ self.area_mock.uuid,
+ self.area_mock.name,
+ self.area_mock.uuid,
+ ),
+ time_slot=self.time_slot,
)
- @pytest.mark.parametrize(
- "strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
+ @pytest.mark.parametrize("strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
@pytest.mark.parametrize("can_post_settlement_bid", [True, False])
@pytest.mark.parametrize("can_post_settlement_offer", [True, False])
def test_event_tick_updates_bids_and_offers(
- self, strategy_fixture, can_post_settlement_bid, can_post_settlement_offer):
+ self, strategy_fixture, can_post_settlement_bid, can_post_settlement_offer
+ ):
self._setup_strategy_fixture(
- strategy_fixture, can_post_settlement_bid, can_post_settlement_offer)
+ strategy_fixture, can_post_settlement_bid, can_post_settlement_offer
+ )
strategy_fixture.area.current_tick = 0
self.settlement_strategy.event_market_cycle(strategy_fixture)
@@ -123,85 +138,122 @@ def test_event_tick_updates_bids_and_offers(
self.settlement_strategy.event_tick(strategy_fixture)
if can_post_settlement_bid:
self.market_mock.bid.assert_called_once_with(
- 30.0, 1.0, TraderDetails(
- self.area_mock.name, self.area_mock.uuid,
- self.area_mock.name, self.area_mock.uuid), original_price=30.0,
- time_slot=self.time_slot
+ 30.0,
+ 1.0,
+ TraderDetails(
+ self.area_mock.name,
+ self.area_mock.uuid,
+ self.area_mock.name,
+ self.area_mock.uuid,
+ ),
+ original_price=30.0,
+ time_slot=self.time_slot,
)
if can_post_settlement_offer:
self.market_mock.offer.assert_called_once_with(
- 35.0, 1, TraderDetails(
- self.area_mock.name, self.area_mock.uuid),
- original_price=35.0, time_slot=self.time_slot
+ 35.0,
+ 1,
+ TraderDetails(self.area_mock.name, self.area_mock.uuid),
+ original_price=35.0,
+ time_slot=self.time_slot,
)
- @pytest.mark.parametrize(
- "strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
+ @pytest.mark.parametrize("strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
def test_event_trade_updates_energy_deviation(self, strategy_fixture):
self._setup_strategy_fixture(strategy_fixture, False, True)
strategy_fixture.state.set_energy_measurement_kWh(10, self.time_slot)
self.settlement_strategy.event_market_cycle(strategy_fixture)
self.settlement_strategy.event_offer_traded(
- strategy_fixture, self.market_mock.id,
- Trade("456", self.time_slot, self._area_trader_details, self._area_trader_details,
- offer=self.test_offer, traded_energy=1, trade_price=1)
+ strategy_fixture,
+ self.market_mock.id,
+ Trade(
+ "456",
+ self.time_slot,
+ self._area_trader_details,
+ self._area_trader_details,
+ offer=self.test_offer,
+ traded_energy=1,
+ trade_price=1,
+ ),
)
assert strategy_fixture.state.get_unsettled_deviation_kWh(self.time_slot) == 9
- @pytest.mark.parametrize(
- "strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
+ @pytest.mark.parametrize("strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
def test_event_trade_not_update_energy_deviation_on_bid_trade(self, strategy_fixture):
self._setup_strategy_fixture(strategy_fixture, False, True)
strategy_fixture.state.set_energy_measurement_kWh(10, self.time_slot)
self.settlement_strategy.event_market_cycle(strategy_fixture)
self.settlement_strategy.event_offer_traded(
- strategy_fixture, self.market_mock.id,
- Trade("456", self.time_slot, self._area_trader_details, self._area_trader_details,
- bid=self.test_bid, traded_energy=1, trade_price=1)
+ strategy_fixture,
+ self.market_mock.id,
+ Trade(
+ "456",
+ self.time_slot,
+ self._area_trader_details,
+ self._area_trader_details,
+ bid=self.test_bid,
+ traded_energy=1,
+ trade_price=1,
+ ),
)
assert strategy_fixture.state.get_unsettled_deviation_kWh(self.time_slot) == 10
- @pytest.mark.parametrize(
- "strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
+ @pytest.mark.parametrize("strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
def test_event_bid_trade_updates_energy_deviation(self, strategy_fixture):
self._setup_strategy_fixture(strategy_fixture, True, False)
strategy_fixture.state.set_energy_measurement_kWh(15, self.time_slot)
self.settlement_strategy.event_market_cycle(strategy_fixture)
self.settlement_strategy.event_bid_traded(
- strategy_fixture, self.market_mock.id,
- Trade("456", self.time_slot, self._area_trader_details, self._area_trader_details,
- bid=self.test_bid, traded_energy=1, trade_price=1)
+ strategy_fixture,
+ self.market_mock.id,
+ Trade(
+ "456",
+ self.time_slot,
+ self._area_trader_details,
+ self._area_trader_details,
+ bid=self.test_bid,
+ traded_energy=1,
+ trade_price=1,
+ ),
)
assert strategy_fixture.state.get_unsettled_deviation_kWh(self.time_slot) == 14
- @pytest.mark.parametrize(
- "strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
+ @pytest.mark.parametrize("strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
def test_event_bid_traded_does_not_update_energy_deviation_offer_trade(self, strategy_fixture):
self._setup_strategy_fixture(strategy_fixture, True, False)
strategy_fixture.state.set_energy_measurement_kWh(15, self.time_slot)
self.settlement_strategy.event_market_cycle(strategy_fixture)
self.settlement_strategy.event_bid_traded(
- strategy_fixture, self.market_mock.id,
- Trade("456", self.time_slot, self._area_trader_details, self._area_trader_details,
- offer=self.test_offer, traded_energy=1, trade_price=1)
+ strategy_fixture,
+ self.market_mock.id,
+ Trade(
+ "456",
+ self.time_slot,
+ self._area_trader_details,
+ self._area_trader_details,
+ offer=self.test_offer,
+ traded_energy=1,
+ trade_price=1,
+ ),
)
assert strategy_fixture.state.get_unsettled_deviation_kWh(self.time_slot) == 15
- @pytest.mark.parametrize(
- "strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
+ @pytest.mark.parametrize("strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
def test_get_unsettled_deviation_dict(self, strategy_fixture):
self._setup_strategy_fixture(strategy_fixture, True, False)
strategy_fixture.state.set_energy_measurement_kWh(15, self.time_slot)
unsettled_deviation_dict = self.settlement_strategy.get_unsettled_deviation_dict(
- strategy_fixture)
+ strategy_fixture
+ )
assert len(unsettled_deviation_dict["unsettled_deviation_kWh"]) == 1
- assert (list(unsettled_deviation_dict["unsettled_deviation_kWh"].keys()) ==
- [format_datetime(self.time_slot)])
- assert (list(unsettled_deviation_dict["unsettled_deviation_kWh"].values()) ==
- [strategy_fixture.state.get_signed_unsettled_deviation_kWh(self.time_slot)])
+ assert list(unsettled_deviation_dict["unsettled_deviation_kWh"].keys()) == [
+ format_datetime(self.time_slot)
+ ]
+ assert list(unsettled_deviation_dict["unsettled_deviation_kWh"].values()) == [
+ strategy_fixture.state.get_signed_unsettled_deviation_kWh(self.time_slot)
+ ]
- @pytest.mark.parametrize(
- "strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
+ @pytest.mark.parametrize("strategy_fixture", [LoadHoursStrategy(100), PVStrategy()])
def test_get_market_from_id_works_for_settlement_markets(self, strategy_fixture):
self._setup_strategy_fixture(strategy_fixture, True, True)
market = strategy_fixture.get_market_from_id(self.settlement_markets[self.time_slot].id)
@@ -211,24 +263,48 @@ def test_can_get_settlement_bid_be_posted(self):
strategy_fixture = LoadHoursStrategy(100)
self._setup_strategy_fixture(strategy_fixture, True, False)
- assert strategy_fixture.can_settlement_offer_be_posted(
- 1.1, 1, self.settlement_markets[self.time_slot]) is False
+ assert (
+ strategy_fixture.can_settlement_offer_be_posted(
+ 1.1, 1, self.settlement_markets[self.time_slot]
+ )
+ is False
+ )
- assert strategy_fixture.can_settlement_bid_be_posted(
- 0.9, 1, self.settlement_markets[self.time_slot]) is True
+ assert (
+ strategy_fixture.can_settlement_bid_be_posted(
+ 0.9, 1, self.settlement_markets[self.time_slot]
+ )
+ is True
+ )
- assert strategy_fixture.can_settlement_bid_be_posted(
- 1.1, 1, self.settlement_markets[self.time_slot]) is False
+ assert (
+ strategy_fixture.can_settlement_bid_be_posted(
+ 1.1, 1, self.settlement_markets[self.time_slot]
+ )
+ is False
+ )
def test_can_get_settlement_offer_be_posted(self):
strategy_fixture = PVStrategy()
self._setup_strategy_fixture(strategy_fixture, False, True)
- assert strategy_fixture.can_settlement_bid_be_posted(
- 1.1, 1, self.settlement_markets[self.time_slot]) is False
+ assert (
+ strategy_fixture.can_settlement_bid_be_posted(
+ 1.1, 1, self.settlement_markets[self.time_slot]
+ )
+ is False
+ )
- assert strategy_fixture.can_settlement_offer_be_posted(
- 0.9, 1, self.settlement_markets[self.time_slot]) is True
+ assert (
+ strategy_fixture.can_settlement_offer_be_posted(
+ 0.9, 1, self.settlement_markets[self.time_slot]
+ )
+ is True
+ )
- assert strategy_fixture.can_settlement_offer_be_posted(
- 1.1, 1, self.settlement_markets[self.time_slot]) is False
+ assert (
+ strategy_fixture.can_settlement_offer_be_posted(
+ 1.1, 1, self.settlement_markets[self.time_slot]
+ )
+ is False
+ )
diff --git a/tests/strategies/test_strategy_base.py b/tests/strategies/test_strategy_base.py
index 862ed27c8..5cd2d43bc 100644
--- a/tests/strategies/test_strategy_base.py
+++ b/tests/strategies/test_strategy_base.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
# pylint: disable=missing-function-docstring, protected-access, missing-class-docstring
# pylint: disable=no-self-use, redefined-builtin, unused-argument, too-many-arguments
from unittest.mock import MagicMock, patch
@@ -26,7 +27,7 @@
from gsy_framework.data_classes import Bid, Offer, Trade, TraderDetails
from gsy_framework.enums import SpotMarketTypeEnum
-from gsy_e.constants import TIME_ZONE
+from gsy_framework.constants_limits import TIME_ZONE
from gsy_e.gsy_e_core.blockchain_interface import NonBlockchainInterface
from gsy_e.gsy_e_core.exceptions import MarketException
from gsy_e.models.market.one_sided import OneSidedMarket
@@ -110,13 +111,18 @@ def accept_offer(self, offer_or_id, *, buyer="", energy=None, time=None, trade_b
if energy is None:
energy = offer.energy
offer.energy = energy
- return Trade("trade", 0, offer.seller,
- TraderDetails("FakeOwner", ""),
- offer=offer, traded_energy=offer.energy, trade_price=offer.price)
+ return Trade(
+ "trade",
+ 0,
+ offer.seller,
+ TraderDetails("FakeOwner", ""),
+ offer=offer,
+ traded_energy=offer.energy,
+ trade_price=offer.price,
+ )
def bid(self, price, energy, buyer, original_price=None, time_slot=None):
- return Bid(123, pendulum.now(), price, energy, buyer,
- original_price, time_slot=time_slot)
+ return Bid(123, pendulum.now(), price, energy, buyer, original_price, time_slot=time_slot)
@pytest.fixture(name="offers")
@@ -188,9 +194,15 @@ def test_offers_partial_offer(offer1, offers3):
accepted_offer = Offer("id", pendulum.now(), 1, 0.6, offer1.seller)
residual_offer = Offer("new_id", pendulum.now(), 1, 1.2, offer1.seller)
offers3.on_offer_split(offer1, accepted_offer, residual_offer, "market")
- trade = Trade("trade_id", pendulum.now(tz=TIME_ZONE), offer1.seller,
- TraderDetails("buyer", ""),
- offer=accepted_offer, traded_energy=0.6, trade_price=1)
+ trade = Trade(
+ "trade_id",
+ pendulum.now(tz=TIME_ZONE),
+ offer1.seller,
+ TraderDetails("buyer", ""),
+ offer=accepted_offer,
+ traded_energy=0.6,
+ trade_price=1,
+ )
offers3.on_trade("market", trade)
assert len(offers3.sold_in_market("market")) == 1
assert accepted_offer in offers3.sold_in_market("market")
@@ -232,8 +244,10 @@ def test_accept_offer_handles_market_exception(base, offer_to_accept):
assert len(base.offers.bought.keys()) == 0
-@patch("gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
- SpotMarketTypeEnum.TWO_SIDED.value)
+@patch(
+ "gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
+ SpotMarketTypeEnum.TWO_SIDED.value,
+)
def test_accept_post_bid(base):
market = FakeMarket(raises=True)
@@ -246,8 +260,10 @@ def test_accept_post_bid(base):
assert bid.buyer.name == "FakeOwner"
-@patch("gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
- SpotMarketTypeEnum.TWO_SIDED.value)
+@patch(
+ "gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
+ SpotMarketTypeEnum.TWO_SIDED.value,
+)
def test_remove_bid_from_pending(base):
market = FakeMarket(raises=True)
base.area._market = market
@@ -258,8 +274,10 @@ def test_remove_bid_from_pending(base):
assert not base.are_bids_posted(market.id)
-@patch("gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
- SpotMarketTypeEnum.TWO_SIDED.value)
+@patch(
+ "gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
+ SpotMarketTypeEnum.TWO_SIDED.value,
+)
def test_add_bid_to_bought(base):
market = FakeMarket(raises=True)
base.area._market = market
@@ -280,8 +298,9 @@ def test_bid_events_fail_for_one_sided_market(base):
with pytest.raises(AssertionError):
base.event_bid_deleted(market_id=123, bid=test_bid)
with pytest.raises(AssertionError):
- base.event_bid_split(market_id=123, original_bid=test_bid, accepted_bid=test_bid,
- residual_bid=test_bid)
+ base.event_bid_split(
+ market_id=123, original_bid=test_bid, accepted_bid=test_bid, residual_bid=test_bid
+ )
def test_bid_deleted_removes_bid_from_posted(base):
@@ -302,8 +321,9 @@ def test_bid_split_adds_bid_to_posted(base):
market = FakeMarket(raises=False, id=21)
base.area._market = market
base._bids[market.id] = []
- base.event_bid_split(market_id=21, original_bid=test_bid, accepted_bid=accepted_bid,
- residual_bid=residual_bid)
+ base.event_bid_split(
+ market_id=21, original_bid=test_bid, accepted_bid=accepted_bid, residual_bid=residual_bid
+ )
assert base.get_posted_bids(market) == [accepted_bid, residual_bid]
@@ -323,14 +343,33 @@ def test_bid_traded_moves_bid_from_posted_to_traded(base):
def test_trades_returns_market_trades(base):
test_trades = [
- Trade("123", pendulum.now(), TraderDetails(base.owner.name, ""),
- TraderDetails("buyer", ""), 10, 5),
- Trade("123", pendulum.now(), TraderDetails("seller", ""),
- TraderDetails(base.owner.name, ""), 11, 6),
- Trade("123", pendulum.now(), TraderDetails("seller", ""),
- TraderDetails("buyer", ""), 12, 7),
- Trade("123", pendulum.now(), TraderDetails(base.owner.name, ""),
- TraderDetails("buyer", ""), 13, 8),
+ Trade(
+ "123",
+ pendulum.now(),
+ TraderDetails(base.owner.name, ""),
+ TraderDetails("buyer", ""),
+ 10,
+ 5,
+ ),
+ Trade(
+ "123",
+ pendulum.now(),
+ TraderDetails("seller", ""),
+ TraderDetails(base.owner.name, ""),
+ 11,
+ 6,
+ ),
+ Trade(
+ "123", pendulum.now(), TraderDetails("seller", ""), TraderDetails("buyer", ""), 12, 7
+ ),
+ Trade(
+ "123",
+ pendulum.now(),
+ TraderDetails(base.owner.name, ""),
+ TraderDetails("buyer", ""),
+ 13,
+ 8,
+ ),
]
market = FakeMarket(raises=False, id=21)
# pylint: disable=attribute-defined-outside-init
@@ -352,35 +391,67 @@ def test_can_offer_be_posted(market_class):
time_slot = pendulum.now(tz=TIME_ZONE)
market = market_class(time_slot=time_slot)
- base.offers.post(Offer("id", time_slot.add(seconds=1), price=1, energy=12,
- seller=TraderDetails("A", ""),
- time_slot=time_slot), market.id)
- base.offers.post(Offer("id2", time_slot.add(seconds=2), price=1, energy=13,
- seller=TraderDetails("A", ""),
- time_slot=time_slot), market.id)
- base.offers.post(Offer("id3", time_slot.add(seconds=3), price=1, energy=20,
- seller=TraderDetails("A", ""),
- time_slot=time_slot), market.id)
+ base.offers.post(
+ Offer(
+ "id",
+ time_slot.add(seconds=1),
+ price=1,
+ energy=12,
+ seller=TraderDetails("A", ""),
+ time_slot=time_slot,
+ ),
+ market.id,
+ )
+ base.offers.post(
+ Offer(
+ "id2",
+ time_slot.add(seconds=2),
+ price=1,
+ energy=13,
+ seller=TraderDetails("A", ""),
+ time_slot=time_slot,
+ ),
+ market.id,
+ )
+ base.offers.post(
+ Offer(
+ "id3",
+ time_slot.add(seconds=3),
+ price=1,
+ energy=20,
+ seller=TraderDetails("A", ""),
+ time_slot=time_slot,
+ ),
+ market.id,
+ )
assert base.can_offer_be_posted(4.999, 1, 50, market, time_slot=None) is True
- assert base.can_offer_be_posted(5.0, 1, 50, market, time_slot=None) is True
- assert base.can_offer_be_posted(5.001, 1, 50, market, time_slot=None) is False
+ assert base.can_offer_be_posted(5.0, 1, 50, market, time_slot=None) is True
+ assert base.can_offer_be_posted(5.001, 1, 50, market, time_slot=None) is False
assert base.can_offer_be_posted(4.999, 1, 50, market, time_slot=time_slot) is True
- assert base.can_offer_be_posted(5.0, 1, 50, market, time_slot=time_slot) is True
- assert base.can_offer_be_posted(5.001, 1, 50, market, time_slot=time_slot) is False
-
- assert base.can_offer_be_posted(
- 5.001, 1, 50, market, time_slot=time_slot, replace_existing=True) is True
- assert base.can_offer_be_posted(
- 50, 1, 50, market, time_slot=time_slot, replace_existing=True) is True
- assert base.can_offer_be_posted(
- 50.001, 1, 50, market, time_slot=time_slot, replace_existing=True) is False
+ assert base.can_offer_be_posted(5.0, 1, 50, market, time_slot=time_slot) is True
+ assert base.can_offer_be_posted(5.001, 1, 50, market, time_slot=time_slot) is False
+
+ assert (
+ base.can_offer_be_posted(5.001, 1, 50, market, time_slot=time_slot, replace_existing=True)
+ is True
+ )
+ assert (
+ base.can_offer_be_posted(50, 1, 50, market, time_slot=time_slot, replace_existing=True)
+ is True
+ )
+ assert (
+ base.can_offer_be_posted(50.001, 1, 50, market, time_slot=time_slot, replace_existing=True)
+ is False
+ )
@pytest.mark.parametrize("market_class", [TwoSidedMarket])
-@patch("gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
- SpotMarketTypeEnum.TWO_SIDED.value)
+@patch(
+ "gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
+ SpotMarketTypeEnum.TWO_SIDED.value,
+)
def test_can_bid_be_posted(market_class, base):
market = market_class(time_slot=pendulum.now())
@@ -398,8 +469,10 @@ def test_can_bid_be_posted(market_class, base):
@pytest.mark.parametrize("market_class", [TwoSidedMarket])
-@patch("gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
- SpotMarketTypeEnum.TWO_SIDED.value)
+@patch(
+ "gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
+ SpotMarketTypeEnum.TWO_SIDED.value,
+)
def test_post_bid_with_replace_existing(market_class, base):
"""Calling post_bid with replace_existing=True triggers the removal of the existing bids."""
@@ -415,8 +488,10 @@ def test_post_bid_with_replace_existing(market_class, base):
@pytest.mark.parametrize("market_class", [TwoSidedMarket])
-@patch("gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
- SpotMarketTypeEnum.TWO_SIDED.value)
+@patch(
+ "gsy_framework.constants_limits.ConstSettings.MASettings.MARKET_TYPE",
+ SpotMarketTypeEnum.TWO_SIDED.value,
+)
def test_post_bid_without_replace_existing(market_class, base):
"""Calling post_bid with replace_existing=False does not trigger the removal of the existing
bids.
@@ -443,8 +518,7 @@ def test_post_offer_creates_offer_with_correct_parameters(market_class):
market = market_class(bc=NonBlockchainInterface(str(uuid4())), time_slot=pendulum.now())
strategy.area._market = market
- offer_args = {
- "price": 1, "energy": 1}
+ offer_args = {"price": 1, "energy": 1}
offer = strategy.post_offer(market, replace_existing=False, **offer_args)
@@ -467,22 +541,28 @@ def test_post_offer_with_replace_existing(market_class):
# Post a first offer on the market
offer_1_args = {
- "price": 1, "energy": 1, "seller": TraderDetails(
- "FakeOwner", "", "FakeOwnerOrigin", "")}
+ "price": 1,
+ "energy": 1,
+ "seller": TraderDetails("FakeOwner", "", "FakeOwnerOrigin", ""),
+ }
offer = strategy.post_offer(market, replace_existing=False, **offer_1_args)
assert strategy.offers.open_in_market(market.id) == [offer]
# Post a new offer not replacing the previous ones
offer_2_args = {
- "price": 1, "energy": 1, "seller": TraderDetails(
- "FakeOwner", "", "FakeOwnerOrigin", "")}
+ "price": 1,
+ "energy": 1,
+ "seller": TraderDetails("FakeOwner", "", "FakeOwnerOrigin", ""),
+ }
offer_2 = strategy.post_offer(market, replace_existing=False, **offer_2_args)
assert strategy.offers.open_in_market(market.id) == [offer, offer_2]
# Post a new offer replacing the previous ones (default behavior)
offer_3_args = {
- "price": 1, "energy": 1, "seller": TraderDetails(
- "FakeOwner", "", "FakeOwnerOrigin", "")}
+ "price": 1,
+ "energy": 1,
+ "seller": TraderDetails("FakeOwner", "", "FakeOwnerOrigin", ""),
+ }
offer_3 = strategy.post_offer(market, **offer_3_args)
assert strategy.offers.open_in_market(market.id) == [offer_3]
diff --git a/tests/strategies/test_strategy_commercial_producer.py b/tests/strategies/test_strategy_commercial_producer.py
index 2741bb3e1..1f564dcde 100644
--- a/tests/strategies/test_strategy_commercial_producer.py
+++ b/tests/strategies/test_strategy_commercial_producer.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 sys
from uuid import uuid4
@@ -23,7 +24,7 @@
from gsy_framework.constants_limits import ConstSettings, GlobalConfig
from gsy_framework.data_classes import Offer, Trade, BalancingOffer, TraderDetails
-from gsy_e.constants import TIME_ZONE, TIME_FORMAT
+from gsy_framework.constants_limits import TIME_ZONE, TIME_FORMAT
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
from gsy_e.gsy_e_core.util import change_global_config
from gsy_e.models.config import create_simulation_config_from_global_config
@@ -50,6 +51,7 @@ def auto_fixture():
class FakeArea:
"""Fake class that mimics the Area class."""
+
# pylint: disable=too-many-instance-attributes,missing-function-docstring
def __init__(self, _count):
self.current_tick = 2
@@ -89,6 +91,7 @@ def last_past_market(self):
class FakeMarket:
"""Fake class that mimics the Market class."""
+
# pylint: disable=missing-function-docstring,too-many-arguments
def __init__(self, count):
self.id = str(count)
@@ -97,8 +100,7 @@ def __init__(self, count):
self.created_balancing_offers = []
def offer(self, price, energy, seller, original_price=None):
- offer = Offer(
- "id", pendulum.now(), price, energy, seller, original_price)
+ offer = Offer("id", pendulum.now(), price, energy, seller, original_price)
self.created_offers.append(offer)
offer.id = "id"
return offer
@@ -140,14 +142,16 @@ def test_offer_is_created_at_first_market_not_on_activate(commercial_test1, area
def test_balancing_offers_are_not_sent_to_all_markets_if_device_not_in_registry(
- commercial_test1, area_test1):
+ commercial_test1, area_test1
+):
DeviceRegistry.REGISTRY = {}
commercial_test1.event_activate()
assert len(area_test1.test_balancing_market.created_balancing_offers) == 0
def test_balancing_offers_are_sent_to_all_markets_if_device_in_registry(
- commercial_test1, area_test1):
+ commercial_test1, area_test1
+):
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True
DeviceRegistry.REGISTRY = {"FakeArea": (30, 40)}
@@ -164,7 +168,8 @@ def test_balancing_offers_are_sent_to_all_markets_if_device_in_registry(
def test_event_market_cycle_does_not_create_balancing_offer_if_not_in_registry(
- commercial_test1, area_test1):
+ commercial_test1, area_test1
+):
DeviceRegistry.REGISTRY = {}
commercial_test1.event_activate()
commercial_test1.event_market_cycle()
@@ -173,17 +178,16 @@ def test_event_market_cycle_does_not_create_balancing_offer_if_not_in_registry(
def test_event_market_cycle_creates_balancing_offer_on_last_market_if_in_registry(
- commercial_test1, area_test1):
+ commercial_test1, area_test1
+):
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True
DeviceRegistry.REGISTRY = {"FakeArea": (40, 50)}
commercial_test1.event_activate()
commercial_test1.event_market_cycle()
assert len(area_test1.test_balancing_market.created_balancing_offers) == 1
assert len(area_test1.test_balancing_market_2.created_balancing_offers) == 1
- assert area_test1.test_balancing_market_2.created_balancing_offers[0].energy == \
- sys.maxsize
- assert area_test1.test_balancing_market_2.created_balancing_offers[0].price == \
- sys.maxsize * 50
+ assert area_test1.test_balancing_market_2.created_balancing_offers[0].energy == sys.maxsize
+ assert area_test1.test_balancing_market_2.created_balancing_offers[0].price == sys.maxsize * 50
DeviceRegistry.REGISTRY = {}
@@ -205,59 +209,105 @@ def test_event_trade(area_test2, commercial_test2):
commercial_test2.event_activate()
commercial_test2.event_market_cycle()
traded_offer = Offer(
- id="id", creation_time=pendulum.now(), price=20, energy=1,
- seller=TraderDetails("FakeArea", ""))
- commercial_test2.event_offer_traded(market_id=area_test2.test_market.id,
- trade=Trade(id="id",
- offer=traded_offer,
- creation_time=pendulum.now(),
- seller=TraderDetails("FakeArea", ""),
- buyer=TraderDetails("buyer", ""),
- traded_energy=1, trade_price=1)
- )
+ id="id",
+ creation_time=pendulum.now(),
+ price=20,
+ energy=1,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ commercial_test2.event_offer_traded(
+ market_id=area_test2.test_market.id,
+ trade=Trade(
+ id="id",
+ offer=traded_offer,
+ creation_time=pendulum.now(),
+ seller=TraderDetails("FakeArea", ""),
+ buyer=TraderDetails("buyer", ""),
+ traded_energy=1,
+ trade_price=1,
+ ),
+ )
assert len(area_test2.test_market.created_offers) == 1
assert area_test2.test_market.created_offers[-1].energy == sys.maxsize
def test_on_offer_split(area_test2, commercial_test2):
commercial_test2.event_activate()
- original_offer = Offer(id="id", creation_time=pendulum.now(), price=20,
- energy=1, seller=TraderDetails("FakeArea", ""))
- accepted_offer = Offer(id="new_id", creation_time=pendulum.now(), price=15,
- energy=0.75, seller=TraderDetails("FakeArea", ""))
- residual_offer = Offer(id="res_id", creation_time=pendulum.now(), price=55,
- energy=0.25, seller=TraderDetails("FakeArea", ""))
+ original_offer = Offer(
+ id="id",
+ creation_time=pendulum.now(),
+ price=20,
+ energy=1,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ accepted_offer = Offer(
+ id="new_id",
+ creation_time=pendulum.now(),
+ price=15,
+ energy=0.75,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ residual_offer = Offer(
+ id="res_id",
+ creation_time=pendulum.now(),
+ price=55,
+ energy=0.25,
+ seller=TraderDetails("FakeArea", ""),
+ )
commercial_test2.offers.post(original_offer, area_test2.test_market.id)
- commercial_test2.event_offer_split(market_id=area_test2.test_market.id,
- original_offer=original_offer,
- accepted_offer=accepted_offer,
- residual_offer=residual_offer)
+ commercial_test2.event_offer_split(
+ market_id=area_test2.test_market.id,
+ original_offer=original_offer,
+ accepted_offer=accepted_offer,
+ residual_offer=residual_offer,
+ )
assert original_offer.id in commercial_test2.offers.split
assert commercial_test2.offers.split[original_offer.id] == accepted_offer
def test_event_trade_after_offer_changed_partial_offer(area_test2, commercial_test2):
- original_offer = Offer(id="old_id", creation_time=pendulum.now(),
- price=20, energy=1, seller=TraderDetails("FakeArea", ""))
- accepted_offer = Offer(id="old_id", creation_time=pendulum.now(),
- price=15, energy=0.75, seller=TraderDetails("FakeArea", ""))
- residual_offer = Offer(id="res_id", creation_time=pendulum.now(),
- price=5, energy=0.25, seller=TraderDetails("FakeArea", ""))
+ original_offer = Offer(
+ id="old_id",
+ creation_time=pendulum.now(),
+ price=20,
+ energy=1,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ accepted_offer = Offer(
+ id="old_id",
+ creation_time=pendulum.now(),
+ price=15,
+ energy=0.75,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ residual_offer = Offer(
+ id="res_id",
+ creation_time=pendulum.now(),
+ price=5,
+ energy=0.25,
+ seller=TraderDetails("FakeArea", ""),
+ )
commercial_test2.offers.post(original_offer, area_test2.test_market.id)
- commercial_test2.event_offer_split(market_id=area_test2.test_market.id,
- original_offer=original_offer,
- accepted_offer=accepted_offer,
- residual_offer=residual_offer)
+ commercial_test2.event_offer_split(
+ market_id=area_test2.test_market.id,
+ original_offer=original_offer,
+ accepted_offer=accepted_offer,
+ residual_offer=residual_offer,
+ )
assert original_offer.id in commercial_test2.offers.split
assert commercial_test2.offers.split[original_offer.id] == accepted_offer
- commercial_test2.event_offer_traded(market_id=area_test2.test_market.id,
- trade=Trade(id="id",
- offer=original_offer,
- creation_time=pendulum.now(),
- seller=TraderDetails("FakeArea", ""),
- buyer=TraderDetails("buyer", ""),
- traded_energy=1, trade_price=1)
- )
+ commercial_test2.event_offer_traded(
+ market_id=area_test2.test_market.id,
+ trade=Trade(
+ id="id",
+ offer=original_offer,
+ creation_time=pendulum.now(),
+ seller=TraderDetails("FakeArea", ""),
+ buyer=TraderDetails("buyer", ""),
+ traded_energy=1,
+ trade_price=1,
+ ),
+ )
assert residual_offer in commercial_test2.offers.posted
assert commercial_test2.offers.posted[residual_offer] == area_test2.test_market.id
@@ -303,5 +353,4 @@ def test_event_market_cycle(commercial_test3, area_test3):
def test_market_maker_strategy_constructor_modifies_global_market_maker_rate():
# pylint: disable=no-member
MarketMakerStrategy(energy_rate=22)
- assert all(v == 22
- for v in GlobalConfig.market_maker_rate.values())
+ assert all(v == 22 for v in GlobalConfig.market_maker_rate.values())
diff --git a/tests/strategies/test_strategy_infinite_bus.py b/tests/strategies/test_strategy_infinite_bus.py
index 94adc0e4d..ede061576 100644
--- a/tests/strategies/test_strategy_infinite_bus.py
+++ b/tests/strategies/test_strategy_infinite_bus.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
# pylint: disable=no-member, redefined-outer-name, missing-function-docstring, protected-access
# pylint: disable=too-many-instance-attributes, missing-class-docstring, unused-argument
import os
@@ -24,11 +25,10 @@
import pendulum
import pytest
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, TIME_ZONE
from gsy_framework.data_classes import Offer, Trade, BalancingOffer, Bid, TraderDetails
from gsy_e import constants
-from gsy_e.constants import TIME_ZONE
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
from gsy_e.gsy_e_core.util import gsye_root_path
from gsy_e.models.strategy.infinite_bus import InfiniteBusStrategy
@@ -98,8 +98,10 @@ def __init__(self, count):
self.count = count
self.created_offers = []
self.created_balancing_offers = []
- self.sorted_offers = [Offer("id", pendulum.now(), 25., 1., TraderDetails("other", "")),
- Offer("id", pendulum.now(), 26., 1., TraderDetails("other", ""))]
+ self.sorted_offers = [
+ Offer("id", pendulum.now(), 25.0, 1.0, TraderDetails("other", "")),
+ Offer("id", pendulum.now(), 26.0, 1.0, TraderDetails("other", "")),
+ ]
self.traded_offers = []
self._bids = {TIME: []}
@@ -122,16 +124,21 @@ def balancing_offer(self, price, energy, seller):
def accept_offer(self, offer_or_id, buyer, *, energy=None, time=None, trade_bid_info=None):
offer = offer_or_id
- trade = Trade("trade_id", time, offer.seller,
- TraderDetails(buyer, ""),
- offer=offer, traded_energy=1, trade_price=1)
+ trade = Trade(
+ "trade_id",
+ time,
+ offer.seller,
+ TraderDetails(buyer, ""),
+ offer=offer,
+ traded_energy=1,
+ trade_price=1,
+ )
self.traded_offers.append(trade)
return trade
@staticmethod
def bid(price, energy, buyer, original_price=None, time_slot=None):
- bid = Bid("bid_id", pendulum.now(), price, energy, buyer,
- time_slot=time_slot)
+ bid = Bid("bid_id", pendulum.now(), price, energy, buyer, time_slot=time_slot)
return bid
@@ -170,8 +177,9 @@ def testing_offer_is_created_at_first_market_not_on_activate(bus_test1, area_tes
assert area_test1.test_market.created_offers[0].energy == sys.maxsize
-def test_balancing_offers_are_not_sent_to_all_markets_if_device_not_in_registry(bus_test1,
- area_test1):
+def test_balancing_offers_are_not_sent_to_all_markets_if_device_not_in_registry(
+ bus_test1, area_test1
+):
DeviceRegistry.REGISTRY = {}
bus_test1.event_activate()
assert len(area_test1.test_balancing_market.created_balancing_offers) == 0
@@ -194,7 +202,8 @@ def test_balancing_offers_are_sent_to_all_markets_if_device_in_registry(bus_test
def test_event_market_cycle_does_not_create_balancing_offer_if_not_in_registry(
- bus_test1, area_test1):
+ bus_test1, area_test1
+):
DeviceRegistry.REGISTRY = {}
bus_test1.event_activate()
bus_test1.event_market_cycle()
@@ -203,17 +212,16 @@ def test_event_market_cycle_does_not_create_balancing_offer_if_not_in_registry(
def test_event_market_cycle_creates_balancing_offer_on_last_market_if_in_registry(
- bus_test1, area_test1):
+ bus_test1, area_test1
+):
DeviceRegistry.REGISTRY = {"FakeArea": (40, 50)}
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True
bus_test1.event_activate()
bus_test1.event_market_cycle()
assert len(area_test1.test_balancing_market.created_balancing_offers) == 1
assert len(area_test1.test_balancing_market_2.created_balancing_offers) == 1
- assert area_test1.test_balancing_market_2.created_balancing_offers[0].energy == \
- sys.maxsize
- assert area_test1.test_balancing_market_2.created_balancing_offers[0].price == \
- sys.maxsize * 50
+ assert area_test1.test_balancing_market_2.created_balancing_offers[0].energy == sys.maxsize
+ assert area_test1.test_balancing_market_2.created_balancing_offers[0].price == sys.maxsize * 50
@pytest.fixture()
@@ -233,16 +241,24 @@ def test_event_trade(area_test2, bus_test2):
bus_test2.event_activate()
bus_test2.event_market_cycle()
traded_offer = Offer(
- id="id", creation_time=pendulum.now(), price=20, energy=1,
- seller=TraderDetails("FakeArea", ""))
- bus_test2.event_offer_traded(market_id=area_test2.test_market.id,
- trade=Trade(id="id",
- creation_time=pendulum.now(),
- offer=traded_offer,
- seller=TraderDetails("FakeArea", ""),
- buyer=TraderDetails("buyer", ""),
- traded_energy=1, trade_price=1)
- )
+ id="id",
+ creation_time=pendulum.now(),
+ price=20,
+ energy=1,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ bus_test2.event_offer_traded(
+ market_id=area_test2.test_market.id,
+ trade=Trade(
+ id="id",
+ creation_time=pendulum.now(),
+ offer=traded_offer,
+ seller=TraderDetails("FakeArea", ""),
+ buyer=TraderDetails("buyer", ""),
+ traded_energy=1,
+ trade_price=1,
+ ),
+ )
assert len(area_test2.test_market.created_offers) == 1
assert area_test2.test_market.created_offers[-1].energy == sys.maxsize
@@ -250,43 +266,79 @@ def test_event_trade(area_test2, bus_test2):
def test_on_offer_changed(area_test2, bus_test2):
bus_test2.event_activate()
original_offer = Offer(
- id="id", creation_time=pendulum.now(), price=20, energy=1,
- seller=TraderDetails("FakeArea", ""))
+ id="id",
+ creation_time=pendulum.now(),
+ price=20,
+ energy=1,
+ seller=TraderDetails("FakeArea", ""),
+ )
accepted_offer = Offer(
- id="new", creation_time=pendulum.now(), price=15, energy=0.75,
- seller=TraderDetails("FakeArea", ""))
- residual_offer = Offer(id="new_id", creation_time=pendulum.now(), price=5,
- energy=0.25, seller=TraderDetails("FakeArea", ""))
- bus_test2.event_offer_split(market_id=area_test2.test_market.id,
- original_offer=original_offer,
- accepted_offer=accepted_offer,
- residual_offer=residual_offer)
+ id="new",
+ creation_time=pendulum.now(),
+ price=15,
+ energy=0.75,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ residual_offer = Offer(
+ id="new_id",
+ creation_time=pendulum.now(),
+ price=5,
+ energy=0.25,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ bus_test2.event_offer_split(
+ market_id=area_test2.test_market.id,
+ original_offer=original_offer,
+ accepted_offer=accepted_offer,
+ residual_offer=residual_offer,
+ )
assert original_offer.id in bus_test2.offers.split
assert bus_test2.offers.split[original_offer.id] == accepted_offer
def test_event_trade_after_offer_changed_partial_offer(area_test2, bus_test2):
- original_offer = Offer(id="old_id", creation_time=pendulum.now(),
- price=20, energy=1, seller=TraderDetails("FakeArea", ""))
- accepted_offer = Offer(id="old_id", creation_time=pendulum.now(),
- price=15, energy=0.75, seller=TraderDetails("FakeArea", ""))
- residual_offer = Offer(id="res_id", creation_time=pendulum.now(),
- price=5, energy=0.25, seller=TraderDetails("FakeArea", ""))
+ original_offer = Offer(
+ id="old_id",
+ creation_time=pendulum.now(),
+ price=20,
+ energy=1,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ accepted_offer = Offer(
+ id="old_id",
+ creation_time=pendulum.now(),
+ price=15,
+ energy=0.75,
+ seller=TraderDetails("FakeArea", ""),
+ )
+ residual_offer = Offer(
+ id="res_id",
+ creation_time=pendulum.now(),
+ price=5,
+ energy=0.25,
+ seller=TraderDetails("FakeArea", ""),
+ )
bus_test2.offers.post(original_offer, area_test2.test_market.id)
- bus_test2.event_offer_split(market_id=area_test2.test_market.id,
- original_offer=original_offer,
- accepted_offer=accepted_offer,
- residual_offer=residual_offer)
+ bus_test2.event_offer_split(
+ market_id=area_test2.test_market.id,
+ original_offer=original_offer,
+ accepted_offer=accepted_offer,
+ residual_offer=residual_offer,
+ )
assert original_offer.id in bus_test2.offers.split
assert bus_test2.offers.split[original_offer.id] == accepted_offer
- bus_test2.event_offer_traded(market_id=area_test2.test_market.id,
- trade=Trade(id="id",
- creation_time=pendulum.now(),
- offer=original_offer,
- seller=TraderDetails("FakeArea", ""),
- buyer=TraderDetails("buyer", ""),
- traded_energy=1, trade_price=1)
- )
+ bus_test2.event_offer_traded(
+ market_id=area_test2.test_market.id,
+ trade=Trade(
+ id="id",
+ creation_time=pendulum.now(),
+ offer=original_offer,
+ seller=TraderDetails("FakeArea", ""),
+ buyer=TraderDetails("buyer", ""),
+ traded_energy=1,
+ trade_price=1,
+ ),
+ )
assert residual_offer in bus_test2.offers.posted
assert bus_test2.offers.posted[residual_offer] == area_test2.test_market.id
@@ -352,13 +404,15 @@ def test_global_market_maker_rate_single_value(bus_test4):
assert isinstance(GlobalConfig.market_maker_rate, dict)
assert all(
v == ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE
- for v in GlobalConfig.market_maker_rate.values())
+ for v in GlobalConfig.market_maker_rate.values()
+ )
@pytest.fixture()
def bus_test5(area_test1):
c = InfiniteBusStrategy(
- energy_rate_profile=os.path.join(gsye_root_path, "resources", "SAM_SF_Summer.csv"))
+ energy_rate_profile=os.path.join(gsye_root_path, "resources", "SAM_SF_Summer.csv")
+ )
c.area = area_test1
c.owner = area_test1
yield c
@@ -377,7 +431,8 @@ def test_global_market_maker_rate_profile_and_infinite_bus_selling_rate_profile(
@pytest.fixture()
def bus_test6(area_test1):
c = InfiniteBusStrategy(
- buying_rate_profile=os.path.join(gsye_root_path, "resources", "LOAD_DATA_1.csv"))
+ buying_rate_profile=os.path.join(gsye_root_path, "resources", "LOAD_DATA_1.csv")
+ )
c.area = area_test1
c.owner = area_test1
yield c
diff --git a/tests/strategies/test_strategy_load_hours.py b/tests/strategies/test_strategy_load_hours.py
index e68d4289e..aa7e6682c 100644
--- a/tests/strategies/test_strategy_load_hours.py
+++ b/tests/strategies/test_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 .
"""
+
import os
import unittest
from copy import deepcopy
@@ -23,13 +24,12 @@
from uuid import uuid4
import pytest
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, TIME_ZONE, TIME_FORMAT
from gsy_framework.data_classes import Offer, BalancingOffer, Bid, Trade, TraderDetails
from gsy_framework.enums import SpotMarketTypeEnum
from gsy_framework.exceptions import GSyDeviceException
from pendulum import DateTime, duration, today, now
-from gsy_e.constants import TIME_ZONE, TIME_FORMAT
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
from gsy_e.gsy_e_core.util import gsye_root_path
from gsy_e.models.area import Area
@@ -54,15 +54,17 @@ def auto_fixture():
class FakeArea:
def __init__(self):
self.config = create_simulation_config_from_global_config()
- self.name = 'FakeArea'
+ self.name = "FakeArea"
self.uuid = str(uuid4())
self._spot_market = FakeMarket(0)
self.current_market = FakeMarket(0)
self._bids = {}
- self.markets = {TIME: self.current_market,
- TIME + self.config.slot_length: FakeMarket(0),
- TIME + 2 * self.config.slot_length: FakeMarket(0)}
+ self.markets = {
+ TIME: self.current_market,
+ TIME + self.config.slot_length: FakeMarket(0),
+ TIME + 2 * self.config.slot_length: FakeMarket(0),
+ }
self.test_balancing_market = FakeMarket(1)
self.test_balancing_market_2 = FakeMarket(2)
@@ -73,7 +75,7 @@ def is_market_spot_or_future(self, _):
return True
def get_path_to_root_fees(self):
- return 0.
+ return 0.0
@property
def future_markets(self):
@@ -100,7 +102,7 @@ def now(self) -> DateTime:
In this default implementation 'current time' is defined by the number of ticks that
have passed.
"""
- return DateTime.now(tz=TIME_ZONE).start_of('day') + (
+ return DateTime.now(tz=TIME_ZONE).start_of("day") + (
duration(hours=10) + self.config.tick_length * self.current_tick
)
@@ -128,48 +130,54 @@ def __init__(self, count):
def get_bids(self):
return deepcopy(self.bids)
- def bid(self, price: float, energy: float, buyer: str, original_price=None,
- time_slot=None) -> Bid:
- bid = Bid(id="bid_id", creation_time=now(), price=price, energy=energy,
- buyer=buyer,
- original_price=original_price,
- time_slot=time_slot)
+ def bid(
+ self, price: float, energy: float, buyer: str, original_price=None, time_slot=None
+ ) -> Bid:
+ bid = Bid(
+ id="bid_id",
+ creation_time=now(),
+ price=price,
+ energy=energy,
+ buyer=buyer,
+ original_price=original_price,
+ time_slot=time_slot,
+ )
self.bids[bid.id] = bid
return bid
@property
def offers(self):
- return {
- o.id: o for o in self.sorted_offers
- }
+ return {o.id: o for o in self.sorted_offers}
@property
def sorted_offers(self):
offers = [
# Energy price is 1
- [Offer('id', now(), 1, (MIN_BUY_ENERGY/1000), TraderDetails("A", "")),
- # Energy price is 2
- Offer('id', now(), 2, (MIN_BUY_ENERGY/1000), TraderDetails("A", "")),
- # Energy price is 3
- Offer('id', now(), 3, (MIN_BUY_ENERGY/1000), TraderDetails("A", "")),
- # Energy price is 4
- Offer('id', now(), 4, (MIN_BUY_ENERGY/1000), TraderDetails("A", "")),
- ],
[
- Offer('id', now(), 1, (MIN_BUY_ENERGY * 0.033 / 1000), TraderDetails("A", "")),
- Offer('id', now(), 2, (MIN_BUY_ENERGY * 0.033 / 1000), TraderDetails("A", ""))
+ Offer("id", now(), 1, (MIN_BUY_ENERGY / 1000), TraderDetails("A", "")),
+ # Energy price is 2
+ Offer("id", now(), 2, (MIN_BUY_ENERGY / 1000), TraderDetails("A", "")),
+ # Energy price is 3
+ Offer("id", now(), 3, (MIN_BUY_ENERGY / 1000), TraderDetails("A", "")),
+ # Energy price is 4
+ Offer("id", now(), 4, (MIN_BUY_ENERGY / 1000), TraderDetails("A", "")),
],
[
- Offer('id', now(), 1, 5, TraderDetails("A", "")),
- Offer('id2', now(), 2, (MIN_BUY_ENERGY / 1000), TraderDetails("A", ""))
- ]
+ Offer("id", now(), 1, (MIN_BUY_ENERGY * 0.033 / 1000), TraderDetails("A", "")),
+ Offer("id", now(), 2, (MIN_BUY_ENERGY * 0.033 / 1000), TraderDetails("A", "")),
+ ],
+ [
+ Offer("id", now(), 1, 5, TraderDetails("A", "")),
+ Offer("id2", now(), 2, (MIN_BUY_ENERGY / 1000), TraderDetails("A", "")),
+ ],
]
return offers[self.count]
@property
def most_affordable_offers(self):
- return [Offer('id_affordable', now(), 1,
- self.most_affordable_energy, TraderDetails("A", ""))]
+ return [
+ Offer("id_affordable", now(), 1, self.most_affordable_energy, TraderDetails("A", ""))
+ ]
@property
def time_slot(self):
@@ -180,9 +188,9 @@ def time_slot_str(self):
return self.time_slot.strftime(TIME_FORMAT)
def balancing_offer(self, price, energy, seller, market=None):
- offer = BalancingOffer('id', now(), price, energy, seller, market)
+ offer = BalancingOffer("id", now(), price, energy, seller, market)
self.created_balancing_offers.append(offer)
- offer.id = 'id'
+ offer.id = "id"
return offer
def accept_offer(self, **kwargs):
@@ -232,8 +240,8 @@ def market_test2():
@pytest.fixture
def load_hours_strategy_test(called):
strategy = LoadHoursStrategy(
- avg_power_W=620, hrs_of_day=[8, 9, 10, 12],
- initial_buying_rate=10)
+ avg_power_W=620, hrs_of_day=[8, 9, 10, 12], initial_buying_rate=10
+ )
strategy.accept_offer = called
return strategy
@@ -255,8 +263,8 @@ def load_hours_strategy_test2(load_hours_strategy_test, area_test2):
@pytest.fixture
def load_hours_strategy_test4():
strategy = LoadHoursStrategy(
- avg_power_W=620, hrs_of_day=[8, 9, 10, 12],
- initial_buying_rate=10)
+ avg_power_W=620, hrs_of_day=[8, 9, 10, 12], initial_buying_rate=10
+ )
strategy.accept_offer = Mock()
return strategy
@@ -272,17 +280,29 @@ def load_hours_strategy_test5(load_hours_strategy_test4, area_test2):
def test_activate_event_populates_energy_requirement(load_hours_strategy_test1):
load_hours_strategy_test1.event_activate()
energy_requirement = load_hours_strategy_test1.state._energy_requirement_Wh
- assert all([energy == load_hours_strategy_test1._energy_params.energy_per_slot_Wh
- for energy in energy_requirement.values()])
- assert all([load_hours_strategy_test1.state._desired_energy_Wh[ts] == energy
- for ts, energy in energy_requirement.items()])
+ assert all(
+ [
+ energy == load_hours_strategy_test1._energy_params.energy_per_slot_Wh
+ for energy in energy_requirement.values()
+ ]
+ )
+ assert all(
+ [
+ load_hours_strategy_test1.state._desired_energy_Wh[ts] == energy
+ for ts, energy in energy_requirement.items()
+ ]
+ )
# Test if daily energy requirement is calculated correctly for the device
def test_calculate_daily_energy_req(load_hours_strategy_test1):
load_hours_strategy_test1.event_activate()
- assert all([energy == 620/4
- for energy in load_hours_strategy_test1.state._energy_requirement_Wh.values()])
+ assert all(
+ [
+ energy == 620 / 4
+ for energy in load_hours_strategy_test1.state._energy_requirement_Wh.values()
+ ]
+ )
# Test if device accepts the most affordable offer
@@ -290,16 +310,16 @@ def test_device_accepts_offer(load_hours_strategy_test1, market_test1):
load_hours_strategy_test1.event_activate()
load_hours_strategy_test1.event_market_cycle()
cheapest_offer = market_test1.most_affordable_offers[0]
- load_hours_strategy_test1.state._energy_requirement_Wh = \
- {market_test1.time_slot: cheapest_offer.energy * 1000 + 1}
+ load_hours_strategy_test1.state._energy_requirement_Wh = {
+ market_test1.time_slot: cheapest_offer.energy * 1000 + 1
+ }
load_hours_strategy_test1.event_tick()
assert load_hours_strategy_test1.accept_offer.calls[0][0][1] == repr(cheapest_offer)
def test_active_markets(load_hours_strategy_test1):
load_hours_strategy_test1.event_activate()
- assert load_hours_strategy_test1.active_markets == \
- load_hours_strategy_test1.area.all_markets
+ assert load_hours_strategy_test1.active_markets == load_hours_strategy_test1.area.all_markets
def test_event_tick_updates_rates(load_hours_strategy_test1, market_test1):
@@ -311,8 +331,10 @@ def test_event_tick_updates_rates(load_hours_strategy_test1, market_test1):
number_of_markets = len(load_hours_strategy_test1.area.all_markets)
# Test for all available market types (one-sided and two-sided markets)
- available_market_types = (SpotMarketTypeEnum.ONE_SIDED.value,
- SpotMarketTypeEnum.TWO_SIDED.value)
+ available_market_types = (
+ SpotMarketTypeEnum.ONE_SIDED.value,
+ SpotMarketTypeEnum.TWO_SIDED.value,
+ )
# Bids' rates should be updated both when the load can buy energy and when it cannot do it
for can_buy_energy in (True, False):
load_hours_strategy_test1.state.can_buy_more_energy.return_value = can_buy_energy
@@ -348,8 +370,10 @@ def test_event_tick(load_hours_strategy_test1, market_test1):
load_hours_strategy_test1.event_activate()
load_hours_strategy_test1.area.past_markets = {TIME: market_test1}
load_hours_strategy_test1.event_market_cycle()
- assert isclose(load_hours_strategy_test1.state._energy_requirement_Wh[TIME],
- market_test1.most_affordable_energy * 1000)
+ assert isclose(
+ load_hours_strategy_test1.state._energy_requirement_Wh[TIME],
+ market_test1.most_affordable_energy * 1000,
+ )
load_hours_strategy_test1.event_tick()
assert load_hours_strategy_test1.state._energy_requirement_Wh[TIME] == 0
@@ -363,7 +387,7 @@ def test_event_tick_with_partial_offer(load_hours_strategy_test2, market_test2):
requirement = load_hours_strategy_test2.state._energy_requirement_Wh[TIME] / 1000
load_hours_strategy_test2.event_tick()
assert load_hours_strategy_test2.state._energy_requirement_Wh[TIME] == 0
- assert float(load_hours_strategy_test2.accept_offer.calls[0][1]['energy']) == requirement
+ assert float(load_hours_strategy_test2.accept_offer.calls[0][1]["energy"]) == requirement
def test_load_hours_constructor_rejects_incorrect_hrs_of_day():
@@ -371,25 +395,33 @@ def test_load_hours_constructor_rejects_incorrect_hrs_of_day():
LoadHoursStrategy(100, hrs_of_day=[12, 13, 24])
-def test_device_operating_hours_deduction_with_partial_trade(load_hours_strategy_test5,
- market_test2):
+def test_device_operating_hours_deduction_with_partial_trade(
+ load_hours_strategy_test5, market_test2
+):
market_test2.most_affordable_energy = 0.1
load_hours_strategy_test5.event_activate()
# load_hours_strategy_test5.area.past_markets = {TIME: market_test2}
load_hours_strategy_test5.event_market_cycle()
load_hours_strategy_test5.event_tick()
- assert round(((
- float(load_hours_strategy_test5.accept_offer.call_args[0][1].energy) *
- 1000 / load_hours_strategy_test5._energy_params.energy_per_slot_Wh) *
- (load_hours_strategy_test5.simulation_config.slot_length / duration(hours=1))), 2) == \
- round(((0.1/0.155) * 0.25), 2)
-
-
-@pytest.mark.parametrize("partial", [None, Bid(
- 'test_id', now(), 123, 321, TraderDetails("A", ""))])
-def test_event_bid_traded_removes_bid_for_partial_and_non_trade(load_hours_strategy_test5,
- called,
- partial):
+ assert round(
+ (
+ (
+ float(load_hours_strategy_test5.accept_offer.call_args[0][1].energy)
+ * 1000
+ / load_hours_strategy_test5._energy_params.energy_per_slot_Wh
+ )
+ * (load_hours_strategy_test5.simulation_config.slot_length / duration(hours=1))
+ ),
+ 2,
+ ) == round(((0.1 / 0.155) * 0.25), 2)
+
+
+@pytest.mark.parametrize(
+ "partial", [None, Bid("test_id", now(), 123, 321, TraderDetails("A", ""))]
+)
+def test_event_bid_traded_removes_bid_for_partial_and_non_trade(
+ load_hours_strategy_test5, called, partial
+):
ConstSettings.MASettings.MARKET_TYPE = 2
trade_market = load_hours_strategy_test5.area.spot_market
@@ -402,20 +434,29 @@ def test_event_bid_traded_removes_bid_for_partial_and_non_trade(load_hours_strat
# Increase energy requirement to cover the energy from the bid
load_hours_strategy_test5.state._energy_requirement_Wh[TIME] = 1000
- trade = Trade('idt', None, TraderDetails("B", ""),
- TraderDetails(load_hours_strategy_test5.owner.name, ""), bid=bid,
- residual=partial, time_slot=TIME, traded_energy=1, trade_price=1)
+ trade = Trade(
+ "idt",
+ None,
+ TraderDetails("B", ""),
+ TraderDetails(load_hours_strategy_test5.owner.name, ""),
+ bid=bid,
+ residual=partial,
+ time_slot=TIME,
+ traded_energy=1,
+ trade_price=1,
+ )
load_hours_strategy_test5.event_bid_traded(market_id=trade_market.id, bid_trade=trade)
assert len(load_hours_strategy_test5.remove_bid_from_pending.calls) == 1
assert load_hours_strategy_test5.remove_bid_from_pending.calls[0][0][1] == repr(bid.id)
- assert load_hours_strategy_test5.remove_bid_from_pending.calls[0][0][0] == \
- repr(trade_market.id)
+ assert load_hours_strategy_test5.remove_bid_from_pending.calls[0][0][0] == repr(
+ trade_market.id
+ )
-def test_event_bid_traded_removes_bid_from_pending_if_energy_req_0(load_hours_strategy_test5,
- market_test2,
- called):
+def test_event_bid_traded_removes_bid_from_pending_if_energy_req_0(
+ load_hours_strategy_test5, market_test2, called
+):
ConstSettings.MASettings.MARKET_TYPE = 2
trade_market = load_hours_strategy_test5.area.spot_market
@@ -426,15 +467,24 @@ def test_event_bid_traded_removes_bid_from_pending_if_energy_req_0(load_hours_st
bid = list(load_hours_strategy_test5._bids.values())[0][0]
# Increase energy requirement to cover the energy from the bid + threshold
load_hours_strategy_test5.state._energy_requirement_Wh[TIME] = bid.energy * 1000 + 0.000009
- trade = Trade('idt', None, TraderDetails("B", ""),
- TraderDetails(load_hours_strategy_test5.owner.name, ""), residual=True, bid=bid,
- time_slot=TIME, traded_energy=bid.energy, trade_price=bid.price)
+ trade = Trade(
+ "idt",
+ None,
+ TraderDetails("B", ""),
+ TraderDetails(load_hours_strategy_test5.owner.name, ""),
+ residual=True,
+ bid=bid,
+ time_slot=TIME,
+ traded_energy=bid.energy,
+ trade_price=bid.price,
+ )
load_hours_strategy_test5.event_bid_traded(market_id=trade_market.id, bid_trade=trade)
assert len(load_hours_strategy_test5.remove_bid_from_pending.calls) == 1
assert load_hours_strategy_test5.remove_bid_from_pending.calls[0][0][1] == repr(bid.id)
- assert load_hours_strategy_test5.remove_bid_from_pending.calls[0][0][0] == \
- repr(trade_market.id)
+ assert load_hours_strategy_test5.remove_bid_from_pending.calls[0][0][0] == repr(
+ trade_market.id
+ )
@pytest.fixture
@@ -449,60 +499,78 @@ def balancing_fixture(load_hours_strategy_test5):
DeviceRegistry.REGISTRY = {}
-def test_balancing_offers_are_not_created_if_device_not_in_registry(
- balancing_fixture, area_test2):
+def test_balancing_offers_are_not_created_if_device_not_in_registry(balancing_fixture, area_test2):
DeviceRegistry.REGISTRY = {}
balancing_fixture.event_activate()
balancing_fixture.event_market_cycle()
assert len(area_test2.test_balancing_market.created_balancing_offers) == 0
-def test_balancing_offers_are_created_if_device_in_registry(
- balancing_fixture, area_test2):
+def test_balancing_offers_are_created_if_device_in_registry(balancing_fixture, area_test2):
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True
- DeviceRegistry.REGISTRY = {'FakeArea': (30, 40)}
+ DeviceRegistry.REGISTRY = {"FakeArea": (30, 40)}
balancing_fixture.event_activate()
balancing_fixture.event_market_cycle()
balancing_fixture.event_balancing_market_cycle()
- expected_balancing_demand_energy = \
- balancing_fixture.balancing_energy_ratio.demand * \
- balancing_fixture._energy_params.energy_per_slot_Wh
- actual_balancing_demand_energy = \
- area_test2.test_balancing_market.created_balancing_offers[0].energy
+ expected_balancing_demand_energy = (
+ balancing_fixture.balancing_energy_ratio.demand
+ * balancing_fixture._energy_params.energy_per_slot_Wh
+ )
+ actual_balancing_demand_energy = area_test2.test_balancing_market.created_balancing_offers[
+ 0
+ ].energy
assert len(area_test2.test_balancing_market.created_balancing_offers) == 1
assert actual_balancing_demand_energy == -expected_balancing_demand_energy
- actual_balancing_demand_price = \
- area_test2.test_balancing_market.created_balancing_offers[0].price
+ actual_balancing_demand_price = area_test2.test_balancing_market.created_balancing_offers[
+ 0
+ ].price
assert actual_balancing_demand_price == expected_balancing_demand_energy * 30
selected_offer = area_test2.current_market.sorted_offers[0]
- balancing_fixture.state._energy_requirement_Wh[area_test2.current_market.time_slot] = \
+ balancing_fixture.state._energy_requirement_Wh[area_test2.current_market.time_slot] = (
selected_offer.energy * 1000.0
- balancing_fixture.event_offer_traded(market_id=area_test2.current_market.id,
- trade=Trade(id='id',
- creation_time=area_test2.now,
- offer=selected_offer,
- traded_energy=selected_offer.energy,
- trade_price=selected_offer.price,
- seller=TraderDetails("B", ""),
- buyer=TraderDetails("FakeArea", ""),
- time_slot=area_test2.current_market.time_slot)
- )
+ )
+ balancing_fixture.event_offer_traded(
+ market_id=area_test2.current_market.id,
+ trade=Trade(
+ id="id",
+ creation_time=area_test2.now,
+ offer=selected_offer,
+ traded_energy=selected_offer.energy,
+ trade_price=selected_offer.price,
+ seller=TraderDetails("B", ""),
+ buyer=TraderDetails("FakeArea", ""),
+ time_slot=area_test2.current_market.time_slot,
+ ),
+ )
assert len(area_test2.test_balancing_market.created_balancing_offers) == 2
- actual_balancing_supply_energy = \
- area_test2.test_balancing_market.created_balancing_offers[1].energy
- expected_balancing_supply_energy = \
+ actual_balancing_supply_energy = area_test2.test_balancing_market.created_balancing_offers[
+ 1
+ ].energy
+ expected_balancing_supply_energy = (
selected_offer.energy * balancing_fixture.balancing_energy_ratio.supply
+ )
assert actual_balancing_supply_energy == expected_balancing_supply_energy
- actual_balancing_supply_price = \
- area_test2.test_balancing_market.created_balancing_offers[1].price
+ actual_balancing_supply_price = area_test2.test_balancing_market.created_balancing_offers[
+ 1
+ ].price
assert actual_balancing_supply_price == expected_balancing_supply_energy * 40
DeviceRegistry.REGISTRY = {}
-@pytest.mark.parametrize("use_mmr, expected_rate", [
- [True, 9, ], [False, 33, ]
-])
+@pytest.mark.parametrize(
+ "use_mmr, expected_rate",
+ [
+ [
+ True,
+ 9,
+ ],
+ [
+ False,
+ 33,
+ ],
+ ],
+)
def test_use_market_maker_rate_parameter_is_respected(use_mmr, expected_rate):
original_mmr = GlobalConfig.market_maker_rate
GlobalConfig.market_maker_rate = 9
@@ -514,17 +582,26 @@ def test_use_market_maker_rate_parameter_is_respected(use_mmr, expected_rate):
GlobalConfig.market_maker_rate = original_mmr
-@pytest.mark.parametrize("use_mmr, expected_rate", [
- [True, 9, ], [False, 33, ]
-])
+@pytest.mark.parametrize(
+ "use_mmr, expected_rate",
+ [
+ [
+ True,
+ 9,
+ ],
+ [
+ False,
+ 33,
+ ],
+ ],
+)
def test_use_market_maker_rate_parameter_is_respected_for_load_profiles(use_mmr, expected_rate):
original_mmr = GlobalConfig.market_maker_rate
GlobalConfig.market_maker_rate = 9
user_profile_path = os.path.join(gsye_root_path, "resources/Solar_Curve_W_sunny.csv")
load = DefinedLoadStrategy(
- daily_load_profile=user_profile_path,
- final_buying_rate=33,
- use_market_maker_rate=use_mmr)
+ daily_load_profile=user_profile_path, final_buying_rate=33, use_market_maker_rate=use_mmr
+ )
load.area = FakeArea()
load.owner = load.area
load.event_activate()
@@ -539,23 +616,26 @@ def test_load_constructor_rejects_incorrect_rate_parameters():
with pytest.raises(GSyDeviceException):
load.event_activate()
with pytest.raises(GSyDeviceException):
- LoadHoursStrategy(avg_power_W=100, fit_to_limit=True,
- energy_rate_increase_per_update=1)
+ LoadHoursStrategy(avg_power_W=100, fit_to_limit=True, energy_rate_increase_per_update=1)
with pytest.raises(GSyDeviceException):
- LoadHoursStrategy(avg_power_W=100, fit_to_limit=False,
- energy_rate_increase_per_update=-1)
+ LoadHoursStrategy(avg_power_W=100, fit_to_limit=False, energy_rate_increase_per_update=-1)
def test_load_hour_strategy_increases_rate_when_fit_to_limit_is_false(market_test1):
- load = LoadHoursStrategy(avg_power_W=100, initial_buying_rate=0, final_buying_rate=30,
- fit_to_limit=False, energy_rate_increase_per_update=10,
- update_interval=5)
+ load = LoadHoursStrategy(
+ avg_power_W=100,
+ initial_buying_rate=0,
+ final_buying_rate=30,
+ fit_to_limit=False,
+ energy_rate_increase_per_update=10,
+ update_interval=5,
+ )
load.area = FakeArea()
load.owner = load.area
load.event_activate()
load.event_market_cycle()
assert load.state._energy_requirement_Wh[TIME] == 25.0
- offer = Offer('id', now(), 1, (MIN_BUY_ENERGY/500), TraderDetails("A", ""), 1)
+ offer = Offer("id", now(), 1, (MIN_BUY_ENERGY / 500), TraderDetails("A", ""), 1)
load._one_sided_market_event_tick(market_test1, offer)
assert load.bid_update.get_updated_rate(TIME) == 0
assert load.state._energy_requirement_Wh[TIME] == 25.0
@@ -576,12 +656,19 @@ def load_hours_strategy_test3(area_test1):
def test_assert_if_trade_rate_is_higher_than_bid_rate(load_hours_strategy_test3):
market_id = 0
- load_hours_strategy_test3._bids[market_id] = \
- [Bid("bid_id", now(), 30, 1, buyer=TraderDetails("FakeArea", ""))]
+ load_hours_strategy_test3._bids[market_id] = [
+ Bid("bid_id", now(), 30, 1, buyer=TraderDetails("FakeArea", ""))
+ ]
expensive_bid = Bid("bid_id", now(), 31, 1, buyer=TraderDetails("FakeArea", ""))
- trade = Trade("trade_id", "time", TraderDetails(load_hours_strategy_test3.owner.name, ""),
- TraderDetails(load_hours_strategy_test3.owner.name, ""), bid=expensive_bid,
- traded_energy=1, trade_price=31)
+ trade = Trade(
+ "trade_id",
+ "time",
+ TraderDetails(load_hours_strategy_test3.owner.name, ""),
+ TraderDetails(load_hours_strategy_test3.owner.name, ""),
+ bid=expensive_bid,
+ traded_energy=1,
+ trade_price=31,
+ )
with pytest.raises(AssertionError):
load_hours_strategy_test3.event_offer_traded(market_id=market_id, trade=trade)
@@ -612,27 +699,34 @@ def test_set_energy_measurement_of_last_market(utils_mock, load_hours_strategy_t
load_hours_strategy_test1._set_energy_measurement_of_last_market()
load_hours_strategy_test1.state.set_energy_measurement_kWh.assert_called_once_with(
- 100, load_hours_strategy_test1.area.current_market.time_slot)
+ 100, load_hours_strategy_test1.area.current_market.time_slot
+ )
-@pytest.mark.parametrize("use_mmr, initial_buying_rate", [
- (True, 40), (False, 40)])
+@pytest.mark.parametrize("use_mmr, initial_buying_rate", [(True, 40), (False, 40)])
def test_predefined_load_strategy_rejects_incorrect_rate_parameters(use_mmr, initial_buying_rate):
user_profile_path = os.path.join(gsye_root_path, "resources/Solar_Curve_W_sunny.csv")
load = DefinedLoadStrategy(
daily_load_profile=user_profile_path,
initial_buying_rate=initial_buying_rate,
- use_market_maker_rate=use_mmr)
+ use_market_maker_rate=use_mmr,
+ )
load.area = FakeArea()
load.owner = load.area
with pytest.raises(GSyDeviceException):
load.event_activate()
with pytest.raises(GSyDeviceException):
- DefinedLoadStrategy(daily_load_profile=user_profile_path, fit_to_limit=True,
- energy_rate_increase_per_update=1)
+ DefinedLoadStrategy(
+ daily_load_profile=user_profile_path,
+ fit_to_limit=True,
+ energy_rate_increase_per_update=1,
+ )
with pytest.raises(GSyDeviceException):
- DefinedLoadStrategy(daily_load_profile=user_profile_path, fit_to_limit=False,
- energy_rate_increase_per_update=-1)
+ DefinedLoadStrategy(
+ daily_load_profile=user_profile_path,
+ fit_to_limit=False,
+ energy_rate_increase_per_update=-1,
+ )
@pytest.fixture(name="load_hours_fixture")
@@ -653,9 +747,15 @@ def test_event_bid_traded_calls_settlement_market_event_bid_traded(load_hours_fi
although no spot market can be found by event_bid_traded."""
load_hours_fixture._settlement_market_strategy = Mock()
bid = Bid("bid", None, 1, 1, TraderDetails("buyer", ""))
- trade = Trade('idt', None, TraderDetails("B", ""),
- TraderDetails(load_hours_fixture.owner.name, ""), bid=bid,
- traded_energy=1, trade_price=1)
+ trade = Trade(
+ "idt",
+ None,
+ TraderDetails("B", ""),
+ TraderDetails(load_hours_fixture.owner.name, ""),
+ bid=bid,
+ traded_energy=1,
+ trade_price=1,
+ )
load_hours_fixture.event_bid_traded(market_id="not existing", bid_trade=trade)
load_hours_fixture._settlement_market_strategy.event_bid_traded.assert_called_once()
@@ -665,8 +765,14 @@ def test_event_offer_traded_calls_settlement_market_event_offer_traded(load_hour
although no spot market can be found by event_offer_traded."""
load_hours_fixture._settlement_market_strategy = Mock()
offer = Offer("oid", None, 1, 1, TraderDetails("seller", ""))
- trade = Trade('idt', None, TraderDetails("B", ""),
- TraderDetails(load_hours_fixture.owner.name, ""), offer=offer,
- traded_energy=1, trade_price=1)
+ trade = Trade(
+ "idt",
+ None,
+ TraderDetails("B", ""),
+ TraderDetails(load_hours_fixture.owner.name, ""),
+ offer=offer,
+ traded_energy=1,
+ trade_price=1,
+ )
load_hours_fixture.event_offer_traded(market_id="not existing", trade=trade)
load_hours_fixture._settlement_market_strategy.event_offer_traded.assert_called_once()
diff --git a/tests/strategies/test_strategy_pv.py b/tests/strategies/test_strategy_pv.py
index f0d7705f9..103b92846 100644
--- a/tests/strategies/test_strategy_pv.py
+++ b/tests/strategies/test_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 .
"""
+
# pylint: disable=missing-function-docstring,protected-access
import os
import uuid
@@ -24,13 +25,12 @@
import pendulum
import pytest
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, TIME_FORMAT, TIME_ZONE
from gsy_framework.data_classes import Offer, Trade, TraderDetails
from gsy_framework.exceptions import GSyDeviceException
from gsy_framework.utils import generate_market_slot_list
from parameterized import parameterized
-from gsy_e.constants import TIME_FORMAT, TIME_ZONE
from gsy_e.gsy_e_core.util import gsye_root_path
from gsy_e.models.config import create_simulation_config_from_global_config
from gsy_e.models.strategy.predefined_pv import PVPredefinedStrategy, PVUserProfileStrategy
@@ -52,6 +52,7 @@ def auto_fixture():
class FakeArea:
"""Fake class that mimics the Area class."""
+
def __init__(self):
self.config = create_simulation_config_from_global_config()
self.current_tick = 2
@@ -81,7 +82,7 @@ def current_market(self):
@staticmethod
def get_path_to_root_fees():
- return 0.
+ return 0.0
@property
def now(self) -> pendulum.DateTime:
@@ -98,9 +99,7 @@ def now(self) -> pendulum.DateTime:
@property
def all_markets(self):
- return [self.test_market,
- self.test_market,
- self.test_market]
+ return [self.test_market, self.test_market, self.test_market]
@property
def spot_market(self):
@@ -120,24 +119,39 @@ def create_spot_market(self, time_slot):
class FakeMarketTimeSlot:
"""Add fake market implementation that contains the time slot."""
+
def __init__(self, time_slot):
self.time_slot = time_slot
class FakeMarket:
"""Fake class that mimics the Market class."""
+
def __init__(self, count):
self.count = count
self.id = str(count)
self.created_offers = []
self.offers = {
- "id": Offer(id="id", creation_time=pendulum.now(), price=10, energy=0.5,
- seller=TraderDetails("A", ""))}
+ "id": Offer(
+ id="id",
+ creation_time=pendulum.now(),
+ price=10,
+ energy=0.5,
+ seller=TraderDetails("A", ""),
+ )
+ }
def offer(self, price, energy, seller, original_price=None, time_slot=None):
# pylint: disable=too-many-arguments
- offer = Offer(str(uuid.uuid4()), pendulum.now(), price, energy, seller,
- original_price, time_slot=time_slot)
+ offer = Offer(
+ str(uuid.uuid4()),
+ pendulum.now(),
+ price,
+ energy,
+ seller,
+ original_price,
+ time_slot=time_slot,
+ )
self.created_offers.append(offer)
self.offers[offer.id] = offer
return offer
@@ -157,6 +171,7 @@ def delete_offer(_offer_id):
class FakeTrade:
"""Fake class that mimics the Trade class."""
+
def __init__(self, offer):
self.offer = offer
self.match_details = {"offer": offer, "bid": None}
@@ -219,11 +234,16 @@ def testing_event_tick(pv_test2, market_test2, area_test2):
assert len(pv_test2.offers.posted.items()) == 1
offer_id1 = list(pv_test2.offers.posted.keys())[0]
offer1 = market_test2.offers[offer_id1]
- assert market_test2.created_offers[0].price == \
- 29.9 * pv_test2.state._energy_production_forecast_kWh[TIME]
- assert pv_test2.state._energy_production_forecast_kWh[
- pendulum.today(tz=TIME_ZONE).at(hour=0, minute=0, second=2)
- ] == 0
+ assert (
+ market_test2.created_offers[0].price
+ == 29.9 * pv_test2.state._energy_production_forecast_kWh[TIME]
+ )
+ assert (
+ pv_test2.state._energy_production_forecast_kWh[
+ pendulum.today(tz=TIME_ZONE).at(hour=0, minute=0, second=2)
+ ]
+ == 0
+ )
area_test2.current_tick_in_slot = area_test2.config.ticks_per_slot - 2
pv_test2.event_tick()
offer_id2 = list(pv_test2.offers.posted.keys())[0]
@@ -249,8 +269,7 @@ def fixture_pv_test3(area_test3):
p.area = area_test3
p.owner = area_test3
p.offers.posted = {
- Offer("id", pendulum.now(), 1, 1, TraderDetails("FakeArea", "")):
- area_test3.test_market.id
+ Offer("id", pendulum.now(), 1, 1, TraderDetails("FakeArea", "")): area_test3.test_market.id
}
return p
@@ -284,25 +303,34 @@ def fixture_pv_test4(area_test3):
p.area = area_test3
p.owner = area_test3
p.offers.posted = {
- Offer(id="id", creation_time=TIME, price=20,
- energy=1, seller=TraderDetails("FakeArea", "")): area_test3.test_market.id
+ Offer(
+ id="id", creation_time=TIME, price=20, energy=1, seller=TraderDetails("FakeArea", "")
+ ): area_test3.test_market.id
}
return p
def testing_event_trade(area_test3, pv_test4):
pv_test4.state._available_energy_kWh[area_test3.test_market.time_slot] = 1
- pv_test4.event_offer_traded(market_id=area_test3.test_market.id,
- trade=Trade(
- id="id", creation_time=pendulum.now(),
- traded_energy=1, trade_price=20,
- offer=Offer(id="id", creation_time=TIME,
- price=20,
- energy=1, seller=TraderDetails("FakeArea", "")),
- seller=TraderDetails(area_test3.name, ""),
- buyer=TraderDetails("buyer", ""),
- time_slot=area_test3.test_market.time_slot)
- )
+ pv_test4.event_offer_traded(
+ market_id=area_test3.test_market.id,
+ trade=Trade(
+ id="id",
+ creation_time=pendulum.now(),
+ traded_energy=1,
+ trade_price=20,
+ offer=Offer(
+ id="id",
+ creation_time=TIME,
+ price=20,
+ energy=1,
+ seller=TraderDetails("FakeArea", ""),
+ ),
+ seller=TraderDetails(area_test3.name, ""),
+ buyer=TraderDetails("buyer", ""),
+ time_slot=area_test3.test_market.time_slot,
+ ),
+ )
assert len(pv_test4.offers.open) == 0
@@ -333,7 +361,8 @@ def fixture_area_test66():
@pytest.fixture(name="pv_test66")
def fixture_pv_test66(area_test66):
original_future_markets_duration = (
- ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS)
+ ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS
+ )
ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS = 0
p = PVStrategy()
p.area = area_test66
@@ -341,7 +370,8 @@ def fixture_pv_test66(area_test66):
p.offers.posted = {}
yield p
ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS = (
- original_future_markets_duration)
+ original_future_markets_duration
+ )
def testing_produced_energy_forecast_real_data(pv_test66):
@@ -358,25 +388,32 @@ def __init__(self, time_of_day: str):
self.total = 0
self.count = 0
self.time_of_day = time_of_day
+
morning_counts = _Counts("morning")
afternoon_counts = _Counts("afternoon")
evening_counts = _Counts("evening")
- for (time, _power) in pv_test66.state._energy_production_forecast_kWh.items():
+ for time, _power in pv_test66.state._energy_production_forecast_kWh.items():
if time < morning_time:
morning_counts.total += 1
- morning_counts.count = morning_counts.count + 1 \
- if pv_test66.state._energy_production_forecast_kWh[time] == 0 \
+ morning_counts.count = (
+ morning_counts.count + 1
+ if pv_test66.state._energy_production_forecast_kWh[time] == 0
else morning_counts.count
+ )
elif morning_time < time < afternoon_time:
afternoon_counts.total += 1
- afternoon_counts.count = afternoon_counts.count + 1 \
- if pv_test66.state._energy_production_forecast_kWh[time] > 0.001 \
+ afternoon_counts.count = (
+ afternoon_counts.count + 1
+ if pv_test66.state._energy_production_forecast_kWh[time] > 0.001
else afternoon_counts.count
+ )
elif time > afternoon_time:
evening_counts.total += 1
- evening_counts.count = evening_counts.count + 1 \
- if pv_test66.state._energy_production_forecast_kWh[time] == 0 \
+ evening_counts.count = (
+ evening_counts.count + 1
+ if pv_test66.state._energy_production_forecast_kWh[time] == 0
else evening_counts.count
+ )
total_count = morning_counts.total + afternoon_counts.total + evening_counts.total
assert len(list(pv_test66.state._energy_production_forecast_kWh.items())) == total_count
@@ -400,15 +437,21 @@ def __init__(self, time_of_day: str):
# The pv sells its whole production at once if possible.
# Make sure that it doesnt offer it again after selling.
+
def test_does_not_offer_sold_energy_again(pv_test6, market_test3):
pv_test6.event_activate()
pv_test6.event_market_cycle()
- assert market_test3.created_offers[0].energy == \
- pv_test6.state._energy_production_forecast_kWh[TIME]
+ assert (
+ market_test3.created_offers[0].energy
+ == pv_test6.state._energy_production_forecast_kWh[TIME]
+ )
fake_trade = FakeTrade(market_test3.created_offers[0])
fake_trade.seller = TraderDetails(
- pv_test6.owner.name, fake_trade.seller.uuid,
- fake_trade.seller.origin, fake_trade.seller.origin_uuid)
+ pv_test6.owner.name,
+ fake_trade.seller.uuid,
+ fake_trade.seller.origin,
+ fake_trade.seller.origin_uuid,
+ )
fake_trade.time_slot = market_test3.time_slot
pv_test6.event_offer_traded(market_id=market_test3.id, trade=fake_trade)
market_test3.created_offers = []
@@ -434,8 +477,9 @@ def fixture_pv_test7(area_test3):
p = PVStrategy(panel_count=1, initial_selling_rate=30)
p.area = area_test3
p.owner = area_test3
- p.offers.posted = {Offer(
- "id", pendulum.now(), 1, 1, TraderDetails("FakeArea", "")): area_test3.test_market.id}
+ p.offers.posted = {
+ Offer("id", pendulum.now(), 1, 1, TraderDetails("FakeArea", "")): area_test3.test_market.id
+ }
return p
@@ -444,8 +488,9 @@ def fixture_pv_test8(area_test3):
p = PVStrategy(panel_count=1, initial_selling_rate=30)
p.area = area_test3
p.owner = area_test3
- p.offers.posted = {Offer(
- "id", pendulum.now(), 1, 1, TraderDetails("FakeArea", "")): area_test3.test_market.id}
+ p.offers.posted = {
+ Offer("id", pendulum.now(), 1, 1, TraderDetails("FakeArea", "")): area_test3.test_market.id
+ }
return p
@@ -499,18 +544,27 @@ def test_initial_selling_rate(pv_strategy_test10, area_test10):
pv_strategy_test10.event_activate()
pv_strategy_test10.event_market_cycle()
created_offer = area_test10.all_markets[0].created_offers[0]
- assert created_offer.price/created_offer.energy == 25
-
-
-@parameterized.expand([
- [PVStrategy, True, 12, ],
- [PVStrategy, False, 19, ],
-])
+ assert created_offer.price / created_offer.energy == 25
+
+
+@parameterized.expand(
+ [
+ [
+ PVStrategy,
+ True,
+ 12,
+ ],
+ [
+ PVStrategy,
+ False,
+ 19,
+ ],
+ ]
+)
def test_use_mmr_parameter_is_respected1(strategy_type, use_mmr, expected_rate):
original_mmr = GlobalConfig.market_maker_rate
GlobalConfig.market_maker_rate = 12
- pv = strategy_type(initial_selling_rate=19, use_market_maker_rate=use_mmr,
- capacity_kW=0.2)
+ pv = strategy_type(initial_selling_rate=19, use_market_maker_rate=use_mmr, capacity_kW=0.2)
pv.area = FakeArea()
pv.owner = pv.area
pv.event_activate()
@@ -518,15 +572,24 @@ def test_use_mmr_parameter_is_respected1(strategy_type, use_mmr, expected_rate):
GlobalConfig.market_maker_rate = original_mmr
-@parameterized.expand([
- [PVPredefinedStrategy, True, 12, ],
- [PVPredefinedStrategy, False, 19, ],
-])
+@parameterized.expand(
+ [
+ [
+ PVPredefinedStrategy,
+ True,
+ 12,
+ ],
+ [
+ PVPredefinedStrategy,
+ False,
+ 19,
+ ],
+ ]
+)
def test_use_mmr_parameter_is_respected2(strategy_type, use_mmr, expected_rate):
original_mmr = GlobalConfig.market_maker_rate
GlobalConfig.market_maker_rate = 12
- pv = strategy_type(initial_selling_rate=19, use_market_maker_rate=use_mmr,
- cloud_coverage=1)
+ pv = strategy_type(initial_selling_rate=19, use_market_maker_rate=use_mmr, cloud_coverage=1)
pv.area = FakeArea()
pv.owner = pv.area
pv.event_activate()
@@ -534,16 +597,25 @@ def test_use_mmr_parameter_is_respected2(strategy_type, use_mmr, expected_rate):
GlobalConfig.market_maker_rate = original_mmr
-@parameterized.expand([
- [True, 13, ],
- [False, 17, ],
-])
+@parameterized.expand(
+ [
+ [
+ True,
+ 13,
+ ],
+ [
+ False,
+ 17,
+ ],
+ ]
+)
def test_use_mmr_parameter_is_respected_for_pv_profiles(use_mmr, expected_rate):
original_mmr = GlobalConfig.market_maker_rate
GlobalConfig.market_maker_rate = 13
user_profile_path = os.path.join(gsye_root_path, "resources/Solar_Curve_W_sunny.csv")
pv = PVUserProfileStrategy(
- power_profile=user_profile_path, initial_selling_rate=17, use_market_maker_rate=use_mmr)
+ power_profile=user_profile_path, initial_selling_rate=17, use_market_maker_rate=use_mmr
+ )
pv.area = FakeArea()
pv.owner = pv.area
pv.event_activate()
@@ -562,12 +634,18 @@ def fixture_pv_test11(area_test3):
def test_assert_if_trade_rate_is_lower_than_offer_rate(pv_test11):
market_id = "market_id"
pv_test11.offers.sold[market_id] = [
- Offer("offer_id", pendulum.now(), 30, 1, TraderDetails("FakeArea", ""))]
+ Offer("offer_id", pendulum.now(), 30, 1, TraderDetails("FakeArea", ""))
+ ]
too_cheap_offer = Offer("offer_id", pendulum.now(), 29, 1, TraderDetails("FakeArea", ""))
trade = Trade(
- "trade_id", "time", TraderDetails(pv_test11.owner.name, ""),
- TraderDetails("buyer", ""), offer=too_cheap_offer,
- traded_energy=1, trade_price=1)
+ "trade_id",
+ "time",
+ TraderDetails(pv_test11.owner.name, ""),
+ TraderDetails("buyer", ""),
+ offer=too_cheap_offer,
+ traded_energy=1,
+ trade_price=1,
+ )
with pytest.raises(AssertionError):
pv_test11.event_offer_traded(market_id=market_id, trade=trade)
@@ -599,4 +677,5 @@ def test_set_energy_measurement_of_last_market(utils_mock, pv_strategy):
pv_strategy._set_energy_measurement_of_last_market()
pv_strategy.state.set_energy_measurement_kWh.assert_called_once_with(
- 100, pv_strategy.area.current_market.time_slot)
+ 100, pv_strategy.area.current_market.time_slot
+ )
diff --git a/tests/strategies/test_strategy_pvpredefined.py b/tests/strategies/test_strategy_pvpredefined.py
index 0a346071e..0a0dd1102 100644
--- a/tests/strategies/test_strategy_pvpredefined.py
+++ b/tests/strategies/test_strategy_pvpredefined.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
# pylint: disable=redefined-outer-name, protected-access, missing-function-docstring
# pylint: disable=missing-class-docstring, pointless-string-statement, no-self-use,global-statement
import os
@@ -25,7 +26,7 @@
import pendulum
import pytest
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, TIME_ZONE, TIME_FORMAT
from gsy_framework.data_classes import Offer, TraderDetails
from gsy_framework.exceptions import GSyDeviceException
from gsy_framework.read_user_profile import read_arbitrary_profile, InputProfileTypes
@@ -33,7 +34,6 @@
from gsy_framework.enums import ConfigurationType
from pendulum import DateTime, duration, datetime
-from gsy_e.constants import TIME_ZONE, TIME_FORMAT
from gsy_e.gsy_e_core.util import gsye_root_path, change_global_config
from gsy_e.models.config import create_simulation_config_from_global_config
from gsy_e.models.strategy.predefined_pv import PVPredefinedStrategy, PVUserProfileStrategy
@@ -125,13 +125,26 @@ def __init__(self, count):
self.id = str(count)
self.created_offers = []
self.offers = {
- "id": Offer(id="id", creation_time=pendulum.now(), price=10, energy=0.5,
- seller=TraderDetails("A", ""))}
+ "id": Offer(
+ id="id",
+ creation_time=pendulum.now(),
+ price=10,
+ energy=0.5,
+ seller=TraderDetails("A", ""),
+ )
+ }
self._time_slot = TIME
def offer(self, price, energy, seller, original_price=None, time_slot=None):
- offer = Offer(str(uuid.uuid4()), pendulum.now(), price, energy, seller,
- original_price, time_slot=time_slot)
+ offer = Offer(
+ str(uuid.uuid4()),
+ pendulum.now(),
+ price,
+ energy,
+ seller,
+ original_price,
+ time_slot=time_slot,
+ )
self.created_offers.append(offer)
self.offers[offer.id] = offer
return offer
@@ -216,8 +229,11 @@ def pv_test3(area_test3):
p = PVPredefinedStrategy(cloud_coverage=ConstSettings.PVSettings.DEFAULT_POWER_PROFILE)
p.area = area_test3
p.owner = area_test3
- p.offers.posted = {Offer("id", pendulum.now(), 30, 1,
- TraderDetails("FakeArea", "")): area_test3.test_market.id}
+ p.offers.posted = {
+ Offer(
+ "id", pendulum.now(), 30, 1, TraderDetails("FakeArea", "")
+ ): area_test3.test_market.id
+ }
return p
@@ -244,8 +260,13 @@ def pv_test4(area_test3, _called):
p.area = area_test3
p.owner = area_test3
p.offers.posted = {
- Offer(id="id", creation_time=pendulum.now(), price=20, energy=1,
- seller=TraderDetails("FakeArea", "")): area_test3.test_market.id
+ Offer(
+ id="id",
+ creation_time=pendulum.now(),
+ price=20,
+ energy=1,
+ seller=TraderDetails("FakeArea", ""),
+ ): area_test3.test_market.id
}
return p
@@ -304,26 +325,33 @@ def __init__(self, time):
self.total = 0
self.count = 0
self.time = time
+
morning_counts = Counts("morning")
afternoon_counts = Counts("afternoon")
evening_counts = Counts("evening")
- for (time, _) in pv_test66.state._energy_production_forecast_kWh.items():
+ for time, _ in pv_test66.state._energy_production_forecast_kWh.items():
if time < morning_time:
morning_counts.total += 1
- morning_counts.count = morning_counts.count + 1 \
- if pv_test66.state._energy_production_forecast_kWh[time] == 0 \
+ morning_counts.count = (
+ morning_counts.count + 1
+ if pv_test66.state._energy_production_forecast_kWh[time] == 0
else morning_counts.count
+ )
elif morning_time < time < afternoon_time:
afternoon_counts.total += 1
- afternoon_counts.count = afternoon_counts.count + 1 \
- if pv_test66.state._energy_production_forecast_kWh[time] > 0.001 \
+ afternoon_counts.count = (
+ afternoon_counts.count + 1
+ if pv_test66.state._energy_production_forecast_kWh[time] > 0.001
else afternoon_counts.count
+ )
elif time > afternoon_time:
evening_counts.total += 1
- evening_counts.count = evening_counts.count + 1 \
- if pv_test66.state._energy_production_forecast_kWh[time] == 0 \
+ evening_counts.count = (
+ evening_counts.count + 1
+ if pv_test66.state._energy_production_forecast_kWh[time] == 0
else evening_counts.count
+ )
total_count = morning_counts.total + afternoon_counts.total + evening_counts.total
assert len(list(pv_test66.state._energy_production_forecast_kWh.items())) == total_count
@@ -343,12 +371,17 @@ def test_does_not_offer_sold_energy_again(pv_test6, market_test3):
# pylint: disable = attribute-defined-outside-init
pv_test6.event_activate()
pv_test6.event_market_cycle()
- assert market_test3.created_offers[0].energy == \
- pv_test6.state._energy_production_forecast_kWh[TIME]
+ assert (
+ market_test3.created_offers[0].energy
+ == pv_test6.state._energy_production_forecast_kWh[TIME]
+ )
fake_trade = FakeTrade(market_test3.created_offers[0])
fake_trade.seller = TraderDetails(
- pv_test6.owner.name, fake_trade.seller.uuid,
- fake_trade.seller.origin, fake_trade.seller.origin_uuid)
+ pv_test6.owner.name,
+ fake_trade.seller.uuid,
+ fake_trade.seller.origin,
+ fake_trade.seller.origin_uuid,
+ )
fake_trade.time_slot = market_test3.time_slot
pv_test6.event_offer_traded(market_id=market_test3.id, trade=fake_trade)
market_test3.created_offers = []
@@ -396,8 +429,8 @@ def test_correct_interpolation_power_profile():
profile_path = pathlib.Path(gsye_root_path + "/resources/Solar_Curve_W_sunny.csv")
profile = read_arbitrary_profile(InputProfileTypes.POWER_W, str(profile_path))
times = list(profile)
- for ii in range(len(times)-1):
- assert abs((times[ii]-times[ii+1]).in_seconds()) == slot_length * 60
+ for ii in range(len(times) - 1):
+ assert abs((times[ii] - times[ii + 1]).in_seconds()) == slot_length * 60
GlobalConfig.slot_length = original_slot_length
@@ -418,15 +451,18 @@ def test_pv_user_profile_constructor_rejects_incorrect_parameters():
with pytest.raises(GSyDeviceException):
PVUserProfileStrategy(power_profile=user_profile_path, panel_count=-1)
with pytest.raises(GSyDeviceException):
- pv = PVUserProfileStrategy(power_profile=user_profile_path,
- initial_selling_rate=5, final_selling_rate=15)
+ pv = PVUserProfileStrategy(
+ power_profile=user_profile_path, initial_selling_rate=5, final_selling_rate=15
+ )
pv.event_activate()
with pytest.raises(GSyDeviceException):
- PVUserProfileStrategy(power_profile=user_profile_path,
- fit_to_limit=True, energy_rate_decrease_per_update=1)
+ PVUserProfileStrategy(
+ power_profile=user_profile_path, fit_to_limit=True, energy_rate_decrease_per_update=1
+ )
with pytest.raises(GSyDeviceException):
- PVUserProfileStrategy(power_profile=user_profile_path,
- fit_to_limit=False, energy_rate_decrease_per_update=-1)
+ PVUserProfileStrategy(
+ power_profile=user_profile_path, fit_to_limit=False, energy_rate_decrease_per_update=-1
+ )
@pytest.mark.parametrize("is_canary", [False, True])
diff --git a/tests/strategies/test_strategy_storage.py b/tests/strategies/test_strategy_storage.py
index a9054097a..4ebd7904b 100644
--- a/tests/strategies/test_strategy_storage.py
+++ b/tests/strategies/test_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 .
"""
+
import logging
from copy import deepcopy
from logging import getLogger
@@ -22,14 +23,18 @@
from uuid import uuid4
import pytest
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import (
+ ConstSettings,
+ GlobalConfig,
+ TIME_FORMAT,
+ FLOATING_POINT_TOLERANCE,
+ TIME_ZONE,
+)
from gsy_framework.data_classes import Offer, Trade, BalancingOffer, Bid, TraderDetails
from gsy_framework.exceptions import GSyDeviceException
from gsy_framework.read_user_profile import read_arbitrary_profile, InputProfileTypes
from pendulum import Duration, DateTime, now
-from gsy_e.constants import TIME_FORMAT, FLOATING_POINT_TOLERANCE
-from gsy_e.constants import TIME_ZONE
from gsy_e.gsy_e_core.device_registry import DeviceRegistry
from gsy_e.gsy_e_core.util import change_global_config
from gsy_e.models.config import SimulationConfig
@@ -93,7 +98,7 @@ def cheapest_offers(self):
[Offer("id", now(), 12, 0.4, TraderDetails("A", ""))],
[Offer("id", now(), 20, 1, TraderDetails("A", ""))],
[Offer("id", now(), 20, 5.1, TraderDetails("A", ""))],
- [Offer("id", now(), 20, 5.1, TraderDetails("A", ""))]
+ [Offer("id", now(), 20, 5.1, TraderDetails("A", ""))],
]
return offers[self.count]
@@ -104,7 +109,7 @@ def past_markets(self):
@property
def now(self):
return DateTime.now(tz=TIME_ZONE).start_of("day") + (
- self.config.tick_length * self.current_tick
+ self.config.tick_length * self.current_tick
)
@property
@@ -118,7 +123,7 @@ def config(self):
slot_length=Duration(minutes=15),
tick_length=Duration(seconds=15),
market_maker_rate=ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE,
- external_connection_enabled=False
+ external_connection_enabled=False,
)
change_global_config(**configuration.__dict__)
return configuration
@@ -128,15 +133,23 @@ class FakeMarket:
def __init__(self, count):
self.count = count
self.id = str(count)
- self.trade = Trade("id", now(), TraderDetails("FakeArea", ""),
- TraderDetails("buyer", ""), time_slot=self.time_slot,
- offer=Offer("id", now(), 11.8, 0.5, TraderDetails("FakeArea", "")),
- traded_energy=0.5, trade_price=11.8)
+ self.trade = Trade(
+ "id",
+ now(),
+ TraderDetails("FakeArea", ""),
+ TraderDetails("buyer", ""),
+ time_slot=self.time_slot,
+ offer=Offer("id", now(), 11.8, 0.5, TraderDetails("FakeArea", "")),
+ traded_energy=0.5,
+ trade_price=11.8,
+ )
self.created_offers = []
- self.offers = {"id": Offer("id", now(), 11.8, 0.5, TraderDetails("FakeArea", "")),
- "id2": Offer("id2", now(), 20, 0.5, TraderDetails("A", "")),
- "id3": Offer("id3", now(), 20, 1, TraderDetails("A", "")),
- "id4": Offer("id4", now(), 19, 5.1, TraderDetails("A", ""))}
+ self.offers = {
+ "id": Offer("id", now(), 11.8, 0.5, TraderDetails("FakeArea", "")),
+ "id2": Offer("id2", now(), 20, 0.5, TraderDetails("A", "")),
+ "id3": Offer("id3", now(), 20, 1, TraderDetails("A", "")),
+ "id4": Offer("id4", now(), 19, 5.1, TraderDetails("A", "")),
+ }
self.bids = {}
self.created_balancing_offers = []
@@ -146,7 +159,7 @@ def sorted_offers(self):
[Offer("id", now(), 11.8, 0.5, TraderDetails("A", ""))],
[Offer("id2", now(), 20, 0.5, TraderDetails("A", ""))],
[Offer("id3", now(), 20, 1, TraderDetails("A", ""))],
- [Offer("id4", now(), 19, 5.1, TraderDetails("A", ""))]
+ [Offer("id4", now(), 19, 5.1, TraderDetails("A", ""))],
]
return offers[self.count]
@@ -184,6 +197,7 @@ def bid(self, price, energy, buyer, market=None, original_price=None):
# Test if storage buys cheap energy
+
@pytest.fixture()
def area_test1():
return FakeArea(0)
@@ -191,9 +205,13 @@ def area_test1():
@pytest.fixture()
def storage_strategy_test1(area_test1, called):
- s = StorageStrategy(max_abs_battery_power_kW=2.01,
- initial_buying_rate=23.6, final_buying_rate=23.6,
- initial_selling_rate=23.7, final_selling_rate=23.7)
+ s = StorageStrategy(
+ max_abs_battery_power_kW=2.01,
+ initial_buying_rate=23.6,
+ final_buying_rate=23.6,
+ initial_selling_rate=23.7,
+ final_selling_rate=23.7,
+ )
s.owner = area_test1
s.area = area_test1
s.accept_offer = called
@@ -207,7 +225,8 @@ def test_if_storage_buys_cheap_energy(storage_strategy_test1, area_test1):
area_test1.current_tick += 310
storage_strategy_test1.event_tick()
assert storage_strategy_test1.accept_offer.calls[0][0][1] == repr(
- FakeMarket(0).sorted_offers[0])
+ FakeMarket(0).sorted_offers[0]
+ )
"""TEST2"""
@@ -215,6 +234,7 @@ def test_if_storage_buys_cheap_energy(storage_strategy_test1, area_test1):
# Test if storage doesn't buy energy for more than 30ct
+
@pytest.fixture()
def area_test2():
return FakeArea(1)
@@ -238,14 +258,14 @@ def test_if_storage_doesnt_buy_30ct(storage_strategy_test2, area_test2):
def test_if_storage_doesnt_buy_above_break_even_point(storage_strategy_test2, area_test2):
storage_strategy_test2.event_activate()
storage_strategy_test2.break_even_buy = 10.0
- area_test2.current_market.offers = {"id": Offer("id", now(), 10.1, 1,
- TraderDetails("FakeArea", ""),
- 10.1)}
+ area_test2.current_market.offers = {
+ "id": Offer("id", now(), 10.1, 1, TraderDetails("FakeArea", ""), 10.1)
+ }
storage_strategy_test2.event_tick()
assert len(storage_strategy_test2.accept_offer.calls) == 0
- area_test2.current_market.offers = {"id": Offer("id", now(), 9.9, 1,
- TraderDetails("FakeArea", ""),
- 9.9)}
+ area_test2.current_market.offers = {
+ "id": Offer("id", now(), 9.9, 1, TraderDetails("FakeArea", ""), 9.9)
+ }
storage_strategy_test2.event_tick()
assert len(storage_strategy_test2.accept_offer.calls) == 0
@@ -256,6 +276,7 @@ def test_if_storage_doesnt_buy_above_break_even_point(storage_strategy_test2, ar
# Test if storage doesn't buy for over avg price
+
@pytest.fixture()
def area_test3():
return FakeArea(2)
@@ -271,10 +292,12 @@ def storage_strategy_test3(area_test3, called):
def test_if_storage_doesnt_buy_too_expensive(storage_strategy_test3, area_test3):
- storage_strategy_test3.bid_update.initial_rate = \
- read_arbitrary_profile(InputProfileTypes.IDENTITY, 0)
- storage_strategy_test3.bid_update.final_rate = \
- read_arbitrary_profile(InputProfileTypes.IDENTITY, 1)
+ storage_strategy_test3.bid_update.initial_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, 0
+ )
+ storage_strategy_test3.bid_update.final_rate = read_arbitrary_profile(
+ InputProfileTypes.IDENTITY, 1
+ )
storage_strategy_test3.event_activate()
storage_strategy_test3.event_tick()
assert len(storage_strategy_test3.accept_offer.calls) == 0
@@ -309,8 +332,7 @@ def area_test4():
@pytest.fixture()
def storage_strategy_test4(area_test4, called):
- s = StorageStrategy(initial_soc=100,
- battery_capacity_kWh=2.1)
+ s = StorageStrategy(initial_soc=100, battery_capacity_kWh=2.1)
s.owner = area_test4
s.area = area_test4
s.accept_offer = called
@@ -328,6 +350,7 @@ def test_if_storage_pays_respect_to_capacity_limits(storage_strategy_test4, area
# Test if internal storage is handled correctly
+
@pytest.fixture()
def area_test5():
return FakeArea(4)
@@ -342,7 +365,7 @@ def storage_strategy_test5(area_test5, called):
area_test5.past_market.offers = {
"id": Offer("id", now(), 20, 1, TraderDetails("A", "")),
"id2": Offer("id2", now(), 20, 3, TraderDetails("FakeArea", "")),
- "id3": Offer("id3", now(), 100, 1, TraderDetails("FakeArea", ""))
+ "id3": Offer("id3", now(), 100, 1, TraderDetails("FakeArea", "")),
}
s.offers.bought_offer(area_test5.past_market.offers["id"], area_test5.past_market.id)
@@ -365,6 +388,7 @@ def test_if_storage_handles_capacity_correctly(storage_strategy_test5, area_test
# Test if trades are handled correctly
+
@pytest.fixture()
def area_test6():
return FakeArea(0)
@@ -387,13 +411,17 @@ def storage_strategy_test6(area_test6, market_test6, called):
def test_if_trades_are_handled_correctly(storage_strategy_test6, market_test6):
- storage_strategy_test6.area.get_future_market_from_id = (
- lambda _id: market_test6 if _id == market_test6.id else None)
+ storage_strategy_test6.area.get_future_market_from_id = lambda _id: (
+ market_test6 if _id == market_test6.id else None
+ )
storage_strategy_test6.state.add_default_values_to_state_profiles(
- [storage_strategy_test6.spot_market_time_slot])
+ [storage_strategy_test6.spot_market_time_slot]
+ )
storage_strategy_test6.event_offer_traded(market_id=market_test6.id, trade=market_test6.trade)
- assert (market_test6.trade.match_details["offer"] in
- storage_strategy_test6.offers.sold[market_test6.id])
+ assert (
+ market_test6.trade.match_details["offer"]
+ in storage_strategy_test6.offers.sold[market_test6.id]
+ )
assert market_test6.trade.match_details["offer"] not in storage_strategy_test6.offers.open
@@ -408,10 +436,15 @@ def area_test7():
@pytest.fixture()
def storage_strategy_test7(area_test7):
- s = StorageStrategy(initial_soc=99.667, battery_capacity_kWh=3.01,
- max_abs_battery_power_kW=5.21, initial_buying_rate=31,
- final_buying_rate=31, initial_selling_rate=32,
- final_selling_rate=32)
+ s = StorageStrategy(
+ initial_soc=99.667,
+ battery_capacity_kWh=3.01,
+ max_abs_battery_power_kW=5.21,
+ initial_buying_rate=31,
+ final_buying_rate=31,
+ initial_selling_rate=32,
+ final_selling_rate=32,
+ )
s.owner = area_test7
s.area = area_test7
return s
@@ -420,12 +453,16 @@ def storage_strategy_test7(area_test7):
def test_sell_energy_function(storage_strategy_test7, area_test7: FakeArea):
storage_strategy_test7.event_activate()
sell_market = area_test7.spot_market
- energy_sell_dict = \
- storage_strategy_test7.state._clamp_energy_to_sell_kWh([sell_market.time_slot])
+ energy_sell_dict = storage_strategy_test7.state._clamp_energy_to_sell_kWh(
+ [sell_market.time_slot]
+ )
storage_strategy_test7.event_market_cycle()
- assert (isclose(storage_strategy_test7.state.offered_sell_kWh[sell_market.time_slot],
- energy_sell_dict[sell_market.time_slot], rel_tol=1e-03))
- assert (isclose(storage_strategy_test7.state.used_storage, 3.0, rel_tol=1e-03))
+ assert isclose(
+ storage_strategy_test7.state.offered_sell_kWh[sell_market.time_slot],
+ energy_sell_dict[sell_market.time_slot],
+ rel_tol=1e-03,
+ )
+ assert isclose(storage_strategy_test7.state.used_storage, 3.0, rel_tol=1e-03)
assert len(storage_strategy_test7.offers.posted_in_market(sell_market.id)) > 0
@@ -434,15 +471,20 @@ def test_calculate_sell_energy_rate_lower_bound(storage_strategy_test7):
storage_strategy_test7.event_activate()
market = storage_strategy_test7.area.current_market
final_selling_rate = storage_strategy_test7.offer_update.final_rate
- assert (isclose(storage_strategy_test7.calculate_selling_rate(market),
- final_selling_rate[market.time_slot]))
+ assert isclose(
+ storage_strategy_test7.calculate_selling_rate(market), final_selling_rate[market.time_slot]
+ )
@pytest.fixture()
def storage_strategy_test7_1(area_test7):
- s = StorageStrategy(initial_soc=99.67, battery_capacity_kWh=3.01,
- max_abs_battery_power_kW=5.21, final_buying_rate=26,
- final_selling_rate=27)
+ s = StorageStrategy(
+ initial_soc=99.67,
+ battery_capacity_kWh=3.01,
+ max_abs_battery_power_kW=5.21,
+ final_buying_rate=26,
+ final_selling_rate=27,
+ )
s.owner = area_test7
s.area = area_test7
return s
@@ -451,37 +493,46 @@ def storage_strategy_test7_1(area_test7):
def test_calculate_initial_sell_energy_rate_upper_bound(storage_strategy_test7_1):
storage_strategy_test7_1.event_activate()
market = storage_strategy_test7_1.area.current_market
- market_maker_rate = \
- storage_strategy_test7_1.simulation_config.market_maker_rate[market.time_slot]
+ market_maker_rate = storage_strategy_test7_1.simulation_config.market_maker_rate[
+ market.time_slot
+ ]
assert storage_strategy_test7_1.calculate_selling_rate(market) == market_maker_rate
@pytest.fixture()
def storage_strategy_test7_3(area_test7):
- s = StorageStrategy(initial_soc=19.96, battery_capacity_kWh=5.01,
- max_abs_battery_power_kW=5.21, final_selling_rate=17,
- initial_buying_rate=15, final_buying_rate=16)
+ s = StorageStrategy(
+ initial_soc=19.96,
+ battery_capacity_kWh=5.01,
+ max_abs_battery_power_kW=5.21,
+ final_selling_rate=17,
+ initial_buying_rate=15,
+ final_buying_rate=16,
+ )
s.owner = area_test7
s.area = area_test7
- s.offers.posted = {Offer("id", now(),
- 30, 1, TraderDetails("FakeArea", "")): area_test7.current_market.id}
+ s.offers.posted = {
+ Offer("id", now(), 30, 1, TraderDetails("FakeArea", "")): area_test7.current_market.id
+ }
s.market = area_test7.current_market
return s
-def test_calculate_energy_amount_to_sell_respects_min_allowed_soc(storage_strategy_test7_3,
- area_test7):
+def test_calculate_energy_amount_to_sell_respects_min_allowed_soc(
+ storage_strategy_test7_3, area_test7
+):
storage_strategy_test7_3.event_activate()
time_slot = area_test7.current_market.time_slot
- energy_sell_dict = storage_strategy_test7_3.state._clamp_energy_to_sell_kWh(
- [time_slot])
- target_energy = (storage_strategy_test7_3.state.used_storage
- - storage_strategy_test7_3.state.pledged_sell_kWh[time_slot]
- - storage_strategy_test7_3.state.offered_sell_kWh[time_slot]
- - storage_strategy_test7_3.state.capacity
- * storage_strategy_test7_3.state.min_allowed_soc_ratio)
+ energy_sell_dict = storage_strategy_test7_3.state._clamp_energy_to_sell_kWh([time_slot])
+ target_energy = (
+ storage_strategy_test7_3.state.used_storage
+ - storage_strategy_test7_3.state.pledged_sell_kWh[time_slot]
+ - storage_strategy_test7_3.state.offered_sell_kWh[time_slot]
+ - storage_strategy_test7_3.state.capacity
+ * storage_strategy_test7_3.state.min_allowed_soc_ratio
+ )
- assert (isclose(energy_sell_dict[time_slot], target_energy, rel_tol=1e-03))
+ assert isclose(energy_sell_dict[time_slot], target_energy, rel_tol=1e-03)
def test_clamp_energy_to_buy(storage_strategy_test7_3):
@@ -489,8 +540,10 @@ def test_clamp_energy_to_buy(storage_strategy_test7_3):
storage_strategy_test7_3.state._battery_energy_per_slot = 0.5
time_slot = storage_strategy_test7_3.market.time_slot
storage_strategy_test7_3.state._clamp_energy_to_buy_kWh([time_slot])
- assert storage_strategy_test7_3.state.energy_to_buy_dict[time_slot] == \
- storage_strategy_test7_3.state._battery_energy_per_slot
+ assert (
+ storage_strategy_test7_3.state.energy_to_buy_dict[time_slot]
+ == storage_strategy_test7_3.state._battery_energy_per_slot
+ )
# Reduce used storage below battery_energy_per_slot
@@ -511,8 +564,7 @@ def area_test8():
@pytest.fixture()
def storage_strategy_test8(area_test8):
- s = StorageStrategy(initial_soc=99, battery_capacity_kWh=101,
- max_abs_battery_power_kW=401)
+ s = StorageStrategy(initial_soc=99, battery_capacity_kWh=101, max_abs_battery_power_kW=401)
s.owner = area_test8
s.area = area_test8
return s
@@ -522,37 +574,51 @@ def test_sell_energy_function_with_stored_capacity(storage_strategy_test8, area_
storage_strategy_test8.event_activate()
storage_strategy_test8.event_market_cycle()
sell_market = area_test8.spot_market
- assert abs(storage_strategy_test8.state.used_storage
- - storage_strategy_test8.state.offered_sell_kWh[sell_market.time_slot] -
- storage_strategy_test8.state.capacity *
- storage_strategy_test8.state.min_allowed_soc_ratio) < FLOATING_POINT_TOLERANCE
- assert (isclose(storage_strategy_test8.state.offered_sell_kWh[sell_market.time_slot],
- 100 - storage_strategy_test8.state.capacity *
- storage_strategy_test8.state.min_allowed_soc_ratio, rel_tol=1e-02))
-
- assert (isclose(area_test8.spot_market.created_offers[0].energy,
- 100 - storage_strategy_test8.state.capacity *
- storage_strategy_test8.state.min_allowed_soc_ratio, rel_tol=1e-02))
- assert len(storage_strategy_test8.offers.posted_in_market(
- area_test8.spot_market.id)
- ) > 0
+ assert (
+ abs(
+ storage_strategy_test8.state.used_storage
+ - storage_strategy_test8.state.offered_sell_kWh[sell_market.time_slot]
+ - storage_strategy_test8.state.capacity
+ * storage_strategy_test8.state.min_allowed_soc_ratio
+ )
+ < FLOATING_POINT_TOLERANCE
+ )
+ assert isclose(
+ storage_strategy_test8.state.offered_sell_kWh[sell_market.time_slot],
+ 100
+ - storage_strategy_test8.state.capacity
+ * storage_strategy_test8.state.min_allowed_soc_ratio,
+ rel_tol=1e-02,
+ )
+
+ assert isclose(
+ area_test8.spot_market.created_offers[0].energy,
+ 100
+ - storage_strategy_test8.state.capacity
+ * storage_strategy_test8.state.min_allowed_soc_ratio,
+ rel_tol=1e-02,
+ )
+ assert len(storage_strategy_test8.offers.posted_in_market(area_test8.spot_market.id)) > 0
"""TEST9"""
# Test if initial capacity is sold
-def test_first_market_cycle_with_initial_capacity(storage_strategy_test8: StorageStrategy,
- area_test8: FakeArea):
+def test_first_market_cycle_with_initial_capacity(
+ storage_strategy_test8: StorageStrategy, area_test8: FakeArea
+):
storage_strategy_test8.event_activate()
storage_strategy_test8.event_market_cycle()
sell_market = area_test8.spot_market
- assert (isclose(storage_strategy_test8.state.offered_sell_kWh[sell_market.time_slot],
- 100.0 - storage_strategy_test8.state.capacity *
- storage_strategy_test8.state.min_allowed_soc_ratio, rel_tol=1e-02))
- assert len(storage_strategy_test8.offers.posted_in_market(
- area_test8.spot_market.id)
- ) > 0
+ assert isclose(
+ storage_strategy_test8.state.offered_sell_kWh[sell_market.time_slot],
+ 100.0
+ - storage_strategy_test8.state.capacity
+ * storage_strategy_test8.state.min_allowed_soc_ratio,
+ rel_tol=1e-02,
+ )
+ assert len(storage_strategy_test8.offers.posted_in_market(area_test8.spot_market.id)) > 0
"""TEST10"""
@@ -604,11 +670,13 @@ def test_free_storage_calculation_takes_into_account_storage_capacity(storage_st
storage_strategy_test1.state.offered_buy_kWh[time_slot] = 14.0
storage_strategy_test1.state.capacity = capacity
- assert storage_strategy_test1.state.free_storage(time_slot) == \
- storage_strategy_test1.state.capacity \
- + storage_strategy_test1.state.pledged_sell_kWh[time_slot] \
- - storage_strategy_test1.state.pledged_buy_kWh[time_slot] \
- - storage_strategy_test1.state.used_storage
+ assert (
+ storage_strategy_test1.state.free_storage(time_slot)
+ == storage_strategy_test1.state.capacity
+ + storage_strategy_test1.state.pledged_sell_kWh[time_slot]
+ - storage_strategy_test1.state.pledged_buy_kWh[time_slot]
+ - storage_strategy_test1.state.used_storage
+ )
"""TEST11"""
@@ -621,9 +689,15 @@ def area_test11():
@pytest.fixture()
def storage_strategy_test11(area_test11, called):
- s = StorageStrategy(battery_capacity_kWh=100, initial_soc=50,
- max_abs_battery_power_kW=1, initial_buying_rate=30,
- final_buying_rate=30, initial_selling_rate=33, final_selling_rate=32)
+ s = StorageStrategy(
+ battery_capacity_kWh=100,
+ initial_soc=50,
+ max_abs_battery_power_kW=1,
+ initial_buying_rate=30,
+ final_buying_rate=30,
+ initial_selling_rate=33,
+ final_selling_rate=32,
+ )
s.owner = area_test11
s.area = area_test11
s.accept_offer = called
@@ -631,8 +705,9 @@ def storage_strategy_test11(area_test11, called):
return s
-def test_storage_buys_partial_offer_and_respecting_battery_power(storage_strategy_test11,
- area_test11):
+def test_storage_buys_partial_offer_and_respecting_battery_power(
+ storage_strategy_test11, area_test11
+):
storage_strategy_test11.event_activate()
buy_market = area_test11.spot_market
storage_strategy_test11.event_tick()
@@ -642,7 +717,7 @@ def test_storage_buys_partial_offer_and_respecting_battery_power(storage_strateg
# storage should not be able to buy energy after this tick because
# self.state._battery_energy_per_slot is exceeded
te = storage_strategy_test11.state.energy_to_buy_dict[buy_market.time_slot]
- assert te == 0.
+ assert te == 0.0
assert len(storage_strategy_test11.accept_offer.calls) >= 1
@@ -653,10 +728,14 @@ def test_has_battery_reached_max_power(storage_strategy_test11):
storage_strategy_test11.state.offered_sell_kWh[time_slot] = 5
storage_strategy_test11.state.pledged_buy_kWh[time_slot] = 5
storage_strategy_test11.state.offered_buy_kWh[time_slot] = 5
- assert storage_strategy_test11.state._has_battery_reached_max_discharge_power(
- 1, time_slot) is True
- assert storage_strategy_test11.state._has_battery_reached_max_discharge_power(
- 0.25, time_slot) is False
+ assert (
+ storage_strategy_test11.state._has_battery_reached_max_discharge_power(1, time_slot)
+ is True
+ )
+ assert (
+ storage_strategy_test11.state._has_battery_reached_max_discharge_power(0.25, time_slot)
+ is False
+ )
"""TEST12"""
@@ -674,8 +753,9 @@ def market_test7():
@pytest.fixture()
def storage_strategy_test12(area_test12):
- s = StorageStrategy(battery_capacity_kWh=5, max_abs_battery_power_kW=5,
- cap_price_strategy=True)
+ s = StorageStrategy(
+ battery_capacity_kWh=5, max_abs_battery_power_kW=5, cap_price_strategy=True
+ )
s.owner = area_test12
s.area = area_test12
return s
@@ -708,9 +788,14 @@ def market_test13():
@pytest.fixture()
def storage_strategy_test13(area_test13, called):
- s = StorageStrategy(battery_capacity_kWh=5, max_abs_battery_power_kW=5,
- initial_selling_rate=35.1, final_selling_rate=35,
- initial_buying_rate=34, final_buying_rate=34)
+ s = StorageStrategy(
+ battery_capacity_kWh=5,
+ max_abs_battery_power_kW=5,
+ initial_selling_rate=35.1,
+ final_selling_rate=35,
+ initial_buying_rate=34,
+ final_buying_rate=34,
+ )
s.owner = area_test13
s.area = area_test13
s.accept_offer = called
@@ -719,17 +804,24 @@ def storage_strategy_test13(area_test13, called):
def test_storage_event_trade(storage_strategy_test11, market_test13):
storage_strategy_test11.state.add_default_values_to_state_profiles(
- [storage_strategy_test11.spot_market_time_slot])
- storage_strategy_test11.event_offer_traded(market_id=market_test13.id,
- trade=market_test13.trade)
- assert storage_strategy_test11.state.pledged_sell_kWh[market_test13.time_slot] == \
- market_test13.trade.traded_energy
- assert storage_strategy_test11.state.offered_sell_kWh[
- market_test13.time_slot] == -market_test13.trade.traded_energy
+ [storage_strategy_test11.spot_market_time_slot]
+ )
+ storage_strategy_test11.event_offer_traded(
+ market_id=market_test13.id, trade=market_test13.trade
+ )
+ assert (
+ storage_strategy_test11.state.pledged_sell_kWh[market_test13.time_slot]
+ == market_test13.trade.traded_energy
+ )
+ assert (
+ storage_strategy_test11.state.offered_sell_kWh[market_test13.time_slot]
+ == -market_test13.trade.traded_energy
+ )
def test_balancing_offers_are_not_created_if_device_not_in_registry(
- storage_strategy_test13, area_test13):
+ storage_strategy_test13, area_test13
+):
DeviceRegistry.REGISTRY = {}
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True
storage_strategy_test13.event_activate()
@@ -739,8 +831,7 @@ def test_balancing_offers_are_not_created_if_device_not_in_registry(
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = False
-def test_balancing_offers_are_created_if_device_in_registry(
- storage_strategy_test13, area_test13):
+def test_balancing_offers_are_created_if_device_in_registry(storage_strategy_test13, area_test13):
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = True
DeviceRegistry.REGISTRY = {"FakeArea": (30, 40)}
storage_strategy_test13.event_activate()
@@ -748,24 +839,31 @@ def test_balancing_offers_are_created_if_device_in_registry(
storage_strategy_test13.event_balancing_market_cycle()
storage_slot_market = storage_strategy_test13.area.spot_market
assert len(area_test13.test_balancing_market.created_balancing_offers) == 2
- actual_balancing_demand_energy = \
- area_test13.test_balancing_market.created_balancing_offers[0].energy
-
- expected_balancing_demand_energy = \
- -1 * storage_strategy_test13.balancing_energy_ratio.demand * \
- storage_strategy_test13.state.free_storage(storage_slot_market.time_slot)
+ actual_balancing_demand_energy = area_test13.test_balancing_market.created_balancing_offers[
+ 0
+ ].energy
+
+ expected_balancing_demand_energy = (
+ -1
+ * storage_strategy_test13.balancing_energy_ratio.demand
+ * storage_strategy_test13.state.free_storage(storage_slot_market.time_slot)
+ )
assert actual_balancing_demand_energy == expected_balancing_demand_energy
- actual_balancing_demand_price = \
- area_test13.test_balancing_market.created_balancing_offers[0].price
+ actual_balancing_demand_price = area_test13.test_balancing_market.created_balancing_offers[
+ 0
+ ].price
assert actual_balancing_demand_price == abs(expected_balancing_demand_energy) * 30
- actual_balancing_supply_energy = \
- area_test13.test_balancing_market.created_balancing_offers[1].energy
- expected_balancing_supply_energy = \
- storage_strategy_test13.state.used_storage * \
- storage_strategy_test13.balancing_energy_ratio.supply
+ actual_balancing_supply_energy = area_test13.test_balancing_market.created_balancing_offers[
+ 1
+ ].energy
+ expected_balancing_supply_energy = (
+ storage_strategy_test13.state.used_storage
+ * storage_strategy_test13.balancing_energy_ratio.supply
+ )
assert actual_balancing_supply_energy == expected_balancing_supply_energy
- actual_balancing_supply_price = \
- area_test13.test_balancing_market.created_balancing_offers[1].price
+ actual_balancing_supply_price = area_test13.test_balancing_market.created_balancing_offers[
+ 1
+ ].price
assert actual_balancing_supply_price == expected_balancing_supply_energy * 40
DeviceRegistry.REGISTRY = {}
ConstSettings.BalancingSettings.ENABLE_BALANCING_MARKET = False
@@ -786,9 +884,15 @@ def market_test14():
@pytest.fixture()
def storage_strategy_test14(area_test14, called):
- s = StorageStrategy(initial_soc=50, battery_capacity_kWh=30,
- max_abs_battery_power_kW=10, initial_selling_rate=25,
- final_selling_rate=24, initial_buying_rate=0, final_buying_rate=23.9)
+ s = StorageStrategy(
+ initial_soc=50,
+ battery_capacity_kWh=30,
+ max_abs_battery_power_kW=10,
+ initial_selling_rate=25,
+ final_selling_rate=24,
+ initial_buying_rate=0,
+ final_buying_rate=23.9,
+ )
s.owner = area_test14
s.area = area_test14
s.accept_offer = called
@@ -817,10 +921,15 @@ def market_test15():
@pytest.fixture()
def storage_strategy_test15(area_test15, called):
- s = StorageStrategy(initial_soc=50, battery_capacity_kWh=30,
- max_abs_battery_power_kW=10, initial_selling_rate=25,
- final_selling_rate=25, initial_buying_rate=24,
- final_buying_rate=24)
+ s = StorageStrategy(
+ initial_soc=50,
+ battery_capacity_kWh=30,
+ max_abs_battery_power_kW=10,
+ initial_selling_rate=25,
+ final_selling_rate=25,
+ initial_buying_rate=24,
+ final_buying_rate=24,
+ )
s.owner = area_test15
s.area = deepcopy(area_test15)
s.area.parent = deepcopy(area_test15)
@@ -836,55 +945,86 @@ def test_energy_origin(storage_strategy_test15, market_test15):
storage_strategy_test15.event_activate()
assert len(storage_strategy_test15.state._used_storage_share) == 1
assert storage_strategy_test15.state._used_storage_share[0] == EnergyOrigin(
- ESSEnergyOrigin.EXTERNAL, 15)
+ ESSEnergyOrigin.EXTERNAL, 15
+ )
# Validate that local energy origin is correctly registered
current_market = storage_strategy_test15.area.current_market
offer = Offer("id", now(), 20, 1.0, TraderDetails("OtherChildArea", ""))
storage_strategy_test15._try_to_buy_offer(offer, current_market, 21)
storage_strategy_test15.area.current_market.trade = Trade(
- "id", now(), TraderDetails("OtherChildArea", ""), TraderDetails("Storage", ""),
- offer=offer, traded_energy=1, trade_price=20)
- storage_strategy_test15.event_offer_traded(market_id=market_test15.id,
- trade=current_market.trade)
+ "id",
+ now(),
+ TraderDetails("OtherChildArea", ""),
+ TraderDetails("Storage", ""),
+ offer=offer,
+ traded_energy=1,
+ trade_price=20,
+ )
+ storage_strategy_test15.event_offer_traded(
+ market_id=market_test15.id, trade=current_market.trade
+ )
assert len(storage_strategy_test15.state._used_storage_share) == 2
- assert storage_strategy_test15.state._used_storage_share == [EnergyOrigin(
- ESSEnergyOrigin.EXTERNAL, 15), EnergyOrigin(ESSEnergyOrigin.LOCAL, 1)]
+ assert storage_strategy_test15.state._used_storage_share == [
+ EnergyOrigin(ESSEnergyOrigin.EXTERNAL, 15),
+ EnergyOrigin(ESSEnergyOrigin.LOCAL, 1),
+ ]
# Validate that local energy origin with the same seller / buyer is correctly registered
offer = Offer("id", now(), 20, 2.0, TraderDetails("Storage", ""))
storage_strategy_test15._try_to_buy_offer(offer, current_market, 21)
storage_strategy_test15.area.current_market.trade = Trade(
- "id", now(), TraderDetails("Storage", ""), TraderDetails("A", ""),
+ "id",
+ now(),
+ TraderDetails("Storage", ""),
+ TraderDetails("A", ""),
time_slot=current_market.time_slot,
- offer=offer, traded_energy=2, trade_price=20)
+ offer=offer,
+ traded_energy=2,
+ trade_price=20,
+ )
storage_strategy_test15.event_offer_traded(
- market_id=market_test15.id,
- trade=current_market.trade)
+ market_id=market_test15.id, trade=current_market.trade
+ )
assert len(storage_strategy_test15.state._used_storage_share) == 2
- assert storage_strategy_test15.state._used_storage_share == [EnergyOrigin(
- ESSEnergyOrigin.EXTERNAL, 13), EnergyOrigin(ESSEnergyOrigin.LOCAL, 1)]
+ assert storage_strategy_test15.state._used_storage_share == [
+ EnergyOrigin(ESSEnergyOrigin.EXTERNAL, 13),
+ EnergyOrigin(ESSEnergyOrigin.LOCAL, 1),
+ ]
# Validate that external energy origin is correctly registered
offer = Offer("id", now(), 20, 1.0, TraderDetails("FakeArea", ""))
storage_strategy_test15._try_to_buy_offer(offer, current_market, 21)
current_market.trade = Trade(
- "id", now(), TraderDetails("FakeArea", ""), TraderDetails("Storage", ""),
- offer=offer, traded_energy=1, trade_price=20)
+ "id",
+ now(),
+ TraderDetails("FakeArea", ""),
+ TraderDetails("Storage", ""),
+ offer=offer,
+ traded_energy=1,
+ trade_price=20,
+ )
storage_strategy_test15.event_offer_traded(
- market_id=market_test15.id,
- trade=current_market.trade)
+ market_id=market_test15.id, trade=current_market.trade
+ )
assert len(storage_strategy_test15.state._used_storage_share) == 3
- assert storage_strategy_test15.state._used_storage_share == [EnergyOrigin(
- ESSEnergyOrigin.EXTERNAL, 13.0), EnergyOrigin(ESSEnergyOrigin.LOCAL, 1.0),
- EnergyOrigin(ESSEnergyOrigin.EXTERNAL, 1.0)]
+ assert storage_strategy_test15.state._used_storage_share == [
+ EnergyOrigin(ESSEnergyOrigin.EXTERNAL, 13.0),
+ EnergyOrigin(ESSEnergyOrigin.LOCAL, 1.0),
+ EnergyOrigin(ESSEnergyOrigin.EXTERNAL, 1.0),
+ ]
def test_storage_strategy_increases_rate_when_fit_to_limit_is_false():
storage = StorageStrategy(
fit_to_limit=False,
- initial_selling_rate=30, final_selling_rate=25, energy_rate_decrease_per_update=1,
- initial_buying_rate=10, final_buying_rate=20, energy_rate_increase_per_update=1)
+ initial_selling_rate=30,
+ final_selling_rate=25,
+ energy_rate_decrease_per_update=1,
+ initial_buying_rate=10,
+ final_buying_rate=20,
+ energy_rate_increase_per_update=1,
+ )
storage.area = FakeArea(1)
storage.event_activate()
assert all([rate == -1 for rate in storage.bid_update.energy_rate_change_per_update.values()])
@@ -905,10 +1045,18 @@ def storage_test11(area_test3):
def test_assert_if_trade_rate_is_lower_than_offer_rate(storage_test11):
market_id = "market_id"
storage_test11.offers.sold[market_id] = [
- Offer("offer_id", now(), 30, 1, TraderDetails("FakeArea", ""))]
+ Offer("offer_id", now(), 30, 1, TraderDetails("FakeArea", ""))
+ ]
too_cheap_offer = Offer("offer_id", now(), 29, 1, TraderDetails("FakeArea", ""))
- trade = Trade("trade_id", now(), TraderDetails("FakeArea", ""), TraderDetails("buyer", ""),
- offer=too_cheap_offer, traded_energy=1, trade_price=29)
+ trade = Trade(
+ "trade_id",
+ now(),
+ TraderDetails("FakeArea", ""),
+ TraderDetails("buyer", ""),
+ offer=too_cheap_offer,
+ traded_energy=1,
+ trade_price=29,
+ )
with pytest.raises(AssertionError):
storage_test11.event_offer_traded(market_id=market_id, trade=trade)
@@ -918,10 +1066,18 @@ def test_assert_if_trade_rate_is_higher_than_bid_rate(storage_test11):
market_id = "2"
storage_test11.area.spot_market.id = market_id
storage_test11._bids[market_id] = [
- Bid("bid_id", now(), 30, 1, buyer=TraderDetails("FakeArea", ""))]
+ Bid("bid_id", now(), 30, 1, buyer=TraderDetails("FakeArea", ""))
+ ]
expensive_bid = Bid("bid_id", now(), 31, 1, buyer=TraderDetails("FakeArea", ""))
- trade = Trade("trade_id", now(), TraderDetails("FakeArea", ""), TraderDetails("FakeArea", ""),
- bid=expensive_bid, traded_energy=1, trade_price=31,
- time_slot=storage_test11.area.spot_market.time_slot)
+ trade = Trade(
+ "trade_id",
+ now(),
+ TraderDetails("FakeArea", ""),
+ TraderDetails("FakeArea", ""),
+ bid=expensive_bid,
+ traded_energy=1,
+ trade_price=31,
+ time_slot=storage_test11.area.spot_market.time_slot,
+ )
with pytest.raises(AssertionError):
storage_test11.event_offer_traded(market_id=market_id, trade=trade)
diff --git a/tests/strategies/test_virtual_heatpump.py b/tests/strategies/test_virtual_heatpump.py
index 85a31fee7..dc3b48465 100644
--- a/tests/strategies/test_virtual_heatpump.py
+++ b/tests/strategies/test_virtual_heatpump.py
@@ -1,10 +1,9 @@
from math import isclose
import pytest
-from gsy_framework.constants_limits import ConstSettings, GlobalConfig
+from gsy_framework.constants_limits import ConstSettings, GlobalConfig, FLOATING_POINT_TOLERANCE
from pendulum import DateTime, UTC
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
from gsy_e.models.area import Area
from gsy_e.models.strategy.virtual_heatpump import VirtualHeatpumpStrategy
from src.gsy_e.models.strategy.strategy_profile import global_objects
diff --git a/tests/test_balancing_agent.py b/tests/test_balancing_agent.py
index e766ab96c..ac052a52b 100644
--- a/tests/test_balancing_agent.py
+++ b/tests/test_balancing_agent.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
@@ -26,7 +27,7 @@
from gsy_framework.constants_limits import ConstSettings
from gsy_framework.data_classes import BalancingOffer, BalancingTrade, Offer, Trade, TraderDetails
-from gsy_e.constants import TIME_ZONE
+from gsy_framework.constants_limits import TIME_ZONE
from gsy_e.gsy_e_core.exceptions import InvalidBalancingTradeException
from gsy_e.models.strategy.market_agents.balancing_agent import BalancingAgent
from tests.test_market_agent import FakeMarket
@@ -71,22 +72,34 @@ def accept_offer(self, offer_or_id, buyer, energy=None, time=None, trade_rate: f
if abs(energy) < abs(offer.energy):
residual_energy = offer.energy - energy
- residual = BalancingOffer("res", pendulum.now(), offer.price, residual_energy,
- offer.seller)
+ residual = BalancingOffer(
+ "res", pendulum.now(), offer.price, residual_energy, offer.seller
+ )
traded = BalancingOffer(offer.id, pendulum.now(), offer.price, energy, offer.seller)
return BalancingTrade(
- "trade_id", time, traded.seller, buyer, residual=residual, offer=traded,
- traded_energy=1, trade_price=1)
+ "trade_id",
+ time,
+ traded.seller,
+ buyer,
+ residual=residual,
+ offer=traded,
+ traded_energy=1,
+ trade_price=1,
+ )
- return BalancingTrade("trade_id", time, offer.seller, buyer, offer=offer,
- traded_energy=1, trade_price=1)
+ return BalancingTrade(
+ "trade_id", time, offer.seller, buyer, offer=offer, traded_energy=1, trade_price=1
+ )
@pytest.fixture(name="balancing_agent")
def balancing_agent_fixture():
- lower_market = FakeBalancingMarket([
- BalancingOffer("id", pendulum.now(), 2, 2, TraderDetails("other", "")),
- BalancingOffer("id", pendulum.now(), 2, -2, TraderDetails("other", ""))])
+ lower_market = FakeBalancingMarket(
+ [
+ BalancingOffer("id", pendulum.now(), 2, 2, TraderDetails("other", "")),
+ BalancingOffer("id", pendulum.now(), 2, -2, TraderDetails("other", "")),
+ ]
+ )
higher_market = FakeBalancingMarket([])
owner = FakeArea("owner")
baa = BalancingAgent(owner=owner, lower_market=lower_market, higher_market=higher_market)
@@ -94,12 +107,15 @@ def balancing_agent_fixture():
def test_baa_event_trade(balancing_agent):
- trade = Trade("trade_id",
- balancing_agent.lower_market.time_slot,
- TraderDetails("someone_else", ""),
- TraderDetails("MA owner", ""),
- offer=Offer("A", pendulum.now(), 2, 2, TraderDetails("B", "")),
- traded_energy=1, trade_price=1)
+ trade = Trade(
+ "trade_id",
+ balancing_agent.lower_market.time_slot,
+ TraderDetails("someone_else", ""),
+ TraderDetails("MA owner", ""),
+ offer=Offer("A", pendulum.now(), 2, 2, TraderDetails("B", "")),
+ traded_energy=1,
+ trade_price=1,
+ )
fake_spot_market = FakeMarket([])
fake_spot_market.set_time_slot(balancing_agent.lower_market.time_slot)
balancing_agent.event_offer_traded(trade=trade, market_id=fake_spot_market.id)
@@ -110,9 +126,12 @@ def test_baa_event_trade(balancing_agent):
@pytest.fixture(name="balancing_agent_2")
def balancing_agent_2_fixture():
- lower_market = FakeBalancingMarket([
- BalancingOffer("id", pendulum.now(), 2, 0.2, TraderDetails("other", "")),
- BalancingOffer("id", pendulum.now(), 2, -0.2, TraderDetails("other", ""))])
+ lower_market = FakeBalancingMarket(
+ [
+ BalancingOffer("id", pendulum.now(), 2, 0.2, TraderDetails("other", "")),
+ BalancingOffer("id", pendulum.now(), 2, -0.2, TraderDetails("other", "")),
+ ]
+ )
higher_market = FakeBalancingMarket([])
owner = FakeArea("owner")
baa = BalancingAgent(owner=owner, lower_market=lower_market, higher_market=higher_market)
@@ -121,12 +140,15 @@ def balancing_agent_2_fixture():
def test_baa_unmatched_event_trade(balancing_agent_2):
- trade = Trade("trade_id",
- pendulum.now(tz=TIME_ZONE),
- TraderDetails("someone_else", ""),
- TraderDetails("owner", ""),
- offer=Offer("A", pendulum.now(), 2, 2, TraderDetails("B", "")),
- traded_energy=1, trade_price=1)
+ trade = Trade(
+ "trade_id",
+ pendulum.now(tz=TIME_ZONE),
+ TraderDetails("someone_else", ""),
+ TraderDetails("owner", ""),
+ offer=Offer("A", pendulum.now(), 2, 2, TraderDetails("B", "")),
+ traded_energy=1,
+ trade_price=1,
+ )
fake_spot_market = FakeMarket([])
fake_spot_market.set_time_slot(balancing_agent_2.lower_market.time_slot)
balancing_agent_2.owner.fake_spot_market = fake_spot_market
diff --git a/tests/test_global_objects.py b/tests/test_global_objects.py
index 48dab6b4d..915449322 100644
--- a/tests/test_global_objects.py
+++ b/tests/test_global_objects.py
@@ -15,13 +15,14 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from unittest.mock import MagicMock
from pendulum import duration, today
from gsy_framework.constants_limits import ConstSettings
from gsy_framework.enums import SpotMarketTypeEnum
-from gsy_e.constants import TIME_ZONE
+from gsy_framework.constants_limits import TIME_ZONE
from gsy_e.gsy_e_core.global_stats import ExternalConnectionGlobalStatistics
from gsy_e.gsy_e_core.redis_connections.area_market import ExternalConnectionCommunicator
from gsy_e.models.area import Area
@@ -39,23 +40,38 @@ def setup_method(self):
self.config = MagicMock(spec=SimulationConfig)
self.config.slot_length = duration(minutes=15)
self.config.tick_length = duration(seconds=15)
- self.config.ticks_per_slot = int(self.config.slot_length.seconds /
- self.config.tick_length.seconds)
+ self.config.ticks_per_slot = int(
+ self.config.slot_length.seconds / self.config.tick_length.seconds
+ )
self.config.start_date = today(tz=TIME_ZONE)
self.config.sim_duration = duration(days=1)
self.config.grid_fee_type = 1
self.config.end_date = self.config.start_date + self.config.sim_duration
self.config.capacity_kW = 1
- self.config.external_redis_communicator = \
- MagicMock(spec=ExternalConnectionCommunicator(True))
- self.storage = Area("Storage", strategy=StorageExternalStrategy(), config=self.config,
- external_connection_available=True)
- self.load = Area("Load", strategy=LoadHoursExternalStrategy(avg_power_W=100),
- config=self.config, external_connection_available=True)
- self.pv = Area("PV", strategy=PVExternalStrategy(), config=self.config,
- external_connection_available=True)
- self.house_area = Area("House", children=[self.storage, self.load, self.pv],
- config=self.config)
+ self.config.external_redis_communicator = MagicMock(
+ spec=ExternalConnectionCommunicator(True)
+ )
+ self.storage = Area(
+ "Storage",
+ strategy=StorageExternalStrategy(),
+ config=self.config,
+ external_connection_available=True,
+ )
+ self.load = Area(
+ "Load",
+ strategy=LoadHoursExternalStrategy(avg_power_W=100),
+ config=self.config,
+ external_connection_available=True,
+ )
+ self.pv = Area(
+ "PV",
+ strategy=PVExternalStrategy(),
+ config=self.config,
+ external_connection_available=True,
+ )
+ self.house_area = Area(
+ "House", children=[self.storage, self.load, self.pv], config=self.config
+ )
self.grid_area = Area("Grid", children=[self.house_area], config=self.config)
self.grid_area.activate()
@@ -69,60 +85,73 @@ def test_global_objects_area_stats_tree_dict_general_structure(self):
go.update()
expected_area_stats_tree_dict = {
- self.grid_area.uuid:
- {"last_market_bill": {"accumulated_trades": {}, "external_trades": {}},
- "last_market_stats": {"min_trade_rate": None, "max_trade_rate": None,
- "avg_trade_rate": None, "median_trade_rate": None,
- "total_traded_energy_kWh": None},
- "last_market_fee": 0.0,
- "current_market_fee": None,
- "area_name": "Grid",
- "children": {
- self.house_area.uuid: {
- "last_market_bill": {"accumulated_trades": {},
- "external_trades": {}},
- "last_market_stats": {"min_trade_rate": None,
- "max_trade_rate": None,
- "avg_trade_rate": None,
- "median_trade_rate": None,
- "total_traded_energy_kWh": None},
- "last_market_fee": 0.0,
- "current_market_fee": None,
- "area_name": "House",
- "children": {
- self.storage.uuid: {
- "asset_info": {"energy_to_sell": 0.0,
- "energy_active_in_bids": 0,
- "energy_to_buy": 1.08,
- "energy_active_in_offers": 0,
- "free_storage": 1.08,
- "used_storage": 0.12,
- "energy_traded": 0.0,
- "total_cost": 0.0},
- "last_slot_asset_info": {"energy_traded": 0.0,
- "total_cost": 0.0},
- "asset_bill": None,
- "area_name": "Storage"},
- self.load.uuid: {"asset_info": {
- "energy_requirement_kWh": 0.025,
- "energy_active_in_bids": 0.0,
- "energy_traded": 0.0,
- "total_cost": 0.0},
- "last_slot_asset_info": {
- "energy_traded": 0.0,
- "total_cost": 0.0},
- "asset_bill": None,
- "area_name": "Load"},
- self.pv.uuid: {"asset_info": {
- "available_energy_kWh": 0.0,
- "energy_active_in_offers": 0,
- "energy_traded": 0,
- "total_cost": 0},
- "last_slot_asset_info": {
- "energy_traded": 0,
- "total_cost": 0},
- "asset_bill": None,
- "area_name": "PV"
- }}}}}}
+ self.grid_area.uuid: {
+ "last_market_bill": {"accumulated_trades": {}, "external_trades": {}},
+ "last_market_stats": {
+ "min_trade_rate": None,
+ "max_trade_rate": None,
+ "avg_trade_rate": None,
+ "median_trade_rate": None,
+ "total_traded_energy_kWh": None,
+ },
+ "last_market_fee": 0.0,
+ "current_market_fee": None,
+ "area_name": "Grid",
+ "children": {
+ self.house_area.uuid: {
+ "last_market_bill": {"accumulated_trades": {}, "external_trades": {}},
+ "last_market_stats": {
+ "min_trade_rate": None,
+ "max_trade_rate": None,
+ "avg_trade_rate": None,
+ "median_trade_rate": None,
+ "total_traded_energy_kWh": None,
+ },
+ "last_market_fee": 0.0,
+ "current_market_fee": None,
+ "area_name": "House",
+ "children": {
+ self.storage.uuid: {
+ "asset_info": {
+ "energy_to_sell": 0.0,
+ "energy_active_in_bids": 0,
+ "energy_to_buy": 1.08,
+ "energy_active_in_offers": 0,
+ "free_storage": 1.08,
+ "used_storage": 0.12,
+ "energy_traded": 0.0,
+ "total_cost": 0.0,
+ },
+ "last_slot_asset_info": {"energy_traded": 0.0, "total_cost": 0.0},
+ "asset_bill": None,
+ "area_name": "Storage",
+ },
+ self.load.uuid: {
+ "asset_info": {
+ "energy_requirement_kWh": 0.025,
+ "energy_active_in_bids": 0.0,
+ "energy_traded": 0.0,
+ "total_cost": 0.0,
+ },
+ "last_slot_asset_info": {"energy_traded": 0.0, "total_cost": 0.0},
+ "asset_bill": None,
+ "area_name": "Load",
+ },
+ self.pv.uuid: {
+ "asset_info": {
+ "available_energy_kWh": 0.0,
+ "energy_active_in_offers": 0,
+ "energy_traded": 0,
+ "total_cost": 0,
+ },
+ "last_slot_asset_info": {"energy_traded": 0, "total_cost": 0},
+ "asset_bill": None,
+ "area_name": "PV",
+ },
+ },
+ }
+ },
+ }
+ }
assert expected_area_stats_tree_dict == go.area_stats_tree_dict
diff --git a/tests/test_gsy_core/test_rq_job_handler.py b/tests/test_gsy_core/test_rq_job_handler.py
index f7c241c8a..43e93328d 100644
--- a/tests/test_gsy_core/test_rq_job_handler.py
+++ b/tests/test_gsy_core/test_rq_job_handler.py
@@ -5,7 +5,7 @@
import pytest
from pendulum import duration, now, datetime
-from gsy_framework.constants_limits import GlobalConfig, ConstSettings
+from gsy_framework.constants_limits import GlobalConfig, ConstSettings, TIME_ZONE
from gsy_framework.enums import ConfigurationType, CoefficientAlgorithm
import gsy_e.constants
@@ -119,9 +119,7 @@ def test_past_market_slots_handles_settings_correctly(run_sim_mock: Mock):
assert config.tick_length == duration(seconds=20)
assert config.start_date == datetime(2023, 1, 1)
expected_end_date = (
- now(tz=gsy_e.constants.TIME_ZONE)
- .subtract(hours=ConstSettings.SCMSettings.HOURS_OF_DELAY)
- .add(hours=4)
+ now(tz=TIME_ZONE).subtract(hours=ConstSettings.SCMSettings.HOURS_OF_DELAY).add(hours=4)
)
assert config.end_date.replace(second=0, microsecond=0) == expected_end_date.replace(
second=0, microsecond=0
diff --git a/tests/test_market_agent.py b/tests/test_market_agent.py
index 0ce037d1e..bd5ce18e8 100644
--- a/tests/test_market_agent.py
+++ b/tests/test_market_agent.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
# pylint: disable=missing-class-docstring
# pylint: disable=missing-function-docstring
@@ -28,7 +29,7 @@
from gsy_framework.constants_limits import ConstSettings, GlobalConfig
from gsy_framework.data_classes import Bid, MarketClearingState, Offer, Trade, TraderDetails
-from gsy_e.constants import TIME_FORMAT, TIME_ZONE
+from gsy_framework.constants_limits import TIME_FORMAT, TIME_ZONE
from gsy_e.models.market import GridFee
from gsy_e.models.market.grid_fees.base_model import GridFees
from gsy_e.models.strategy.market_agents.one_sided_agent import OneSidedAgent
@@ -102,21 +103,35 @@ def accept_offer(self, offer_or_id, buyer, *, energy=None, time=None, trade_bid_
if energy < offer.energy:
residual_energy = offer.energy - energy
residual = Offer(
- "res", offer.creation_time, offer.price, residual_energy, offer.seller)
+ "res", offer.creation_time, offer.price, residual_energy, offer.seller
+ )
traded = Offer(offer.id, offer.creation_time, offer.price, energy, offer.seller)
- return Trade("trade_id", time, traded.seller,
- TraderDetails(buyer, ""),
- residual=residual, offer=traded,
- traded_energy=1, trade_price=1)
-
- return Trade("trade_id", time, offer.seller,
- TraderDetails(buyer, ""),
- offer=offer, traded_energy=1, trade_price=1)
+ return Trade(
+ "trade_id",
+ time,
+ traded.seller,
+ TraderDetails(buyer, ""),
+ residual=residual,
+ offer=traded,
+ traded_energy=1,
+ trade_price=1,
+ )
+
+ return Trade(
+ "trade_id",
+ time,
+ offer.seller,
+ TraderDetails(buyer, ""),
+ offer=offer,
+ traded_energy=1,
+ trade_price=1,
+ )
# pylint: disable=unused-argument
# pylint: disable=too-many-locals
- def accept_bid(self, bid, energy, seller, buyer=None, *,
- time=None, trade_offer_info=None, offer=None):
+ def accept_bid(
+ self, bid, energy, seller, buyer=None, *, time=None, trade_offer_info=None, offer=None
+ ):
self.calls_energy_bids.append(energy)
self.calls_bids.append(bid)
self.calls_bids_price.append(bid.price)
@@ -132,13 +147,21 @@ def accept_bid(self, bid, energy, seller, buyer=None, *,
residual_energy = bid.energy - energy
residual = Bid("res", bid.creation_time, bid.price, residual_energy, bid.buyer)
traded = Bid(bid.id, bid.creation_time, (trade_rate * energy), energy, bid.buyer)
- return Trade("trade_id", time, seller,
- bid.buyer, bid=traded, residual=residual,
- traded_energy=1, trade_price=1)
+ return Trade(
+ "trade_id",
+ time,
+ seller,
+ bid.buyer,
+ bid=traded,
+ residual=residual,
+ traded_energy=1,
+ trade_price=1,
+ )
traded = Bid(bid.id, bid.creation_time, (trade_rate * energy), energy, bid.buyer)
- return Trade("trade_id", time, seller,
- bid.buyer, bid=traded, traded_energy=1, trade_price=1)
+ return Trade(
+ "trade_id", time, seller, bid.buyer, bid=traded, traded_energy=1, trade_price=1
+ )
def delete_offer(self, *args):
pass
@@ -150,12 +173,19 @@ def _update_new_offer_price_with_fee(self, offer_price, original_price, energy):
return offer_price + self.fee_class.grid_fee_rate * original_price
def _update_new_bid_price_with_fee(self, bid_price, original_price):
- return self.fee_class.update_incoming_bid_with_fee(
- bid_price, original_price)
-
- def offer(self, price: float, energy: float, seller: TraderDetails, offer_id=None,
- original_price=None, dispatch_event=True, adapt_price_with_fees=True,
- time_slot=None) -> Offer:
+ return self.fee_class.update_incoming_bid_with_fee(bid_price, original_price)
+
+ def offer(
+ self,
+ price: float,
+ energy: float,
+ seller: TraderDetails,
+ offer_id=None,
+ original_price=None,
+ dispatch_event=True,
+ adapt_price_with_fees=True,
+ time_slot=None,
+ ) -> Offer:
self.offer_call_count += 1
if original_price is None:
@@ -176,9 +206,17 @@ def dispatch_market_offer_event(self, offer):
def dispatch_market_bid_event(self, bid):
pass
- def bid(self, price: float, energy: float, buyer: TraderDetails,
- bid_id: str = None, original_price=None, dispatch_event=True,
- adapt_price_with_fees=True, time_slot=None):
+ def bid(
+ self,
+ price: float,
+ energy: float,
+ buyer: TraderDetails,
+ bid_id: str = None,
+ original_price=None,
+ dispatch_event=True,
+ adapt_price_with_fees=True,
+ time_slot=None,
+ ):
self.bid_call_count += 1
if original_price is None:
@@ -190,8 +228,7 @@ def bid(self, price: float, energy: float, buyer: TraderDetails,
if adapt_price_with_fees:
price = self._update_new_bid_price_with_fee(price, original_price)
- bid = Bid(bid_id, pendulum.now(), price, energy, buyer,
- original_price=original_price)
+ bid = Bid(bid_id, pendulum.now(), price, energy, buyer, original_price=original_price)
self._bids.append(bid)
self.forwarded_bid = bid
@@ -200,43 +237,53 @@ def bid(self, price: float, energy: float, buyer: TraderDetails,
def split_offer(self, original_offer, energy, orig_offer_price):
self.offers.pop(original_offer.id, None)
# same offer id is used for the new accepted_offer
- accepted_offer = self.offer(offer_id=original_offer.id,
- price=original_offer.price * (energy / original_offer.energy),
- energy=energy,
- seller=original_offer.seller,
- dispatch_event=False)
+ accepted_offer = self.offer(
+ offer_id=original_offer.id,
+ price=original_offer.price * (energy / original_offer.energy),
+ energy=energy,
+ seller=original_offer.seller,
+ dispatch_event=False,
+ )
residual_price = (1 - energy / original_offer.energy) * original_offer.price
residual_energy = original_offer.energy - energy
- original_residual_price = \
- ((original_offer.energy - energy) / original_offer.energy) * orig_offer_price
-
- residual_offer = self.offer(price=residual_price,
- energy=residual_energy,
- seller=original_offer.seller,
- original_price=original_residual_price,
- dispatch_event=False,
- adapt_price_with_fees=False)
+ original_residual_price = (
+ (original_offer.energy - energy) / original_offer.energy
+ ) * orig_offer_price
+
+ residual_offer = self.offer(
+ price=residual_price,
+ energy=residual_energy,
+ seller=original_offer.seller,
+ original_price=original_residual_price,
+ dispatch_event=False,
+ adapt_price_with_fees=False,
+ )
return accepted_offer, residual_offer
def split_bid(self, original_bid, energy, orig_bid_price):
self.offers.pop(original_bid.id, None)
# same offer id is used for the new accepted_offer
- accepted_bid = self.bid(bid_id=original_bid.id,
- buyer=original_bid.buyer,
- price=original_bid.price * (energy / original_bid.energy),
- energy=energy)
+ accepted_bid = self.bid(
+ bid_id=original_bid.id,
+ buyer=original_bid.buyer,
+ price=original_bid.price * (energy / original_bid.energy),
+ energy=energy,
+ )
residual_price = (1 - energy / original_bid.energy) * original_bid.price
residual_energy = original_bid.energy - energy
- original_residual_price = \
- ((original_bid.energy - energy) / original_bid.energy) * orig_bid_price
-
- residual_bid = self.bid(price=residual_price,
- buyer=original_bid.buyer,
- energy=residual_energy,
- original_price=original_residual_price,
- adapt_price_with_fees=False)
+ original_residual_price = (
+ (original_bid.energy - energy) / original_bid.energy
+ ) * orig_bid_price
+
+ residual_bid = self.bid(
+ price=residual_price,
+ buyer=original_bid.buyer,
+ energy=residual_energy,
+ original_price=original_residual_price,
+ adapt_price_with_fees=False,
+ )
return accepted_bid, residual_bid
@@ -250,17 +297,21 @@ def teardown_method():
@staticmethod
@pytest.fixture(name="market_agent")
def market_agent_fixture():
- lower_market = FakeMarket([
- Offer("id", pendulum.now(), 1, 1, TraderDetails("other", ""), 1)],
- transfer_fees=GridFee(grid_fee_percentage=0.1,
- grid_fee_const=2))
- higher_market = FakeMarket([
- Offer("id2", pendulum.now(), 3, 3, TraderDetails("owner", ""), 3),
- Offer("id3", pendulum.now(), 0.5, 1, TraderDetails("owner", ""), 0.5)],
- transfer_fees=GridFee(grid_fee_percentage=0.1, grid_fee_const=2))
+ lower_market = FakeMarket(
+ [Offer("id", pendulum.now(), 1, 1, TraderDetails("other", ""), 1)],
+ transfer_fees=GridFee(grid_fee_percentage=0.1, grid_fee_const=2),
+ )
+ higher_market = FakeMarket(
+ [
+ Offer("id2", pendulum.now(), 3, 3, TraderDetails("owner", ""), 3),
+ Offer("id3", pendulum.now(), 0.5, 1, TraderDetails("owner", ""), 0.5),
+ ],
+ transfer_fees=GridFee(grid_fee_percentage=0.1, grid_fee_const=2),
+ )
owner = FakeArea("owner")
market_agent = OneSidedAgent(
- owner=owner, higher_market=higher_market, lower_market=lower_market)
+ owner=owner, higher_market=higher_market, lower_market=lower_market
+ )
market_agent.event_tick()
market_agent.owner.current_tick = 14
market_agent.event_tick()
@@ -270,9 +321,11 @@ def market_agent_fixture():
@staticmethod
def test_ma_forwarded_offers_complied_to_transfer_fee(market_agent):
source_offer = [
- offer for offer in market_agent.lower_market.sorted_offers if offer.id == "id"][0]
+ offer for offer in market_agent.lower_market.sorted_offers if offer.id == "id"
+ ][0]
target_offer = [
- offer for offer in market_agent.higher_market.sorted_offers if offer.id == "uuid"][0]
+ offer for offer in market_agent.higher_market.sorted_offers if offer.id == "uuid"
+ ][0]
earned_ma_fee = target_offer.price - source_offer.price
expected_ma_fee = market_agent.higher_market.fee_class.grid_fee_rate
@@ -282,41 +335,48 @@ def test_ma_forwarded_offers_complied_to_transfer_fee(market_agent):
@pytest.mark.parametrize("market_agent_fee", [0.1, 0, 0.5, 0.75, 0.05, 0.02, 0.03])
def test_ma_forwards_bids_according_to_percentage(market_agent_fee):
ConstSettings.MASettings.MARKET_TYPE = 2
- lower_market = FakeMarket([], [
- Bid("id", pendulum.now(), 1, 1, TraderDetails("this", ""), 1)],
- transfer_fees=GridFee(grid_fee_percentage=market_agent_fee,
- grid_fee_const=0),
- name="FakeMarket")
- higher_market = FakeMarket([], [
- Bid("id2", pendulum.now(), 3, 3, TraderDetails("child", ""), 3)],
- transfer_fees=GridFee(grid_fee_percentage=market_agent_fee,
- grid_fee_const=0),
- name="FakeMarket")
+ lower_market = FakeMarket(
+ [],
+ [Bid("id", pendulum.now(), 1, 1, TraderDetails("this", ""), 1)],
+ transfer_fees=GridFee(grid_fee_percentage=market_agent_fee, grid_fee_const=0),
+ name="FakeMarket",
+ )
+ higher_market = FakeMarket(
+ [],
+ [Bid("id2", pendulum.now(), 3, 3, TraderDetails("child", ""), 3)],
+ transfer_fees=GridFee(grid_fee_percentage=market_agent_fee, grid_fee_const=0),
+ name="FakeMarket",
+ )
market_agent = TwoSidedAgent(
- owner=FakeArea("owner"), higher_market=higher_market, lower_market=lower_market)
+ owner=FakeArea("owner"), higher_market=higher_market, lower_market=lower_market
+ )
market_agent.event_tick()
market_agent.owner.current_tick = 14
market_agent.event_tick()
assert market_agent.higher_market.bid_call_count == 1
- assert (market_agent.higher_market.forwarded_bid.price ==
- list(market_agent.lower_market.bids.values())[-1].price * (1 - market_agent_fee))
+ assert market_agent.higher_market.forwarded_bid.price == list(
+ market_agent.lower_market.bids.values()
+ )[-1].price * (1 - market_agent_fee)
@staticmethod
@pytest.mark.parametrize("market_agent_fee_const", [0.5, 1, 5, 10])
@pytest.mark.skip("need to define if we need a constant fee")
def test_ma_forwards_bids_according_to_constantfee(market_agent_fee_const):
ConstSettings.MASettings.MARKET_TYPE = 2
- lower_market = FakeMarket([], [
- Bid("id", pendulum.now(), 15, 1, TraderDetails("this", ""), 15)],
- transfer_fees=GridFee(grid_fee_percentage=0,
- grid_fee_const=market_agent_fee_const))
- higher_market = FakeMarket([], [
- Bid("id2", pendulum.now(), 35, 3, TraderDetails("child", ""), 35)],
- transfer_fees=GridFee(grid_fee_percentage=0,
- grid_fee_const=market_agent_fee_const))
+ lower_market = FakeMarket(
+ [],
+ [Bid("id", pendulum.now(), 15, 1, TraderDetails("this", ""), 15)],
+ transfer_fees=GridFee(grid_fee_percentage=0, grid_fee_const=market_agent_fee_const),
+ )
+ higher_market = FakeMarket(
+ [],
+ [Bid("id2", pendulum.now(), 35, 3, TraderDetails("child", ""), 35)],
+ transfer_fees=GridFee(grid_fee_percentage=0, grid_fee_const=market_agent_fee_const),
+ )
market_agent = TwoSidedAgent(
- owner=FakeArea("owner"), higher_market=higher_market, lower_market=lower_market)
+ owner=FakeArea("owner"), higher_market=higher_market, lower_market=lower_market
+ )
market_agent.event_tick()
market_agent.owner.current_tick = 14
market_agent.event_tick()
@@ -324,17 +384,21 @@ def test_ma_forwards_bids_according_to_constantfee(market_agent_fee_const):
assert market_agent.higher_market.bid_call_count == 1
bid = list(market_agent.lower_market.bids.values())[-1]
assert market_agent.higher_market.forwarded_bid.price == (
- bid.price - market_agent_fee_const * bid.energy)
+ bid.price - market_agent_fee_const * bid.energy
+ )
@pytest.fixture(name="market_agent_bid", params=[TwoSidedAgent, SettlementAgent])
def market_agent_bid_fixture(request):
ConstSettings.MASettings.MARKET_TYPE = 2
- lower_market = FakeMarket([], [
- Bid("id", pendulum.now(), 1, 1, TraderDetails("this", ""), 1)])
- higher_market = FakeMarket([], [
- Bid("id2", pendulum.now(), 1, 1, TraderDetails("child", ""), 1),
- Bid("id3", pendulum.now(), 0.5, 1, TraderDetails("child", ""), 1)])
+ lower_market = FakeMarket([], [Bid("id", pendulum.now(), 1, 1, TraderDetails("this", ""), 1)])
+ higher_market = FakeMarket(
+ [],
+ [
+ Bid("id2", pendulum.now(), 1, 1, TraderDetails("child", ""), 1),
+ Bid("id3", pendulum.now(), 0.5, 1, TraderDetails("child", ""), 1),
+ ],
+ )
owner = FakeArea("owner")
agent_class = request.param
@@ -351,12 +415,15 @@ def market_agent_double_sided_fixture():
lower_market = FakeMarket(
offers=[Offer("id", pendulum.now(), 2, 2, TraderDetails("other", ""), 2)],
bids=[Bid("bid_id", pendulum.now(), 10, 10, TraderDetails("B", ""), 10)],
- transfer_fees=GridFee(grid_fee_percentage=0.01, grid_fee_const=0))
- higher_market = FakeMarket([], [], transfer_fees=GridFee(grid_fee_percentage=0.01,
- grid_fee_const=0))
+ transfer_fees=GridFee(grid_fee_percentage=0.01, grid_fee_const=0),
+ )
+ higher_market = FakeMarket(
+ [], [], transfer_fees=GridFee(grid_fee_percentage=0.01, grid_fee_const=0)
+ )
owner = FakeArea("owner")
market_agent = TwoSidedAgent(
- owner=owner, lower_market=lower_market, higher_market=higher_market)
+ owner=owner, lower_market=lower_market, higher_market=higher_market
+ )
market_agent.event_tick()
market_agent.owner.current_tick += 2
market_agent.event_tick()
@@ -379,42 +446,52 @@ def test_ma_forwards_bids(market_agent_bid):
@staticmethod
def test_ma_forwarded_bids_adhere_to_ma_overhead(market_agent_bid):
assert market_agent_bid.higher_market.bid_call_count == 1
- expected_price = (
- list(market_agent_bid.lower_market.bids.values())[-1].price *
- (1 - market_agent_bid.lower_market.fee_class.grid_fee_rate))
+ expected_price = list(market_agent_bid.lower_market.bids.values())[-1].price * (
+ 1 - market_agent_bid.lower_market.fee_class.grid_fee_rate
+ )
assert market_agent_bid.higher_market.forwarded_bid.price == expected_price
@staticmethod
def test_ma_event_trade_bid_deletes_forwarded_bid_when_sold(market_agent_bid, called):
market_agent_bid.lower_market.delete_bid = called
market_agent_bid.event_bid_traded(
- bid_trade=Trade("trade_id",
- pendulum.now(tz=TIME_ZONE),
- TraderDetails("someone_else", ""),
- TraderDetails("owner", ""),
- bid=market_agent_bid.higher_market.bids["id3"],
- traded_energy=1, trade_price=1),
- market_id=market_agent_bid.higher_market.id)
+ bid_trade=Trade(
+ "trade_id",
+ pendulum.now(tz=TIME_ZONE),
+ TraderDetails("someone_else", ""),
+ TraderDetails("owner", ""),
+ bid=market_agent_bid.higher_market.bids["id3"],
+ traded_energy=1,
+ trade_price=1,
+ ),
+ market_id=market_agent_bid.higher_market.id,
+ )
assert len(market_agent_bid.lower_market.delete_bid.calls) == 1
@staticmethod
def test_ma_event_trade_bid_does_not_delete_forwarded_bid_of_counterpart(
- market_agent_bid, called):
+ market_agent_bid, called
+ ):
market_agent_bid.lower_market.delete_bid = called
high_to_low_engine = market_agent_bid.engines[1]
high_to_low_engine.event_bid_traded(
- bid_trade=Trade("trade_id",
- pendulum.now(tz=TIME_ZONE),
- bid=market_agent_bid.higher_market.bids["id3"],
- seller=TraderDetails("owner", ""),
- buyer=TraderDetails("someone_else", ""),
- traded_energy=1, trade_price=1))
+ bid_trade=Trade(
+ "trade_id",
+ pendulum.now(tz=TIME_ZONE),
+ bid=market_agent_bid.higher_market.bids["id3"],
+ seller=TraderDetails("owner", ""),
+ buyer=TraderDetails("someone_else", ""),
+ traded_energy=1,
+ trade_price=1,
+ )
+ )
assert len(market_agent_bid.lower_market.delete_bid.calls) == 0
@staticmethod
@pytest.mark.parametrize("partial", [True, False])
def test_ma_event_bid_split_and_trade_correctly_populate_forwarded_bid_entries(
- market_agent_bid, called, partial):
+ market_agent_bid, called, partial
+ ):
market_agent_bid.lower_market.delete_bid = called
low_to_high_engine = market_agent_bid.engines[0]
market_agent_bid._get_market_from_market_id = lambda x: low_to_high_engine.markets.target
@@ -431,8 +508,9 @@ def test_ma_event_bid_split_and_trade_correctly_populate_forwarded_bid_entries(
original_bid = low_to_high_engine.markets.target._bids[0]
accepted_bid = deepcopy(original_bid)
- accepted_bid.update_price((original_bid.energy - residual_energy) * (
- original_bid.energy_rate))
+ accepted_bid.update_price(
+ (original_bid.energy - residual_energy) * (original_bid.energy_rate)
+ )
accepted_bid.update_energy(original_bid.energy - residual_energy)
residual_bid = deepcopy(original_bid)
@@ -440,30 +518,45 @@ def test_ma_event_bid_split_and_trade_correctly_populate_forwarded_bid_entries(
residual_bid.update_price(residual_energy * original_bid.energy_rate)
residual_bid.update_energy(residual_energy)
- low_to_high_engine.event_bid_split(market_id=low_to_high_engine.markets.target.id,
- original_bid=original_bid,
- accepted_bid=accepted_bid,
- residual_bid=residual_bid)
- assert set(low_to_high_engine.forwarded_bids.keys()) == \
- {original_bid.id, accepted_bid.id, residual_bid.id, "uuid", "id3", "id2"}
+ low_to_high_engine.event_bid_split(
+ market_id=low_to_high_engine.markets.target.id,
+ original_bid=original_bid,
+ accepted_bid=accepted_bid,
+ residual_bid=residual_bid,
+ )
+ assert set(low_to_high_engine.forwarded_bids.keys()) == {
+ original_bid.id,
+ accepted_bid.id,
+ residual_bid.id,
+ "uuid",
+ "id3",
+ "id2",
+ }
else:
original_bid = low_to_high_engine.markets.target._bids[0]
accepted_bid = deepcopy(original_bid)
residual_bid = None
low_to_high_engine.event_bid_traded(
- bid_trade=Trade("trade_id",
- pendulum.now(tz=TIME_ZONE),
- bid=accepted_bid,
- seller=TraderDetails("someone_else", ""),
- buyer=TraderDetails("owner", ""),
- residual=residual_bid,
- traded_energy=1, trade_price=1))
+ bid_trade=Trade(
+ "trade_id",
+ pendulum.now(tz=TIME_ZONE),
+ bid=accepted_bid,
+ seller=TraderDetails("someone_else", ""),
+ buyer=TraderDetails("owner", ""),
+ residual=residual_bid,
+ traded_energy=1,
+ trade_price=1,
+ )
+ )
if partial:
# "id" gets traded in the target market, "id2" gets split in the source market, too
- assert set(
- low_to_high_engine.forwarded_bids.keys()) == {residual_bid.id, "uuid", "id3"}
+ assert set(low_to_high_engine.forwarded_bids.keys()) == {
+ residual_bid.id,
+ "uuid",
+ "id3",
+ }
else:
# "id" and "id2" get traded in both target and source,
# left over is id3 and its forwarded instance uuid
@@ -471,16 +564,21 @@ def test_ma_event_bid_split_and_trade_correctly_populate_forwarded_bid_entries(
@staticmethod
def test_ma_event_trade_buys_accepted_bid(market_agent_double_sided):
- market_agent_double_sided.higher_market.forwarded_bid = \
+ market_agent_double_sided.higher_market.forwarded_bid = (
market_agent_double_sided.higher_market.forwarded_bid
+ )
market_agent_double_sided.event_bid_traded(
- bid_trade=Trade("trade_id",
- pendulum.now(tz=TIME_ZONE),
- TraderDetails("owner", ""),
- TraderDetails("someone_else", ""),
- bid=market_agent_double_sided.higher_market.forwarded_bid,
- traded_energy=1, trade_price=1),
- market_id=market_agent_double_sided.higher_market.id)
+ bid_trade=Trade(
+ "trade_id",
+ pendulum.now(tz=TIME_ZONE),
+ TraderDetails("owner", ""),
+ TraderDetails("someone_else", ""),
+ bid=market_agent_double_sided.higher_market.forwarded_bid,
+ traded_energy=1,
+ trade_price=1,
+ ),
+ market_id=market_agent_double_sided.higher_market.id,
+ )
assert len(market_agent_double_sided.lower_market.calls_energy_bids) == 1
expected_price = 10 * (1 - market_agent_double_sided.lower_market.fee_class.grid_fee_rate)
@@ -490,13 +588,17 @@ def test_ma_event_trade_buys_accepted_bid(market_agent_double_sided):
@staticmethod
def test_ma_event_bid_trade_increases_bid_price(market_agent_double_sided):
market_agent_double_sided.event_bid_traded(
- bid_trade=Trade("trade_id",
- pendulum.now(tz=TIME_ZONE),
- TraderDetails("owner", ""),
- TraderDetails("someone_else", ""),
- bid=market_agent_double_sided.higher_market.forwarded_bid,
- traded_energy=1, trade_price=1),
- market_id=market_agent_double_sided.higher_market.id)
+ bid_trade=Trade(
+ "trade_id",
+ pendulum.now(tz=TIME_ZONE),
+ TraderDetails("owner", ""),
+ TraderDetails("someone_else", ""),
+ bid=market_agent_double_sided.higher_market.forwarded_bid,
+ traded_energy=1,
+ trade_price=1,
+ ),
+ market_id=market_agent_double_sided.higher_market.id,
+ )
assert len(market_agent_double_sided.lower_market.calls_energy_bids) == 1
expected_price = 10 * (1 - market_agent_double_sided.lower_market.fee_class.grid_fee_rate)
assert market_agent_double_sided.higher_market.forwarded_bid.price == expected_price
@@ -506,49 +608,71 @@ def test_ma_event_bid_trade_increases_bid_price(market_agent_double_sided):
@staticmethod
def test_ma_event_trade_buys_partial_accepted_bid(market_agent_double_sided):
market_agent_double_sided._get_market_from_market_id = (
- lambda x: market_agent_double_sided.higher_market)
+ lambda x: market_agent_double_sided.higher_market
+ )
original_bid = market_agent_double_sided.higher_market.forwarded_bid
- accepted_bid_price = (original_bid.price/original_bid.energy) * 1
- residual_bid_price = (original_bid.price/original_bid.energy) * 0.1
- accepted_bid = Bid(original_bid.id, original_bid.creation_time, accepted_bid_price, 1,
- original_bid.buyer)
- residual_bid = Bid("residual_bid", original_bid.creation_time, residual_bid_price, 0.1,
- original_bid.buyer)
+ accepted_bid_price = (original_bid.price / original_bid.energy) * 1
+ residual_bid_price = (original_bid.price / original_bid.energy) * 0.1
+ accepted_bid = Bid(
+ original_bid.id, original_bid.creation_time, accepted_bid_price, 1, original_bid.buyer
+ )
+ residual_bid = Bid(
+ "residual_bid", original_bid.creation_time, residual_bid_price, 0.1, original_bid.buyer
+ )
market_agent_double_sided.event_bid_split(
market_id=market_agent_double_sided.higher_market.id,
original_bid=original_bid,
accepted_bid=accepted_bid,
- residual_bid=residual_bid)
+ residual_bid=residual_bid,
+ )
market_agent_double_sided.event_bid_traded(
- bid_trade=Trade("trade_id",
- pendulum.now(tz=TIME_ZONE),
- TraderDetails("owner", ""),
- TraderDetails("someone_else", ""),
- bid=accepted_bid,
- residual="residual_offer", traded_energy=1, trade_price=1),
- market_id=market_agent_double_sided.higher_market.id)
+ bid_trade=Trade(
+ "trade_id",
+ pendulum.now(tz=TIME_ZONE),
+ TraderDetails("owner", ""),
+ TraderDetails("someone_else", ""),
+ bid=accepted_bid,
+ residual="residual_offer",
+ traded_energy=1,
+ trade_price=1,
+ ),
+ market_id=market_agent_double_sided.higher_market.id,
+ )
assert market_agent_double_sided.lower_market.calls_energy_bids[0] == 1
@staticmethod
def test_ma_forwards_partial_bid_from_source_market(market_agent_double_sided):
market_agent_double_sided._get_market_from_market_id = (
- lambda x: market_agent_double_sided.lower_market)
+ lambda x: market_agent_double_sided.lower_market
+ )
original_bid = market_agent_double_sided.lower_market._bids[0]
residual_energy = 0.1
- accepted_bid = Bid(original_bid.id, original_bid.creation_time, original_bid.price,
- original_bid.energy - residual_energy, original_bid.buyer,
- original_bid.price)
+ accepted_bid = Bid(
+ original_bid.id,
+ original_bid.creation_time,
+ original_bid.price,
+ original_bid.energy - residual_energy,
+ original_bid.buyer,
+ original_bid.price,
+ )
residual_bid = Bid(
- "residual_bid", original_bid.creation_time, original_bid.price, residual_energy,
- original_bid.buyer, original_bid.price)
+ "residual_bid",
+ original_bid.creation_time,
+ original_bid.price,
+ residual_energy,
+ original_bid.buyer,
+ original_bid.price,
+ )
market_agent_double_sided.usable_bid = lambda s: True
market_agent_double_sided.event_bid_split(
market_id=market_agent_double_sided.lower_market.id,
original_bid=original_bid,
accepted_bid=accepted_bid,
- residual_bid=residual_bid)
+ residual_bid=residual_bid,
+ )
assert isclose(
- market_agent_double_sided.higher_market.forwarded_bid.energy, residual_energy)
+ market_agent_double_sided.higher_market.forwarded_bid.energy, residual_energy
+ )
class TestMAOffer:
@@ -561,15 +685,17 @@ def teardown_method():
@staticmethod
@pytest.fixture(name="market_agent")
def market_agent_fixture():
- lower_market = FakeMarket([
- Offer("id", pendulum.now(), 1, 1, TraderDetails("other", ""), 1)])
- higher_market = FakeMarket([
- Offer("id2", pendulum.now(), 3, 3, TraderDetails("higher", ""), 3),
- Offer("id3", pendulum.now(), 0.5, 1, TraderDetails("higher", ""), 0.5)])
+ lower_market = FakeMarket(
+ [Offer("id", pendulum.now(), 1, 1, TraderDetails("other", ""), 1)]
+ )
+ higher_market = FakeMarket(
+ [
+ Offer("id2", pendulum.now(), 3, 3, TraderDetails("higher", ""), 3),
+ Offer("id3", pendulum.now(), 0.5, 1, TraderDetails("higher", ""), 0.5),
+ ]
+ )
owner = FakeArea("owner")
- maa = OneSidedAgent(owner=owner,
- higher_market=higher_market,
- lower_market=lower_market)
+ maa = OneSidedAgent(owner=owner, higher_market=higher_market, lower_market=lower_market)
maa.event_tick()
maa.owner.current_tick = 14
maa.event_tick()
@@ -579,8 +705,9 @@ def market_agent_fixture():
@staticmethod
@pytest.fixture(name="market_agent_2")
def market_agent_2_fixture():
- lower_market = FakeMarket([
- Offer("id", pendulum.now(), 2, 2, TraderDetails("other", ""), 2)], m_id=123)
+ lower_market = FakeMarket(
+ [Offer("id", pendulum.now(), 2, 2, TraderDetails("other", ""), 2)], m_id=123
+ )
higher_market = FakeMarket([], m_id=234)
owner = FakeArea("owner")
owner.future_market = lower_market
@@ -604,9 +731,13 @@ def test_ma_event_trade_deletes_forwarded_offer_when_sold(market_agent, called):
"trade_id",
pendulum.now(tz=TIME_ZONE),
TraderDetails("higher", ""),
- TraderDetails("someone_else", ""), offer=market_agent.higher_market.offers["id3"],
- traded_energy=1, trade_price=1),
- market_id=market_agent.higher_market.id)
+ TraderDetails("someone_else", ""),
+ offer=market_agent.higher_market.offers["id3"],
+ traded_energy=1,
+ trade_price=1,
+ ),
+ market_id=market_agent.higher_market.id,
+ )
assert len(market_agent.lower_market.delete_offer.calls) == 1
@staticmethod
@@ -618,8 +749,12 @@ def test_ma_event_trade_buys_accepted_offer(market_agent_2):
TraderDetails("owner", ""),
TraderDetails("someone_else", ""),
offer=market_agent_2.higher_market.forwarded_offer,
- fee_price=0.0, traded_energy=1, trade_price=1),
- market_id=market_agent_2.higher_market.id)
+ fee_price=0.0,
+ traded_energy=1,
+ trade_price=1,
+ ),
+ market_id=market_agent_2.higher_market.id,
+ )
assert len(market_agent_2.lower_market.calls_energy) == 1
@staticmethod
@@ -634,15 +769,20 @@ def test_ma_event_trade_buys_partial_accepted_offer(market_agent_2):
pendulum.now(tz=TIME_ZONE),
TraderDetails("owner", ""),
TraderDetails("someone_else", ""),
- offer=accepted_offer, traded_energy=1, trade_price=1,
+ offer=accepted_offer,
+ traded_energy=1,
+ trade_price=1,
residual="residual_offer",
- fee_price=0.0, ),
- market_id=market_agent_2.higher_market.id)
+ fee_price=0.0,
+ ),
+ market_id=market_agent_2.higher_market.id,
+ )
assert market_agent_2.lower_market.calls_energy[0] == 1
@staticmethod
def test_ma_event_offer_split_and_trade_correctly_populate_forwarded_offer_entries(
- market_agent_2):
+ market_agent_2,
+ ):
residual_offer_id = "res_id"
original_offer_id = "id"
original = market_agent_2.higher_market.forwarded_offer
@@ -655,15 +795,20 @@ def test_ma_event_offer_split_and_trade_correctly_populate_forwarded_offer_entri
market_id=market_agent_2.higher_market.id,
original_offer=original,
accepted_offer=accepted,
- residual_offer=residual)
+ residual_offer=residual,
+ )
engine = next(
- (e for e in market_agent_2.engines if residual_offer_id in e.forwarded_offers), None)
+ (e for e in market_agent_2.engines if residual_offer_id in e.forwarded_offers), None
+ )
assert engine is not None, "Residual of forwarded offers not found in forwarded_offers"
# after the split event:
# all three offer ids are part of the forwarded_offer member
- assert set(engine.forwarded_offers.keys()) == {residual_offer_id, original_offer_id,
- "uuid"}
+ assert set(engine.forwarded_offers.keys()) == {
+ residual_offer_id,
+ original_offer_id,
+ "uuid",
+ }
# and the accepted offer was added
assert engine.forwarded_offers[original_offer_id].target_offer.energy == accepted.energy
# and the residual offer was added
@@ -676,10 +821,13 @@ def test_ma_event_offer_split_and_trade_correctly_populate_forwarded_offer_entri
TraderDetails("owner", ""),
TraderDetails("someone_else", ""),
offer=accepted,
- traded_energy=1, trade_price=1,
+ traded_energy=1,
+ trade_price=1,
residual=residual,
- fee_price=0.0,),
- market_id=market_agent_2.lower_market.id)
+ fee_price=0.0,
+ ),
+ market_id=market_agent_2.lower_market.id,
+ )
# after the trade event:
# the forwarded_offers only contain the residual offer
diff --git a/tests/test_state/test_consumption_state.py b/tests/test_state/test_consumption_state.py
index 5fb5855a6..8617e6758 100644
--- a/tests/test_state/test_consumption_state.py
+++ b/tests/test_state/test_consumption_state.py
@@ -3,13 +3,14 @@
import pytest
from pendulum import now, DateTime
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.state.base_states import ConsumptionState, StateInterface
from tests.test_state.test_prosumption_interface import TestProsumptionInterface
class ConsumptionInterfaceHelper(ConsumptionState):
"""Add the get_results_dict abstract method to the ConsumptionState."""
+
def get_results_dict(self, current_time_slot: DateTime) -> dict:
raise NotImplementedError
@@ -22,17 +23,14 @@ def _setup_base_configuration():
return ConsumptionInterfaceHelper(), now()
def test_get_state_raise_error_if_conflicting_keys(self):
- with patch.object(StateInterface, "get_state",
- return_value={"desired_energy_Wh": {-1.0}}):
+ with patch.object(StateInterface, "get_state", return_value={"desired_energy_Wh": {-1.0}}):
consumption_state, _ = self._setup_base_configuration()
with pytest.raises(AssertionError) as error:
consumption_state.get_state()
- assert ("Conflicting state values found for {'desired_energy_Wh'}."
- in str(error.value))
+ assert "Conflicting state values found for {'desired_energy_Wh'}." in str(error.value)
def test_get_state_keys_in_state_dict(self):
- expected_keys = ["desired_energy_Wh",
- "total_energy_demanded_Wh"]
+ expected_keys = ["desired_energy_Wh", "total_energy_demanded_Wh"]
consumption_state, _ = self._setup_base_configuration()
state_dict = consumption_state.get_state()
assert set(expected_keys).issubset(state_dict.keys())
@@ -40,39 +38,26 @@ def test_get_state_keys_in_state_dict(self):
def test_restore_state_values_set_properly(self):
time_slot, consumption_state = self._setup_default_test_consumption_state()
past_state = consumption_state.get_state()
- consumption_state.set_desired_energy(
- energy=2.0,
- time_slot=time_slot
- )
- consumption_state.update_total_demanded_energy(
- time_slot=time_slot
- )
+ consumption_state.set_desired_energy(energy=2.0, time_slot=time_slot)
+ consumption_state.update_total_demanded_energy(time_slot=time_slot)
assert past_state != consumption_state.get_state()
consumption_state.restore_state(state_dict=past_state)
assert past_state == consumption_state.get_state()
def _setup_default_test_consumption_state(self):
consumption_state, time_slot = self._setup_base_configuration()
- consumption_state.set_desired_energy(
- energy=1.0,
- time_slot=time_slot
- )
- consumption_state.update_total_demanded_energy(
- time_slot=time_slot
- )
+ consumption_state.set_desired_energy(energy=1.0, time_slot=time_slot)
+ consumption_state.update_total_demanded_energy(time_slot=time_slot)
return time_slot, consumption_state
@pytest.mark.parametrize("overwrite, expected_energy", [(True, 2), (False, 1)])
def test_set_desired_energy_setting_and_overwriting_mechanism(
- self, overwrite, expected_energy):
+ self, overwrite, expected_energy
+ ):
time_slot, consumption_state = self._setup_default_test_consumption_state()
- consumption_state.set_desired_energy(energy=2,
- time_slot=time_slot,
- overwrite=overwrite)
- assert consumption_state.get_energy_requirement_Wh(
- time_slot) == expected_energy
- assert consumption_state.get_desired_energy_Wh(
- time_slot) == expected_energy
+ consumption_state.set_desired_energy(energy=2, time_slot=time_slot, overwrite=overwrite)
+ assert consumption_state.get_energy_requirement_Wh(time_slot) == expected_energy
+ assert consumption_state.get_desired_energy_Wh(time_slot) == expected_energy
def test_update_total_demanded_energy_respects_addition(self):
time_slot, consumption_state = self._setup_default_test_consumption_state()
@@ -81,61 +66,67 @@ def test_update_total_demanded_energy_respects_addition(self):
consumption_state.update_total_demanded_energy(time_slot)
consumption_state.update_total_demanded_energy(time_slot)
consumption_state.update_total_demanded_energy(time_slot)
- assert (consumption_state.get_state()["total_energy_demanded_Wh"]
- == 4 * initial_demanded_energy)
+ assert (
+ consumption_state.get_state()["total_energy_demanded_Wh"]
+ == 4 * initial_demanded_energy
+ )
# For non-pre-existing time_slots
consumption_state.update_total_demanded_energy(time_slot.add(minutes=15))
- assert (consumption_state.get_state()["total_energy_demanded_Wh"]
- == 4 * initial_demanded_energy)
+ assert (
+ consumption_state.get_state()["total_energy_demanded_Wh"]
+ == 4 * initial_demanded_energy
+ )
def test_can_buy_more_energy_time_slot_not_registered_default_false(self):
time_slot, consumption_state = self._setup_default_test_consumption_state()
- assert consumption_state.can_buy_more_energy(
- time_slot=time_slot.add(minutes=15)) is False
+ assert consumption_state.can_buy_more_energy(time_slot=time_slot.add(minutes=15)) is False
@pytest.mark.parametrize(
- "set_energy, expected_bool", [(1, True), (FLOATING_POINT_TOLERANCE, False)])
+ "set_energy, expected_bool", [(1, True), (FLOATING_POINT_TOLERANCE, False)]
+ )
def test_can_buy_more_energy_set_energy_requirement_return_expected(
- self, set_energy, expected_bool):
+ self, set_energy, expected_bool
+ ):
time_slot, consumption_state = self._setup_default_test_consumption_state()
- consumption_state.set_desired_energy(energy=set_energy,
- time_slot=time_slot,
- overwrite=True)
- assert consumption_state.can_buy_more_energy(
- time_slot=time_slot) is expected_bool
+ consumption_state.set_desired_energy(
+ energy=set_energy, time_slot=time_slot, overwrite=True
+ )
+ assert consumption_state.can_buy_more_energy(time_slot=time_slot) is expected_bool
def test_decrement_energy_requirement_respects_subtraction(self):
time_slot, consumption_state = self._setup_default_test_consumption_state()
initial_energy_req = consumption_state.get_energy_requirement_Wh(time_slot)
purchased_energy = 0.3
- consumption_state.decrement_energy_requirement(purchased_energy_Wh=purchased_energy,
- time_slot=time_slot,
- area_name="test_area")
- consumption_state.decrement_energy_requirement(purchased_energy_Wh=purchased_energy,
- time_slot=time_slot,
- area_name="test_area")
+ consumption_state.decrement_energy_requirement(
+ purchased_energy_Wh=purchased_energy, time_slot=time_slot, area_name="test_area"
+ )
+ consumption_state.decrement_energy_requirement(
+ purchased_energy_Wh=purchased_energy, time_slot=time_slot, area_name="test_area"
+ )
assert isclose(
consumption_state.get_energy_requirement_Wh(time_slot),
- initial_energy_req - 2 * purchased_energy
+ initial_energy_req - 2 * purchased_energy,
)
def test_decrement_energy_requirement_purchase_more_than_possible_raise_error(self):
time_slot, consumption_state = self._setup_default_test_consumption_state()
purchased_energy = 1.2
with pytest.raises(AssertionError) as error:
- consumption_state.decrement_energy_requirement(purchased_energy_Wh=purchased_energy,
- time_slot=time_slot,
- area_name="test_area")
- assert ("Energy requirement for device test_area fell below zero "
- ) in str(error.value)
+ consumption_state.decrement_energy_requirement(
+ purchased_energy_Wh=purchased_energy, time_slot=time_slot, area_name="test_area"
+ )
+ assert "Energy requirement for device test_area fell below zero " in str(error.value)
def test_delete_past_state_values_market_slot_not_in_past(self):
past_time_slot, consumption_state = self._setup_default_test_consumption_state()
current_time_slot = past_time_slot.add(minutes=15)
consumption_state.set_desired_energy(energy=1, time_slot=past_time_slot)
consumption_state.set_desired_energy(energy=1, time_slot=current_time_slot)
- with patch("gsy_e.gsy_e_core.util.ConstSettings.SettlementMarketSettings."
- "ENABLE_SETTLEMENT_MARKETS", True):
+ with patch(
+ "gsy_e.gsy_e_core.util.ConstSettings.SettlementMarketSettings."
+ "ENABLE_SETTLEMENT_MARKETS",
+ True,
+ ):
consumption_state.delete_past_state_values(past_time_slot)
assert consumption_state.get_energy_requirement_Wh(past_time_slot) is not None
assert consumption_state.get_desired_energy_Wh(past_time_slot) is not None
@@ -145,10 +136,16 @@ def test_delete_past_state_values_market_slot_in_past(self):
current_time_slot = past_time_slot.add(minutes=15)
consumption_state.set_desired_energy(energy=1, time_slot=past_time_slot)
consumption_state.set_desired_energy(energy=1, time_slot=current_time_slot)
- with patch("gsy_e.gsy_e_core.util.ConstSettings.SettlementMarketSettings."
- "ENABLE_SETTLEMENT_MARKETS", False):
+ with patch(
+ "gsy_e.gsy_e_core.util.ConstSettings.SettlementMarketSettings."
+ "ENABLE_SETTLEMENT_MARKETS",
+ False,
+ ):
consumption_state.delete_past_state_values(current_time_slot)
- assert consumption_state.get_energy_requirement_Wh(past_time_slot,
- default_value=100) == 100
- assert consumption_state.get_desired_energy_Wh(past_time_slot,
- default_value=100) == 100
+ assert (
+ consumption_state.get_energy_requirement_Wh(past_time_slot, default_value=100)
+ == 100
+ )
+ assert (
+ consumption_state.get_desired_energy_Wh(past_time_slot, default_value=100) == 100
+ )
diff --git a/tests/test_state/test_storage_state.py b/tests/test_state/test_storage_state.py
index 609566bdf..6dde38d98 100644
--- a/tests/test_state/test_storage_state.py
+++ b/tests/test_state/test_storage_state.py
@@ -4,7 +4,7 @@
import pytest
from pendulum import now, duration
-from gsy_e.constants import FLOATING_POINT_TOLERANCE
+from gsy_framework.constants_limits import FLOATING_POINT_TOLERANCE
from gsy_e.models.strategy.state import StorageState, ESSEnergyOrigin, EnergyOrigin
SAMPLE_STATE = {
@@ -45,21 +45,24 @@ def test_market_cycle_reset_orders(self):
storage_state.add_default_values_to_state_profiles(active_market_slot_time_list)
storage_state.offered_buy_kWh[future_time_slots[0]] = 10
storage_state.offered_sell_kWh[future_time_slots[0]] = 10
- with patch("gsy_e.models.strategy.state.storage_state.ConstSettings.FutureMarketSettings."
- "FUTURE_MARKET_DURATION_HOURS", 5):
+ with patch(
+ "gsy_e.models.strategy.state.storage_state.ConstSettings.FutureMarketSettings."
+ "FUTURE_MARKET_DURATION_HOURS",
+ 5,
+ ):
storage_state.market_cycle(past_time_slot, current_time_slot, future_time_slots)
# The future_time_slots[0] is in the future, so it won't reset
assert storage_state.offered_buy_kWh[future_time_slots[0]] == 10
assert storage_state.offered_sell_kWh[future_time_slots[0]] == 10
storage_state.market_cycle(
- current_time_slot, future_time_slots[0], future_time_slots[1:])
+ current_time_slot, future_time_slots[0], future_time_slots[1:]
+ )
# The future_time_slots[0] is in the spot market, so it has to reset the orders
assert storage_state.offered_buy_kWh[future_time_slots[0]] == 0
assert storage_state.offered_sell_kWh[future_time_slots[0]] == 0
def test_market_cycle_update_used_storage(self):
- storage_state = StorageState(initial_soc=100,
- capacity=100)
+ storage_state = StorageState(initial_soc=100, capacity=100)
past_time_slot, current_time_slot, future_time_slots = self._initialize_time_slots()
active_market_slot_time_list = [past_time_slot, current_time_slot, *future_time_slots]
storage_state.add_default_values_to_state_profiles(active_market_slot_time_list)
@@ -73,93 +76,97 @@ def test_market_cycle_update_used_storage(self):
assert storage_state.used_storage == 100
def test_market_cycle_ess_share_time_series_dict(self):
- storage_state = StorageState(initial_soc=100,
- capacity=100,
- initial_energy_origin=ESSEnergyOrigin.LOCAL,
- max_abs_battery_power_kW=15)
+ storage_state = StorageState(
+ initial_soc=100,
+ capacity=100,
+ initial_energy_origin=ESSEnergyOrigin.LOCAL,
+ max_abs_battery_power_kW=15,
+ )
past_time_slot, current_time_slot, future_time_slots = self._initialize_time_slots()
active_market_slot_time_list = [past_time_slot, current_time_slot, *future_time_slots]
storage_state.add_default_values_to_state_profiles(active_market_slot_time_list)
energy = 0.3
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
storage_state.register_energy_from_one_sided_market_accept_offer(
- energy, past_time_slot, ESSEnergyOrigin.LOCAL)
+ energy, past_time_slot, ESSEnergyOrigin.LOCAL
+ )
storage_state.register_energy_from_one_sided_market_accept_offer(
- energy, past_time_slot, ESSEnergyOrigin.UNKNOWN)
+ energy, past_time_slot, ESSEnergyOrigin.UNKNOWN
+ )
storage_state.register_energy_from_one_sided_market_accept_offer(
- energy, past_time_slot, ESSEnergyOrigin.UNKNOWN)
+ energy, past_time_slot, ESSEnergyOrigin.UNKNOWN
+ )
storage_state.market_cycle(past_time_slot, current_time_slot, future_time_slots)
expected_ess_share_last_market = {
ESSEnergyOrigin.LOCAL: storage_state.initial_capacity_kWh + energy,
ESSEnergyOrigin.EXTERNAL: 0.0,
- ESSEnergyOrigin.UNKNOWN: 2 * energy
+ ESSEnergyOrigin.UNKNOWN: 2 * energy,
}
- assert (storage_state.time_series_ess_share[past_time_slot] ==
- expected_ess_share_last_market)
+ assert (
+ storage_state.time_series_ess_share[past_time_slot] == expected_ess_share_last_market
+ )
def test_market_cycle_calculate_and_update_soc_and_set_offer_history(self):
- storage_state = StorageState(initial_soc=100,
- capacity=100,
- min_allowed_soc=20)
+ storage_state = StorageState(initial_soc=100, capacity=100, min_allowed_soc=20)
past_time_slot, current_time_slot, future_time_slots = self._initialize_time_slots()
active_market_slot_time_list = [past_time_slot, current_time_slot, *future_time_slots]
storage_state.add_default_values_to_state_profiles(active_market_slot_time_list)
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
storage_state.pledged_sell_kWh[past_time_slot] = 10
storage_state.market_cycle(past_time_slot, current_time_slot, future_time_slots)
assert storage_state.charge_history[current_time_slot] != storage_state.initial_soc
- assert (storage_state.charge_history_kWh[current_time_slot] !=
- storage_state.initial_capacity_kWh)
+ assert (
+ storage_state.charge_history_kWh[current_time_slot]
+ != storage_state.initial_capacity_kWh
+ )
assert storage_state.offered_history[current_time_slot] != "-"
@staticmethod
def _initialize_time_slots():
past_time_slot = now()
current_time_slot = past_time_slot.add(minutes=15)
- future_time_slots = [current_time_slot.add(minutes=15),
- current_time_slot.add(minutes=30)]
+ future_time_slots = [current_time_slot.add(minutes=15), current_time_slot.add(minutes=30)]
return past_time_slot, current_time_slot, future_time_slots
@staticmethod
def test_check_state_charge_less_than_min_soc_error():
- storage_state = StorageState(capacity=100,
- min_allowed_soc=50,
- initial_soc=30)
+ storage_state = StorageState(capacity=100, min_allowed_soc=50, initial_soc=30)
current_time_slot = now()
storage_state.add_default_values_to_state_profiles([current_time_slot])
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
with pytest.raises(AssertionError) as error:
storage_state.check_state(current_time_slot)
assert "less than min soc" in str(error.value)
@staticmethod
def test_check_state_storage_surpasses_capacity_error():
- storage_state = StorageState(capacity=100,
- min_allowed_soc=50,
- initial_soc=110)
+ storage_state = StorageState(capacity=100, min_allowed_soc=50, initial_soc=110)
current_time_slot = now()
storage_state.add_default_values_to_state_profiles([current_time_slot])
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
with pytest.raises(AssertionError) as error:
storage_state.check_state(current_time_slot)
assert "surpassed the capacity" in str(error.value)
@staticmethod
def test_check_state_offered_and_pledged_energy_in_range():
- storage_state = StorageState(capacity=100,
- min_allowed_soc=20,
- initial_soc=50)
+ storage_state = StorageState(capacity=100, min_allowed_soc=20, initial_soc=50)
current_time_slot = now()
storage_state.add_default_values_to_state_profiles([current_time_slot])
max_value = storage_state.capacity * (1 - storage_state.min_allowed_soc_ratio)
storage_state.max_abs_battery_power_kW = max_value * 15
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
def set_attribute_value_and_test(attribute):
attribute[current_time_slot] = -1
@@ -186,10 +193,12 @@ def set_attribute_value_and_test(attribute):
def _setup_storage_state_for_clamp_energy():
storage_state = StorageState()
current_time_slot = now()
- market_slot_list = [current_time_slot,
- current_time_slot + duration(minutes=15),
- current_time_slot + duration(minutes=30),
- current_time_slot + duration(minutes=45)]
+ market_slot_list = [
+ current_time_slot,
+ current_time_slot + duration(minutes=15),
+ current_time_slot + duration(minutes=30),
+ current_time_slot + duration(minutes=45),
+ ]
storage_state._current_market_slot = current_time_slot
storage_state._used_storage = 250.0
storage_state.capacity = 500.0
@@ -207,8 +216,9 @@ def test_clamp_energy_to_sell_kWh_only_on_first_slot(self):
storage_state._battery_energy_per_slot = storage_state.capacity
storage_state._clamp_energy_to_sell_kWh(market_slot_list)
expected_available_energy = (
- storage_state.used_storage -
- storage_state.min_allowed_soc_ratio * storage_state.capacity)
+ storage_state.used_storage
+ - storage_state.min_allowed_soc_ratio * storage_state.capacity
+ )
expected_energy_to_sell = {
market_slot_list[0]: expected_available_energy,
market_slot_list[1]: 0.0,
@@ -313,14 +323,13 @@ def test_get_state_keys_in_dict():
assert set(SAMPLE_STATE.keys()).issubset(storage_state.get_state().keys())
def test_restore_state(self):
- storage_state = StorageState(initial_soc=100,
- capacity=100,
- min_allowed_soc=20)
+ storage_state = StorageState(initial_soc=100, capacity=100, min_allowed_soc=20)
past_time_slot, current_time_slot, future_time_slots = self._initialize_time_slots()
active_market_slot_time_list = [past_time_slot, current_time_slot, *future_time_slots]
storage_state.add_default_values_to_state_profiles(active_market_slot_time_list)
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
past_state = storage_state.get_state()
storage_state.pledged_sell_kWh[current_time_slot] = 50
modified_state = storage_state.get_state()
@@ -330,9 +339,7 @@ def test_restore_state(self):
@staticmethod
def test_free_storage_return_float():
- storage_state = StorageState(initial_soc=100,
- capacity=100,
- min_allowed_soc=20)
+ storage_state = StorageState(initial_soc=100, capacity=100, min_allowed_soc=20)
current_time_slot = now()
storage_state.add_default_values_to_state_profiles([current_time_slot])
assert isinstance(storage_state.free_storage(current_time_slot), float)
@@ -343,7 +350,8 @@ def test_activate_convert_energy_to_power():
storage_state = StorageState()
current_time_slot = now()
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
mocked_func.assert_called()
def test_add_default_values_to_state_profiles_set_values_for_time_slots(self):
@@ -357,27 +365,40 @@ def assert_time_slot_in_dict_attribute_with_default_value(attribute, time_slot,
for time_slot in future_time_slots:
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.pledged_sell_kWh, time_slot, 0)
+ storage_state.pledged_sell_kWh, time_slot, 0
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.pledged_buy_kWh, time_slot, 0)
+ storage_state.pledged_buy_kWh, time_slot, 0
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.offered_sell_kWh, time_slot, 0)
+ storage_state.offered_sell_kWh, time_slot, 0
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.offered_buy_kWh, time_slot, 0)
+ storage_state.offered_buy_kWh, time_slot, 0
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.charge_history, time_slot, storage_state.initial_soc)
+ storage_state.charge_history, time_slot, storage_state.initial_soc
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.charge_history_kWh, time_slot, storage_state.initial_capacity_kWh)
+ storage_state.charge_history_kWh, time_slot, storage_state.initial_capacity_kWh
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.energy_to_sell_dict, time_slot, 0)
+ storage_state.energy_to_sell_dict, time_slot, 0
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.energy_to_buy_dict, time_slot, 0)
+ storage_state.energy_to_buy_dict, time_slot, 0
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.offered_history, time_slot, "-")
+ storage_state.offered_history, time_slot, "-"
+ )
assert_time_slot_in_dict_attribute_with_default_value(
- storage_state.time_series_ess_share, time_slot, {ESSEnergyOrigin.UNKNOWN: 0.,
- ESSEnergyOrigin.LOCAL: 0.,
- ESSEnergyOrigin.EXTERNAL: 0.}
+ storage_state.time_series_ess_share,
+ time_slot,
+ {
+ ESSEnergyOrigin.UNKNOWN: 0.0,
+ ESSEnergyOrigin.LOCAL: 0.0,
+ ESSEnergyOrigin.EXTERNAL: 0.0,
+ },
)
def test_delete_past_state_values_market_slot_not_in_past(self):
@@ -385,8 +406,11 @@ def test_delete_past_state_values_market_slot_not_in_past(self):
past_time_slot, current_time_slot, future_time_slots = self._initialize_time_slots()
active_market_slot_time_list = [past_time_slot, current_time_slot, *future_time_slots]
storage_state.add_default_values_to_state_profiles(active_market_slot_time_list)
- with patch("gsy_e.gsy_e_core.util.ConstSettings.SettlementMarketSettings."
- "ENABLE_SETTLEMENT_MARKETS", True):
+ with patch(
+ "gsy_e.gsy_e_core.util.ConstSettings.SettlementMarketSettings."
+ "ENABLE_SETTLEMENT_MARKETS",
+ True,
+ ):
storage_state.delete_past_state_values(current_time_slot)
assert storage_state.pledged_sell_kWh.get(past_time_slot) is not None
@@ -395,121 +419,136 @@ def test_delete_past_state_values_market_slot_in_past(self):
past_time_slot, current_time_slot, future_time_slots = self._initialize_time_slots()
active_market_slot_time_list = [past_time_slot, current_time_slot, *future_time_slots]
storage_state.add_default_values_to_state_profiles(active_market_slot_time_list)
- with patch("gsy_e.gsy_e_core.util.ConstSettings.SettlementMarketSettings."
- "ENABLE_SETTLEMENT_MARKETS", False):
+ with patch(
+ "gsy_e.gsy_e_core.util.ConstSettings.SettlementMarketSettings."
+ "ENABLE_SETTLEMENT_MARKETS",
+ False,
+ ):
storage_state.delete_past_state_values(current_time_slot)
assert storage_state.pledged_sell_kWh.get(past_time_slot) is None
def test_register_energy_from_posted_bid_negative_energy_raise_error(self):
storage_state, current_time_slot = self._setup_registration_test()
self._assert_negative_energy_raise_error(
- storage_state.register_energy_from_posted_bid, time_slot=current_time_slot)
+ storage_state.register_energy_from_posted_bid, time_slot=current_time_slot
+ )
def test_register_energy_from_posted_bid_energy_add_energy_to_offered_buy_dict(self):
storage_state, current_time_slot = self._setup_registration_test()
bid_energy = 1
storage_state.register_energy_from_posted_bid(
- energy=bid_energy, time_slot=current_time_slot)
+ energy=bid_energy, time_slot=current_time_slot
+ )
storage_state.register_energy_from_posted_bid(
- energy=bid_energy, time_slot=current_time_slot)
+ energy=bid_energy, time_slot=current_time_slot
+ )
assert storage_state.offered_buy_kWh[current_time_slot] == 2 * bid_energy
def test_register_energy_from_posted_offer_negative_energy_raise_error(self):
storage_state, current_time_slot = self._setup_registration_test()
self._assert_negative_energy_raise_error(
- storage_state.register_energy_from_posted_offer,
- time_slot=current_time_slot)
+ storage_state.register_energy_from_posted_offer, time_slot=current_time_slot
+ )
def test_register_energy_from_posted_offer_energy_add_energy_to_offer_sell_dict(self):
storage_state, current_time_slot = self._setup_registration_test()
offered_energy = 1
storage_state.register_energy_from_posted_offer(
- energy=offered_energy, time_slot=current_time_slot)
+ energy=offered_energy, time_slot=current_time_slot
+ )
storage_state.register_energy_from_posted_offer(
- energy=offered_energy, time_slot=current_time_slot)
+ energy=offered_energy, time_slot=current_time_slot
+ )
assert storage_state.offered_sell_kWh[current_time_slot] == 2 * offered_energy
def test_reset_offered_sell_energy_negative_energy_raise_error(self):
storage_state, current_time_slot = self._setup_registration_test()
self._assert_negative_energy_raise_error(
- storage_state.reset_offered_sell_energy, time_slot=current_time_slot)
+ storage_state.reset_offered_sell_energy, time_slot=current_time_slot
+ )
def test_reset_offered_sell_energy_energy_in_offered_sell_dict(self):
storage_state, current_time_slot = self._setup_registration_test()
offered_sell_energy = 1
storage_state.reset_offered_sell_energy(
- energy=offered_sell_energy, time_slot=current_time_slot)
+ energy=offered_sell_energy, time_slot=current_time_slot
+ )
assert storage_state.offered_sell_kWh[current_time_slot] == offered_sell_energy
def test_reset_offered_buy_energy_negative_energy_raise_error(self):
storage_state, current_time_slot = self._setup_registration_test()
self._assert_negative_energy_raise_error(
- storage_state.reset_offered_buy_energy, time_slot=current_time_slot)
+ storage_state.reset_offered_buy_energy, time_slot=current_time_slot
+ )
def test_reset_offered_buy_energy_energy_in_offered_buy_dict(self):
storage_state, current_time_slot = self._setup_registration_test()
offered_buy_energy = 1
storage_state.reset_offered_buy_energy(
- energy=offered_buy_energy, time_slot=current_time_slot)
+ energy=offered_buy_energy, time_slot=current_time_slot
+ )
assert storage_state.offered_buy_kWh[current_time_slot] == offered_buy_energy
def test_remove_energy_from_deleted_offer_negative_energy_raise_error(self):
storage_state, current_time_slot = self._setup_registration_test()
self._assert_negative_energy_raise_error(
- storage_state.remove_energy_from_deleted_offer, time_slot=current_time_slot)
+ storage_state.remove_energy_from_deleted_offer, time_slot=current_time_slot
+ )
def test_remove_energy_from_deleted_offer_energy_in_offered_buy_dict(self):
storage_state, current_time_slot = self._setup_registration_test()
offered_energy = 1
storage_state.offered_sell_kWh[current_time_slot] = offered_energy
storage_state.remove_energy_from_deleted_offer(
- energy=offered_energy, time_slot=current_time_slot)
+ energy=offered_energy, time_slot=current_time_slot
+ )
assert storage_state.offered_buy_kWh[current_time_slot] == 0
def test_register_energy_from_one_sided_market_accept_offer_negative_energy_raise_error(self):
storage_state, current_time_slot = self._setup_registration_test()
self._assert_negative_energy_raise_error(
storage_state.register_energy_from_one_sided_market_accept_offer,
- time_slot=current_time_slot)
+ time_slot=current_time_slot,
+ )
def test_register_energy_from_one_sided_market_accept_offer_energy_register_in_dict(self):
storage_state, current_time_slot = self._setup_registration_test()
energy = 1
storage_state.register_energy_from_one_sided_market_accept_offer(
- energy=energy, time_slot=current_time_slot)
+ energy=energy, time_slot=current_time_slot
+ )
storage_state.register_energy_from_one_sided_market_accept_offer(
- energy=energy, time_slot=current_time_slot)
+ energy=energy, time_slot=current_time_slot
+ )
assert storage_state.pledged_buy_kWh[current_time_slot] == 2 * energy
def test_register_energy_from_bid_trade_negative_energy_raise_error(self):
storage_state, current_time_slot = self._setup_registration_test()
self._assert_negative_energy_raise_error(
- storage_state.register_energy_from_bid_trade, time_slot=current_time_slot)
+ storage_state.register_energy_from_bid_trade, time_slot=current_time_slot
+ )
def test_register_energy_from_bid_trade_energy_register_in_dict(self):
storage_state, current_time_slot = self._setup_registration_test()
energy = 1
storage_state.offered_buy_kWh[current_time_slot] = 2 * energy
- storage_state.register_energy_from_bid_trade(
- energy=energy, time_slot=current_time_slot)
- storage_state.register_energy_from_bid_trade(
- energy=energy, time_slot=current_time_slot)
+ storage_state.register_energy_from_bid_trade(energy=energy, time_slot=current_time_slot)
+ storage_state.register_energy_from_bid_trade(energy=energy, time_slot=current_time_slot)
assert storage_state.pledged_buy_kWh[current_time_slot] == 2 * energy
assert storage_state.offered_buy_kWh[current_time_slot] == 0
def test_register_energy_from_offer_trade_negative_energy_raise_error(self):
storage_state, current_time_slot = self._setup_registration_test()
self._assert_negative_energy_raise_error(
- storage_state.register_energy_from_offer_trade, time_slot=current_time_slot)
+ storage_state.register_energy_from_offer_trade, time_slot=current_time_slot
+ )
def test_register_energy_from_offer_trade_energy_register_in_dict(self):
storage_state, current_time_slot = self._setup_registration_test()
energy = 1
storage_state.offered_sell_kWh[current_time_slot] = 2 * energy
- storage_state.register_energy_from_offer_trade(
- energy=energy, time_slot=current_time_slot)
- storage_state.register_energy_from_offer_trade(
- energy=energy, time_slot=current_time_slot)
+ storage_state.register_energy_from_offer_trade(energy=energy, time_slot=current_time_slot)
+ storage_state.register_energy_from_offer_trade(energy=energy, time_slot=current_time_slot)
assert storage_state.pledged_sell_kWh[current_time_slot] == 2 * energy
assert storage_state.offered_sell_kWh[current_time_slot] == 0
@@ -518,7 +557,8 @@ def _setup_registration_test(self):
current_time_slot, _, _ = self._initialize_time_slots()
storage_state.add_default_values_to_state_profiles([current_time_slot])
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
return storage_state, current_time_slot
@staticmethod
@@ -530,9 +570,11 @@ def _assert_negative_energy_raise_error(method, time_slot):
@staticmethod
def _setup_storage_state_for_energy_origin_tracking():
storage_state = StorageState()
- used_storage_share = [EnergyOrigin(ESSEnergyOrigin.LOCAL, 0.4),
- EnergyOrigin(ESSEnergyOrigin.EXTERNAL, 0.5),
- EnergyOrigin(ESSEnergyOrigin.UNKNOWN, 0.1)]
+ used_storage_share = [
+ EnergyOrigin(ESSEnergyOrigin.LOCAL, 0.4),
+ EnergyOrigin(ESSEnergyOrigin.EXTERNAL, 0.5),
+ EnergyOrigin(ESSEnergyOrigin.UNKNOWN, 0.1),
+ ]
storage_state._used_storage_share = used_storage_share
return storage_state
@@ -554,7 +596,8 @@ def test_track_energy_sell_type_sell_all_energy(self):
storage_state._track_energy_sell_type(energy=available_energy_for_sell)
if len(storage_state._used_storage_share) != 0:
assert isclose(
- storage_state._used_storage_share[0].value, 0, abs_tol=FLOATING_POINT_TOLERANCE)
+ storage_state._used_storage_share[0].value, 0, abs_tol=FLOATING_POINT_TOLERANCE
+ )
else:
assert len(storage_state._used_storage_share) == 0
@@ -573,22 +616,21 @@ def test_track_energy_sell_type_sell_only_first_entry_completely(self):
initial_registry_number = len(storage_state._used_storage_share)
original_used_storage_share = storage_state._used_storage_share.copy()
storage_state._track_energy_sell_type(energy=energy_for_sale)
- assert (len(storage_state._used_storage_share) ==
- initial_registry_number - 1)
- assert (storage_state._used_storage_share[0].origin ==
- original_used_storage_share[1].origin)
+ assert len(storage_state._used_storage_share) == initial_registry_number - 1
+ assert storage_state._used_storage_share[0].origin == original_used_storage_share[1].origin
assert isclose(
storage_state._used_storage_share[0].value,
original_used_storage_share[1].value,
- abs_tol=FLOATING_POINT_TOLERANCE)
+ abs_tol=FLOATING_POINT_TOLERANCE,
+ )
def test_get_soc_level_default_values_and_custom_values(self):
- storage_state = StorageState(initial_soc=100,
- capacity=100)
+ storage_state = StorageState(initial_soc=100, capacity=100)
current_time_slot, _, _ = self._initialize_time_slots()
storage_state.add_default_values_to_state_profiles([current_time_slot])
storage_state.activate(
- slot_length=duration(minutes=15), current_time_slot=current_time_slot)
+ slot_length=duration(minutes=15), current_time_slot=current_time_slot
+ )
assert storage_state.get_soc_level(current_time_slot) == 1
storage_state.charge_history[current_time_slot] = 50
assert storage_state.get_soc_level(current_time_slot) == 0.5
@@ -607,15 +649,15 @@ def test_to_dict_keys_in_return_dict(self):
storage_state._used_storage = used_storage_mock
assert set(SAMPLE_STATS.keys()).issubset(storage_state.to_dict(current_time_slot).keys())
- assert (storage_state.to_dict(current_time_slot)["energy_to_sell"] ==
- "test_energy_to_sell")
- assert (storage_state.to_dict(current_time_slot)["energy_active_in_bids"] ==
- "test_energy_active_in_bids")
- assert (storage_state.to_dict(current_time_slot)["energy_to_buy"] ==
- "test_energy_to_buy")
- assert (storage_state.to_dict(current_time_slot)["energy_active_in_offers"] ==
- "test_energy_active_in_offers")
- assert (storage_state.to_dict(current_time_slot)["free_storage"] ==
- "test_free_storage")
- assert (storage_state.to_dict(current_time_slot)["used_storage"] ==
- used_storage_mock)
+ assert storage_state.to_dict(current_time_slot)["energy_to_sell"] == "test_energy_to_sell"
+ assert (
+ storage_state.to_dict(current_time_slot)["energy_active_in_bids"]
+ == "test_energy_active_in_bids"
+ )
+ assert storage_state.to_dict(current_time_slot)["energy_to_buy"] == "test_energy_to_buy"
+ assert (
+ storage_state.to_dict(current_time_slot)["energy_active_in_offers"]
+ == "test_energy_active_in_offers"
+ )
+ assert storage_state.to_dict(current_time_slot)["free_storage"] == "test_free_storage"
+ assert storage_state.to_dict(current_time_slot)["used_storage"] == used_storage_mock
diff --git a/tests/test_stats.py b/tests/test_stats.py
index a9fa1f96a..b3f005a25 100644
--- a/tests/test_stats.py
+++ b/tests/test_stats.py
@@ -22,6 +22,7 @@
from uuid import uuid4
import pytest
+from gsy_framework.constants_limits import TIME_ZONE, DATE_TIME_FORMAT
from gsy_framework.data_classes import Trade, TraderDetails
from gsy_framework.sim_results.bills import MarketEnergyBills
from gsy_framework.unit_test_utils import (
@@ -93,13 +94,11 @@ class FakeMarket:
def __init__(self, trades, name="Area", fees=0.0):
self.name = name
self.trades = trades
- self.time_slot = today(tz=constants.TIME_ZONE)
+ self.time_slot = today(tz=TIME_ZONE)
self.market_fee = fees
self.const_fee_rate = fees
self.time_slot_str = (
- self.time_slot.format(constants.DATE_TIME_FORMAT)
- if self.time_slot is not None
- else None
+ self.time_slot.format(DATE_TIME_FORMAT) if self.time_slot is not None else None
)
self.offer_history = []
self.bid_history = []
@@ -123,7 +122,7 @@ def serializable_dict(self):
def _trade(price, buyer, energy=1, seller=None, fee_price=0.0):
return Trade(
"id",
- now(tz=constants.TIME_ZONE),
+ now(tz=TIME_ZONE),
TraderDetails(seller, ""),
TraderDetails(buyer, ""),
energy,