In this post, I discuss an energy storage model to analyze the energy arbitrage potential in a day-ahead electricity market. Energy arbitraging takes advantage of the price variations in the market where energy is bought at low prices and sold at high prices, thereby producing a profit. However, the profit depends on several factors, such as the price spread (difference between low and high prices), accuracy of the price forecast, and the storage specifications (maximum charging/discharging power, capacity, efficiency).
Here, the focus is on the energy storage model and how its specifications affect the arbitrage potential. I use the model to determine the energy arbitrage potential in the Austrian day-ahead electricity market with various storage specifications by means of an optimization.
Energy storage model
The energy storage model is described by one function, two constraints, and two parameters, which are described in the following. Bold parameters represent the naming in the Python script whereas x is the power of the storage (optimization variable).
- Constraint 1: Storage capacity (MWh): 0 ≤ E_storage ≤ spec_Emax
- Constraint 2: Max. charging/discharging power (MW): – spec_Pminmax ≤ x ≤ + spec_Pminmax
- Parameter 1: Efficiency for charging and discharging (per unit): spec_eta
- Parameter 2: Initial energy content of the storage (MWh): spec_E0
Function: The actual energy content of the storage E_storage (MWh) is modeled as shown in the code snippet below.
def fun_E_storage(x): #pre-allocation of variables P_charg = np.zeros(x.shape) P_discharg = np.zeros(x.shape) #get charging and discharging of storage: (-)discharging, (+)charging idx_charg = x > 0 idx_discharg = x < 0 P_charg[idx_charg] = x[idx_charg] P_discharg[idx_discharg] = x[idx_discharg] #calculate energy content of storage; consider efficiency for charging and discharging #energy per time step dt E_storage_dt = P_charg * dt * spec_eta + P_discharg * dt * 1/spec_eta #cumulative energy over optimization horizon E_storage = np.cumsum(E_storage_dt) + E0_0 return E_storage
Since the model is used in an optimization, x represents the optimization variable which is a vector of the active power of the storage over the optimization horizon. The cumulative energy content E_storage over the optimization horizon is then calculated considering the efficiency spec_eta for charging and discharging and the time step dt. The energy content must be within the storage capacity (constraint 1) at each time step.
Important: For charging, the energy drawn from the grid is multiplied by the efficiency. For discharging, the energy drawn from the storage is divided by the efficiency. In physical terms, this describes the losses, i.e. less energy arrives in the storage than drawn from the grid and vice versa.
Objective function of the optimization
The objective function represents the cost over the optimization horizon. The variable x represents again the optimization variable (power of the storage), dt the time step, and opt_price the price per time step.
# objective function: minimize costs (negative cost equalts revenue) def obj_fun(x): cost = np.sum(x * dt * opt_price) return cost
Specifications of the optimization
The goal of the optimization is to minimize the costs (maximize the revenue) of the above objective function, given the price and the constraints. This basically translates into charging (= buying) when prices are low and discharging (= selling) when prices are high. The optimization determines the optimal storage schedule under consideration of all the constraints discussed above.
The optimization allows defining the optimization horizon opt_horizon. For example, if you run the optimization on one year of data with an optimization horizon of 24 h, an optimization for each 24-h-interval is carried out. The state-of-charge (SOC) at the end of each interval is considered in the following interval.
Below an example of the arbitrage results for one week with an optimization horizon of 24 hours. The storage parameters are: 1 MWh / ± 1 MW / spec_eta = 0.9 pu / spec_E0 = 0 MWh. You can see nicely that the storage charges at low prices and discharges at high prices producing revenue of 70 Euros. The state-of-charge (SOC) of the storage is always between zero and 100 percent satisfying the energy constraint. The cumulative revenue shows how the revenue changes over time and where it stands at the end of the time period showing the overall revenue.
Use Case: Energy arbitrage potential in the Austrian day-ahead electricity market under various storage specifications
In this section, I use the energy storage model is used to determine the arbitrage potential in Austria using the day-ahead electricity prices from 2020.
I queried the day-ahead price with Python from the ENTSO-E transparency platform: Check this on how to query data from ENTSO-E.
The results are computed based on the Austrian day-ahead electricity prices from 2020. The table below shows the summary of the arbitrage potential for different storage specifications and optimization horizons. The initial energy is set to zero in all cases.
|Optimization horizon |
|Potential yearly revenue |
|2||1||1||0.91||24||9088 (+5 %)|
|3||1||1||0.95||24||10904 (+26 %)|
|4||2||2||0.9||24||17370 (+100 %)|
|5||5||5||0.9||24||43402 (+401 %)|
|6||1||1||0.9||12||8150 (-6 %)|
|7||1||1||0.9||48||8712 (+0.5 %)|
|8||1||1||0.9||168||8742 (+0.9 %)|
The results in the table above offer some interesting conclusions. The arbitrage potential in scenario 1 is only 8668 €. Assuming a storage cost of 250.000 €/MWh (in 2020) it would take almost 29 years to return our investment. This is not very encouraging to get into the arbitraging business.
If you look at scenarios 2 and 3 with increased storage efficiency, the potential increases by 5 % and 26 %, respectively. Therefore, we can conclude that the efficiency of the storage has a significant impact on the potential.
Now let us look at scenarios 4 and 5 where the storage capacity is higher. Here we see that the potential is proportional to the storage capacity, e.g. double the capacity leads to double the potential.
Scenarios 6, 7 and 8 show the impact of the optimization horizon. If you reduce the optimization horizon to 12 hours, the potential reduces by 6 % compared to the base case (scenario 1). However, if you increase the horizon to 48 and 168 hours, the potential only increases slightly. In these cases, only about 0.5 and 0.9 % compared to the base case.
Important remark: The calculated arbitrage potential represents the optimal case assuming perfect knowledge of the day-ahead price. In other words, a perfectly accurate forecast of the day-ahead price. However, in reality, the forecast of the day-ahead price will not be perfect, thus possibly reducing the arbitrage potential significantly.
Python script – storage model and energy arbitrage potential analysis through optimization
Below a screenshot of the input parameters in the Python script to download. As discussed above, you can set the storage specifications and the optimization horizon and period. The optimization period can be set to take only a specific part of the input price (which is the full year of 2020). By default it is set to 8760 h (= 1 year). For example, if you want to use only one week, set it to 168.
The downloadable script also comes with the day-ahead electricity prices for 16 other countries which can be selected by changing the index ‘AT’ to the desired country code as shown in the screenshot above. The following countries are available.
However, you can also plug in your individual prices by modifying the code accordingly.
I hope you find this article insightful.
Please consider leaving a comment and/or sharing it with people who might be interested.
More insights within energy are to follow in the next article.