Skip to content

Repository files navigation

PV Market Value: Yield vs. Revenue

Most PV design tools optimize a plant for energy yield (kWh). This project asks a different question: what design maximizes revenue in a wholesale electricity market, where the price moves with the sun? The two answers are not the same, because a panel that generates at noon sells into the hours it has itself made cheap.

It simulates the hourly output of a PV plant with pvlib and values every design against real German day-ahead prices from SMARD.de, comparing fixed orientations, an east-west layout, and a single-axis tracker on market value rather than yield alone.

Project developed July 2026.


Headline results

Munich, 2019–2023, valued against DE/LU day-ahead prices, per kWp of DC nameplate:

Design Yield (kWh/kWp) Capture price (EUR/MWh) Value factor Revenue (EUR/kWp)
Fixed south 35° (yield-optimal) 5,981 86.9 0.877 520
East-west 15° 5,041 88.2 0.890 445
Single-axis tracker 6,525 91.4 0.923 597

Three findings, each a little against the grain:

  1. A single-axis tracker wins on both counts. It produces more energy and sells it at a higher average price (+5% capture price), for about +15% revenue over the yield-optimal fixed array. Tracking is not just a yield upgrade, it is a market-timing upgrade.

  2. East-west earns a higher price per MWh but less revenue. Its flatter, twin-shouldered profile aligns better with prices (capture price 88.2 vs 86.9), but the ~16% yield penalty outweighs the price gain, so revenue is lower. A higher capture price is not the same as more money.

  3. "Face west for the evening peak" does not pay for a fixed array. The revenue-optimal orientation stays close to due south; the modest evening price premium never overcomes the yield lost by turning away from the sun.

Generation shape vs. price

The mechanism in one chart: the fixed-south array (black) spikes at midday, straight into the price trough the midday solar fleet creates. The tracker (blue) spreads generation into the higher-priced morning and afternoon shoulders. That shift is what raises its capture price.


Why the gap is growing

Splitting the value factor by year shows the price-cannibalization effect deepening as solar penetration rises: the fixed array's value factor falls from about 0.94 (2019) to 0.78 (2023), while the tracker holds a consistent premium. The more midday solar cannibalizes midday prices, the more a design that generates away from midday is worth.

Value factor by year

Orientation heatmap


Method

For any array design the pipeline is a standard pvlib chain, expressed per kWp of DC nameplate so results are size-independent:

resource (GHI, DHI, air temp, wind)         from PVGIS
  -> solar position                          pvlib.solarposition.get_solarposition
  -> DNI from GHI, DHI, zenith               pvlib.irradiance.dni
  -> plane-of-array irradiance (Hay-Davies)  pvlib.irradiance.get_total_irradiance
  -> cell temperature                        pvlib.temperature.faiman
  -> DC power (with system + inverter loss)  pvlib.pvsystem.pvwatts_dc
  -> AC power, clipped at the inverter limit

The single-axis tracker adds pvlib.tracking.singleaxis to compute the panel angles each hour before transposition. Solar geometry and DNI do not depend on orientation, so they are computed once and reused across all 99 orientations in the sweep.

Valuation. The key metric is the capture price, the volume-weighted price a generator actually receives:

capture_price = Sum(price[t] * generation[t]) / Sum(generation[t])
value_factor  = capture_price / baseload_price     (< 1 means cannibalization)
revenue       = Sum(price[t] * generation[t])      (EUR per kWp)

System model. PVWatts DC with a −0.4%/°C temperature coefficient, 14% DC system losses (soiling, mismatch, wiring, availability), a Faiman cell-temperature model, 97% inverter efficiency, and a 1.2 DC/AC ratio with AC clipping. Every parameter lives in config/plant.yaml.


Running it

See RUNNING.md for full setup, including a VS Code walk-through.

pip install -r requirements.txt
python scripts/run_all.py     # downloads data once, then runs the analysis

Tests:

pytest

How pvlib is used

This project uses pvlib as a dependency rather than modifying it. The functions exercised: iotools.get_pvgis_hourly, solarposition.get_solarposition, irradiance.dni, irradiance.get_extra_radiation, irradiance.get_total_irradiance (Hay-Davies transposition), temperature.faiman, pvsystem.pvwatts_dc, and tracking.singleaxis.


Data

Field Source
Hourly GHI, DHI, air temperature, wind speed PVGIS (SARAH3), via pvlib
Day-ahead price (DE/LU) SMARD.de, series 4169 (EPEX SPOT)

Irradiance and prices are aligned in UTC (PVGIS timestamps are floored to the hour) so each generation hour is valued at its own clearing price.


Repository layout

pv-market-value/
├── config/plant.yaml       # site, system losses, sweep, tracker, east-west parameters
├── src/pvmarket/
│   ├── config.py           # typed configuration
│   ├── data/pvgis.py       # PVGIS irradiance via pvlib (cached)
│   ├── data/prices.py      # SMARD day-ahead price client (cached)
│   ├── model.py            # the pvlib generation pipeline (fixed / east-west / tracker)
│   ├── valuation.py        # capture price, value factor, revenue
│   ├── sweep.py            # orientation sweep and design comparison
│   └── plots.py            # charts
├── scripts/                # download, run_analysis, run_all
├── tests/                  # physics sanity, capture-price maths, config validation
├── notebooks/              # exploratory scripts
└── results/                # generated charts and CSVs

Limitations

  • One site (Munich) and the 2019–2023 window PVGIS SARAH3 covers. The framework runs for any European location by changing the config.
  • Price-taker: the plant earns the day-ahead clearing price and does not move it.
  • Day-ahead only; no intraday, balancing, or PPA structures, and no grid or curtailment constraints.
  • PVWatts-level system model, not a module-and-inverter-specific SAPM model.
  • A tracker's higher capex and land use are not modelled here; the comparison is on gross market value per kWp, which is the input to that decision, not the decision itself.

Possible extensions

  • PV plus battery: feed this generation into a storage dispatch optimizer to co-optimize the hybrid against prices (connects to a companion BESS project).
  • Cross-country: repeat for markets with steeper evening ramps, where west-facing and tracking should pay more.
  • Bifacial and different tracker geometries, and a full LCOE / capex-aware ranking rather than gross revenue.

Author

Mohammad Faisal, M.Sc. Power Engineering (Renewable Energy)

License and attribution

Released under the MIT License. See LICENSE.

Built with pvlib python (Holmgren, Hansen, and Mikofski, Journal of Open Source Software, 2018, doi:10.21105/joss.00884). Irradiance data from PVGIS (© European Union); price data from SMARD.de / Bundesnetzagentur, used under their terms.

About

Which PV plant design earns the most, not just the most kWh? A pvlib model valuing fixed, east-west, and single-axis-tracker layouts against real German day-ahead prices.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages