1. Core notions
Underlying. What the contract is written on: power, gas, oil, CO₂, a spread (power–fuel), an index, etc. In power/commodities the underlying is often delivered over a period (month, quarter, year, hour-by-hour), not at a single instant.
Contract specification. A full spec must at least fix:
- Underlying: product (e.g.
Phelix DE power), delivery granularity (hourly, daily, monthly). - Delivery period: start/end, e.g. Cal-27 Baseload, Q1-26 Peakload.
- Quantity: fixed MW, a profile (per-hour schedule), or a range (min/max).
- Price definition: fixed price, index (spot, day-ahead), average of quotes, etc.
- Settlement type: physical vs financial (cash-settled).
- Settlement convention: when cash flows occur, what price(s) are used.
- Optionalities: caps/floors, flex, swings, make-up etc.
Payoff. The payoff is a function of realised prices and quantities:
Where is price at time and is delivered quantity.
2. Vanilla payoffs in commodities
“Vanilla” usually means:
- Simple functional form (linear or standard option).
- No exotic path features beyond a period average or sum.
- Easy to understand and hedge.
2.1 Forward / future (financial)
Delivery over a period; assume fixed quantity per time bucket.
- Contract price:
- Realised price in bucket :
- Number of buckets:
Cash-settled forward on an average price (typical monthly power forward):
If physical, you can think of it as: you deliver (or take) power at , while underlying “spot” revenue is .
2.2 European call/put on a single price
On one fixing date :
- Call payoff:
- Put payoff:
In commodities you may have:
- European call on the monthly average price:
Still considered quite “vanilla” in energy.
2.3 Swap
A fixed-for-floating swap over a period (e.g. fixed price vs spot index):
where is the time weight of each bucket (e.g. 1 hour, 1 day).
3. Structured products – what changes?
“Structured” = vanilla plus complexity. The complexity typically comes from:
- Path dependence – payoff depends on the whole trajectory of prices/quantities, not just one value or a simple average.
- Embedded options – rights to adjust volume, switch indices, exercise storage, etc.
- Non-linear constraints – min/max volumes, ramps, penalties, take-or-pay clauses.
Structured contracts are very common in power and gas because customers want flexibility, and physical assets have constraints.
3.1 Classic examples in power & gas
Swing contract (gas, power). Buyer can choose volumes each day within daily and total limits. Payoff depends on the chosen path of volumes and prices.
Asian options (average price options). Payoff is a function of the average price over a period; already somewhat path-dependent.
Storage contract (gas storage, hydro reservoir). Rights to inject/withdraw subject to capacity and rate constraints. Value depends on the entire future price path and control decisions.
Take-or-pay contract. Buyer must pay for a minimum annual quantity, whether taken or not. Effective payoff depends on realised usage and prices over the year.
Spark-spread or dark-spread options. Payoff depends on two legs (power price and fuel price), often over many time buckets.
Even a “simple” structured deal can be just a vanilla forward plus caps/floors on volumes or prices.
4. Path dependence – intuition and formal idea
Path dependence means: payoff depends on more than the final value . It may depend on:
- The full set ,
- Or a functional of the path: average, minimum, maximum, time spent in a range, etc,
- Or on decisions taken along the path: exercised volumes, nominations.
Formally:
Path-independent payoff:
Path-dependent payoff:
4.1 Simple commodity examples
- Asian call on average monthly price:
- Daily swing with min/max volume:
- Daily price:
- Daily chosen volume:
- Constraints: and
You must choose the whole schedule , so the value depends on the price path and on the optimal exercise strategy.
This is strongly path-dependent.
- Storage: Think of level process . The payoff depends on the entire path of injections/withdrawals and prices.
5. Why contract specs matter for modelling and systems
For a senior quant / quant dev the interviewer expects you to think:
Contract spec drives the payoff function. If the spec is incomplete or ambiguous, valuation and risk are ill-defined.
Vanilla vs structured impacts model choice and infrastructure.
- Vanilla: closed-form formulae (e.g. Black-76), simple Monte Carlo, basic greeks.
- Structured: dynamic optimisation, lattice or Monte Carlo with control, scenario trees, large-scale simulations.
Path dependence has system implications.
- Need to store and replay full price paths (or sufficient statistics).
- Payoff engine must iterate over the time buckets, not just a single date.
- Risk and P&L explain must track decisions (exercise, nominations) as state variables.
In power, time granularity is key.
- Hourly vs daily vs monthly deliverables.
- Peak vs off-peak blocks.
- Profiles (e.g. 9–17 business days only) vs baseload.
If you cannot read a term sheet and translate it into a precise payoff function, you cannot build a robust pricing platform.
6. Mental checklist for an interview
When they show you a contract, ask yourself:
- What exactly is the underlying and its time granularity?
- Over what delivery period does the contract apply?
- How is quantity defined? Fixed, profile, flexible range?
- Is there any path dependence (average price, min/max, constraints)?
- What optionalities does the buyer/seller have?
- How is the settlement price defined? Which index, which fixing, which average?
- Physical or financial settlement?
- Where would I expect vanilla vs structured pricing engines to kick in?
If you answer these systematically, you will rarely miss a key feature.
Python Simulation
The following Python snippet is a small, self-contained example for this topic. It is not a production pricer. It is a didactic toy that isolates the core mechanism we just discussed, so you can see the inputs, the transformation, and the resulting output in one place. Treat it as a sanity check and a template: the structure mirrors how we would implement the same idea in a real library, but with simplified assumptions and minimal plumbing.
import numpy as np
# --- 1. Helper: simulate a simple price path (e.g. daily prices for one month) ---
def simulate_price_path(s0=100.0, mu=0.0, sigma=0.2, days=30, dt=1/252, seed=42):
"""
Simple geometric Brownian motion price path.
This is deliberately over-simplified: just enough to test payoff logic.
"""
rng = np.random.default_rng(seed)
increments = (mu - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * rng.standard_normal(days)
log_prices = np.log(s0) + np.cumsum(increments)
return np.exp(log_prices)
prices = simulate_price_path(s0=100, sigma=0.3, days=30)
prices[:5], prices[-5:]
# --- 2. Vanilla payoffs ---
def forward_payoff(avg_price, strike, quantity):
"""Cash-settled forward on average price over a period."""
return quantity * (avg_price - strike)
def european_call_payoff(spot_T, strike, quantity):
"""European call option on final spot."""
return quantity * max(spot_T - strike, 0.0)
def asian_call_payoff(path, strike, quantity):
"""Asian call option on average price over path."""
avg_price = np.mean(path)
return quantity * max(avg_price - strike, 0.0)
# Example usage
strike = 100.0
quantity = 10.0
avg_price = np.mean(prices)
spot_T = prices[-1]
print(f"Average price over period: {avg_price:6.2f}")
print(f"Final price: {spot_T:6.2f}")
print("Forward payoff: ", forward_payoff(avg_price, strike, quantity))
print("European call payoff: ", european_call_payoff(spot_T, strike, quantity))
print("Asian call payoff: ", asian_call_payoff(prices, strike, quantity))
# --- 3. Very simplified swing-like payoff ---
def swing_payoff(path, strike, q_min, q_max, q_total_max):
"""
Extremely simplified swing contract:
- Each day you choose q_i in [q_min, q_max].
- Total volume sum(q_i) must be <= q_total_max.
- Here we assume the holder is perfectly clairvoyant and chooses
q_i = q_max when S_i > strike, else q_i = q_min, but capped by total.
This is *not* a realistic optimiser; it is only to show path dependence.
"""
prices = np.asarray(path)
n = len(prices)
q = np.full(n, q_min)
# greedy 'exercise': allocate extra volume q_max-q_min on the best days
extra_per_day = q_max - q_min
remaining_extra = q_total_max - q_min * n
remaining_extra = max(0.0, remaining_extra)
# sort days by price descending; allocate extra volume on highest-price days
order = np.argsort(prices)[::-1]
for i in order:
if remaining_extra <= 0:
break
alloc = min(extra_per_day, remaining_extra)
q[i] += alloc
remaining_extra -= alloc
payoff = np.sum(q * (prices - strike))
return payoff, q
# Example swing parameters
q_min = 0.0
q_max = 1.0
q_total_max = 10.0
swing_p, q_schedule = swing_payoff(prices, strike=95.0, q_min=q_min, q_max=q_max, q_total_max=q_total_max)
print("Swing payoff (toy model):", swing_p)
print("Chosen volumes (first 10 days):", q_schedule[:10])
print("Total chosen volume:", q_schedule.sum())
# --- 4. Compare path-independent vs path-dependent contracts on the same path ---
spot_T = prices[-1]
avg_price = np.mean(prices)
call_E = european_call_payoff(spot_T, strike=100, quantity=10)
call_A = asian_call_payoff(prices, strike=100, quantity=10)
swing_p, _ = swing_payoff(prices, strike=95, q_min=0.0, q_max=1.0, q_total_max=10.0)
print("Same simulated price path, different payoffs:")
print(f" European call (path-independent): {call_E:8.2f}")
print(f" Asian call (average, path-dependent): {call_A:8.2f}")
print(f" Swing (strongly path-dependent): {swing_p:8.2f}")