Valuation and optimisation of commodity storage.
Project description
Overview
A collection of models for the valuation and optimisation of commodity storage, either virtual or physical. The models can be used for any commodity, although are most suitable for natural gas storage valuation and optimisation.
Calculations take into account many of the complex features of physical storage including:
- Inventory dependent injection and withdrawal rates, otherwise known as ratchets. For physical storage it is often the case that maximum withdrawal rates will increase, and injection rates will decrease as the storage inventory increases. For natural gas, this due to the increased pressure within the storage cavern.
- Time dependent injection and withdrawal rates, including the ability to add outages when no injection or withdrawal is allowed.
- Forced injection/withdrawal, as can be enforced by regulatory or physical constraints.
- Commodity consumed on injection/withdrawal, for example where natural gas is consumed by the motors that power injection into storage.
- Time dependent minimum and maximum inventory, necessary if different notional volumes of a storage facility are leased for different consecutive years.
- Optional time and inventory dependent loss of commodity in storage. For example this assumption is necessary for electricity storage which isn't 100% efficient.
- Ability to constrain the storage to be empty at the end of it's life, or specify a value of commodity inventory left in storage.
Models
Currently Implemented
Currently the following models are implemented in this repository:
- Intrinsic valuation, i.e. optimal value assuming the commodity price remains static.
- One-factor trinomial tree, with seasonal spot volatility.
Both approaches solve the optimsation problem using backward induction across a discrete inventory grid.
Planned Implementations
Implemenations using the following techniques are planned in the near future:
- Least-Squares Monte Carlo with a multi-factor price process.
- Rolling Intrinsic.
Example
The following example shows how to calculate the intrinsic value and decision profile for a storage facility.
from datetime import date, timedelta
import pandas as pd
from cmdty_storage import CmdtyStorage, InjectWithdrawByInventoryAndPeriod, InjectWithdrawByInventory, intrinsic_value
def _create_piecewise_flat_series(data, dt_index, freq):
period_index = pd.PeriodIndex([pd.Period(dt, freq=freq) for dt in dt_index])
return pd.Series(data, period_index).resample(freq).fillna('pad')
constraints = [
InjectWithdrawByInventoryAndPeriod(date(2019, 8, 28),
[
InjectWithdrawByInventory(0.0, -150.0, 255.2),
InjectWithdrawByInventory(2000.0, -200.0, 175.0),
]),
(date(2019, 9, 10),
[
(0.0, -170.5, 235.8),
(700.0, -180.2, 200.77),
(1800.0, -190.5, 174.45),
])
]
storage_start = date(2019, 8, 28)
storage_end = date(2019, 9, 25)
constant_injection_cost = 0.015
constant_pcnt_consumed_inject = 0.0001
constant_withdrawal_cost = 0.02
constant_pcnt_consumed_withdraw = 0.000088
constant_pcnt_inventory_loss = 0.001;
constant_pcnt_inventory_cost = 0.002;
cmdty_storage = CmdtyStorage('D', storage_start, storage_end, constant_injection_cost, constant_withdrawal_cost, constraints,
cmdty_consumed_inject=constant_pcnt_consumed_inject, cmdty_consumed_withdraw=constant_pcnt_consumed_withdraw,
inventory_loss=constant_pcnt_inventory_loss, inventory_cost=constant_pcnt_inventory_cost)
inventory = 650.0
val_date = date(2019, 9, 2)
forward_curve = _create_piecewise_flat_series([58.89, 61.41, 59.89, 59.89],
[val_date, date(2019, 9, 12), date(2019, 9, 18), storage_end], freq='D')
flat_interest_rate = 0.03
interest_rate_curve = pd.Series(index = pd.period_range(val_date, storage_end + timedelta(days=50), freq='D'))
interest_rate_curve[:] = flat_interest_rate
twentieth_of_next_month = lambda period: period.asfreq('M').asfreq('D', 'end') + 20
intrinsic_results = intrinsic_value(cmdty_storage, val_date, inventory, forward_curve,
settlement_rule=twentieth_of_next_month, interest_rates=interest_rate_curve,
num_inventory_grid_points=100)
Getting the NPV
print("Storage NPV")
print("{:,.2f}".format(intrinsic_results.npv) )
Prints the following:
Storage NPV
40,419.45
Getting the Decision and Inventory Profile
print(intrinsic_results.profile.applymap("{0:.2f}".format))
Prints the following:
inventory inject_withdraw_volume cmdty_consumed inventory_loss net_position
2019-09-02 483.10 -166.25 0.01 0.65 166.24
2019-09-03 320.54 -162.08 0.01 0.48 162.06
2019-09-04 320.22 0.00 0.00 0.32 -0.00
2019-09-05 562.26 242.36 0.02 0.32 -242.38
2019-09-06 794.35 232.65 0.02 0.56 -232.68
2019-09-07 1016.90 223.35 0.02 0.79 -223.37
2019-09-08 1230.31 214.42 0.02 1.02 -214.44
2019-09-09 1434.94 205.86 0.02 1.23 -205.89
2019-09-10 1616.69 183.18 0.02 1.43 -183.20
2019-09-11 1793.91 178.84 0.02 1.62 -178.85
2019-09-12 1601.67 -190.44 0.02 1.79 190.43
2019-09-13 1411.43 -188.64 0.02 1.60 188.63
2019-09-14 1223.16 -186.86 0.02 1.41 186.85
2019-09-15 1036.83 -185.10 0.02 1.22 185.08
2019-09-16 852.44 -183.35 0.02 1.04 183.34
2019-09-17 669.96 -181.63 0.02 0.85 181.61
2019-09-18 489.51 -179.78 0.02 0.67 179.77
2019-09-19 311.74 -177.28 0.02 0.49 177.27
2019-09-20 136.61 -174.82 0.02 0.31 174.80
2019-09-21 0.00 -136.47 0.01 0.14 136.46
2019-09-22 0.00 0.00 0.00 0.00 -0.00
2019-09-23 0.00 0.00 0.00 0.00 -0.00
2019-09-24 0.00 0.00 0.00 0.00 -0.00
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for cmdty_storage-0.1.0a1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a202a2a96bbf7e184e9e5f5962ebd5a59f74d3dce30d37a3ed13e68bba6ee0cc |
|
MD5 | e9a7193ab7633c8177eba34cd657e082 |
|
BLAKE2b-256 | a72cb094b0b4d88c114985d683e2d87794825747eef1d2371d518c46f5f9fb81 |