# Reserve markets | | | |---|---| |License|yes| |Release version|10.6.1| The multi-market functionality enables Prodrisk to account for reserve sales in the optimization and cut calculation. Two reserve markets are implemented, one for up regulation and one for down regulation. For each reserve market, one can specify the maximum market capacity, an obligation to be fulfilled, the reserve price, the maximum allocation per module, and penalty values. All of these are time-dependent. Market input can be given with custom resolution, but will be automatically converted to the same price periods as defined for the spot market. Outputs are the reserve allocations per module per price period. Prodrisk also returns the reserve price as used in the optimization, i.e., with spot price resolution. This is a licensed functionality that is made available on purchase. ![](./images/reserve_markets_input_output.png) ## Assumptions - The reserve price is deterministic (same in each scenario). - Sales on all markets happen at the same time with spot price resolution. - There is no activation of reserves. ## Input format To activate the use of reserve markets, set **use_reserve_up** and/or **use_reserve_down** to 1 in the API, or in the file *prodrisk.cpar* when running from the command line. In the API, reserve market inputs are given as time-series attributes on the area object: **reserve_up_price**, **total_reserve_up_capacity**, **reserve_up_obligation**, **reserve_up_obligation_cost**, and analogously for **reserve_down_\***. When running from the command line, these inputs are contained in the new input file *Reserve.csv*, with corresponding attributes: **Reserve_Up_Price**, **Reserve_Up_Market_Capacity**, **Reserve_Up_Obligation**, **Reserve_Up_Obligation_Penalty**, and analogously for **Reserve_Down_\***. The file format is shown below: ![](./images/reserve_input.png) If only one value is given for a week, the same value is used for all price periods. If a week is omitted, the previous week is repeated. Default values are constant zero for obligation and price, and 30% of CTANK for the obligation cost. The market capacity is mandatory input. The inputs on the module level have weekly resolution, and are given through new input attributes **max_reserve_up**, **max_reserve_up_cost**, **max_reserve_down**, **max_reserve_down_cost** in the API. Default values are zero for the allocation limits, and 50% of CTANK for the associated costs. When running from the command line, the new inputs are given in *constraints.xml*, with corresponding attributes: **MaxCapUp**, **MaxCapUp_Penalty**, **MaxCapDown** and **MaxCapDown_Penalty**. The file format is shown below: ![](./images/reserve_constraints_xml_input.png) ## Output format The allocation per module per price period for each reserve market is accessed in the API through the attributes **reserve_up_allocation** and **reserve_down_allocation**, or in *detsimres.h5* under hydro_module_results as **up_regulation** and **down_regulation**. In the API there are additional area attributes **total_reserve_up_allocation**, **total_reserve_down_allocation** for the summed allocation of the whole system. The reserve prices with spot price resolution are written to the output attributes **output_reserve_up_price**, **output_reserve_down_price** on the area object, and to *ENMRES.h5*. ## Use with owner share If reserve markets are used together with owner share in the API, then the reserve limits for each module as well as the limits for each market correspond to the physical (total) limits. The API will scale module limits with the owner share of the module. Market limits are scaled with an aggregated owner share of the watercourse, i.e., the sum of all max_reserve_up weighted with the respective owner shares, divided by the (physical) sum of all max_reserve_up (analogously for max_reserve_down). ## Calculation time The impact of calculation time mainly depends on (1) the number of modules participating in the reserve market and (2) the relation of market limits and module limits. In a system where the majority of plants can sell reserves, a loose market limit (larger than the summed allocatable capacity of all module) may lead to an increase of calculation time on the order of 20%. In contrast, a tight market limit (similar to or below the summed allocatable capacity) can increase calculation times drastically (doubling is possible). Note that the optimization is more complex in the latter case: The solver finds the optimal distribution of the market limit on the available modules. Thus, users who already know this distribution from experience can save calculation time by setting module limits that sum up to the intended market limit, while setting a loose bound on the market. ## Example In this example, we show how to prepare input for a simple Prodrisk run with reserve markets, both through input files and in pyprodrisk. We consider a system with both up and down regulation, and the following dummy data: - **Modules:** *ModuleA* with id 101 and *ModuleB* with id 102 - **Price periods**: $5$ sequential periods with lengths $42$,$42$,$42$,$24$,$18$ - **Number of weeks**: $10$ - **Market capacity**: - Up regulation: $100$ (weeks 1-6), $120$ (weeks 7-10) - Down regulation: $80$ (constant) - **Market obligation**: - Up regulation: $20$ (constant) - Down regulation: $0$ (constant) - **Reserve price**: - Equal for up and down regulation - Weeks 1-8: - Price period 1-3: $12$ - Price period 4-5: $8$ - Weeks 9-10: - Price period 1-3: $15$ - Price period 4-5: $10$ - **ModuleA: Maximum allocation**: - Up regulation: $50$ (weeks 1-6), $60$ (weeks 7-10) - Down regulation: $50$ (constant) - **ModuleB: Maximum allocation**: - Up regulation: $50$ (constant) - Down regulation: $40$ (constant) When running Prodrisk from the command line, we need to edit *prodrisk.CPAR* and *constraints.xml*, as well as creating the new file *Reserve.csv*. ### prodrisk.CPAR In *prodrisk.CPAR*, we activate up and down regulation by setting **use_reserve_up 1,** **use_reserve_down 1,** ### constraints.xml In *constraints.xml*, the module-specific reserve market input is written: ![](./images/example_reserve_constraints_xml_input.png) ### Reserve.csv The remaining input is written in the new file *Reserve.csv*: ![](./images/example_reserve_input.png) ### PyProdrisk When using pyprodrisk, we activate up and down regulation through the setting attributes *use_reserve_up* and *use_reserve_down*. Reserve market input is written to a series of txy attributes: ``` prodrisk.use_reserve_up = 1 prodrisk.use_reserve_down = 1 area.total_reserve_up_capacity.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0, 6]], data=[100.0, 120.0])) area.reserve_up_obligation.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]], data=[20.0])) area.reserve_up_price.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0, 6]], data=[100.0, 120.0])) area.reserve_up_price.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=w) + pd.Timedelta(hours=i) for w in range(10) for i in [0,42,84,126,150]], data=[12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 15.0, 15.0, 15.0, 10.0, 10.0, 15.0, 15.0, 15.0, 10.0, 10.0])) area.total_reserve_down_capacity.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]], data=[80.0])) area.reserve_down_price.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=w) + pd.Timedelta(hours=i) for w in range(10) for i in [0,42,84,126,150]], data=[12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 12.0, 12.0, 12.0, 8.0, 8.0, 15.0, 15.0, 15.0, 10.0, 10.0, 15.0, 15.0, 15.0, 10.0, 10.0])) #ModuleA modA.max_reserve_up.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0, 6]], data=[50.0, 60.0])) modA.max_reserve_down.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]], data=[50.0])) #ModuleB modB.max_reserve_up.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]], data=[50.0])) modB.max_reserve_down.set(pd.Series(name=0.0, index = [prodrisk.start_time + pd.Timedelta(weeks=i) for i in [0]], data=[40.0])) ```