commodity — Physical Pricing Primitives
The commodity module provides the physical pricing building blocks used
throughout the SIP index and OTC stack. All functions are commodity-agnostic —
replace spot prices, storage costs, convenience yields, and grade factors with
the target cluster’s market data.
Primary use clusters: A (Seasonal Ag), B (Weather-Event), C (Specialty Food), D (Industrial Biomaterials), E (Circular Economy), F (Aquatic/Marine), H (Animal Inputs), L (Emerging Protein), P (Soil Inputs).
import sipQuant as sq
import numpy as np
seasonality()
STL-like additive decomposition of a price series into trend, seasonal, and residual components. Uses a centred moving average for the trend and group means of the detrended series for the seasonal component.
decomp = sq.commodity.seasonality(
dates=np.arange(104), # 2 years of weekly data
values=weekly_prices,
period=52, # 52-week annual harvest cycle
method='stl',
)
trend = decomp['trend']
seasonal = decomp['seasonal']
residual = decomp['residual']
Parameters
dates— array-like, length n. Time index (used for ordering only; can be integers).values— array-like of float, length n.period— int. Seasonal period. Use 52 for weekly data (annual cycle), 12 for monthly, 4 for quarterly.method— str.'stl'or'additive'(both use the same additive algorithm).
Returns — dict with keys: trend, seasonal, residual, values, period, method.
Note
The seasonal component is centred so it sums to zero over one period.
Edge values (first and last period//2 observations) of trend are
filled with the nearest valid moving-average value.
Cluster guide
Cluster A (Hay, Straw, Silage):
period=52, weekly observationsCluster B (Road salt):
period=52, demand spikes in winter monthsCluster F (Seaweed, Fish meal):
period=52, harvest season dependencyCluster T (Horticultural):
period=52with event-spike overlays (Valentine’s Day, etc.)
convenienceYield()
Extracts the implied convenience yield from the cost-of-carry relationship between a spot price and a futures price. The convenience yield represents the benefit of holding the physical commodity rather than the futures contract.
where \(y\) is the convenience yield, \(r\) is the risk-free rate, \(u\) is storage cost, \(F\) is the futures price, \(S\) is spot, and \(T\) is tenor in years.
cy = sq.commodity.convenienceYield(
spotPrice=187.50,
futuresPrice=192.00,
tenor=0.25, # 3-month
r=0.046,
storageCost=0.02, # 2% per year
)
print(f"Convenience yield: {cy['convenienceYield']:.4f}")
print(f"Net carry: {cy['netCarry']:.4f}")
Parameters
spotPrice— float. Current spot price S.futuresPrice— float. Observed futures price F.tenor— float. Time to delivery in years.r— float. Risk-free rate (continuous).storageCost— float. Annualised storage cost rate.
Returns — dict with keys: convenienceYield, carryAdjustedForward, netCarry.
basis()
Cash price minus reference (benchmark) price. The basis captures local supply/demand imbalances, transport differentials, and grade differences relative to a benchmark or composite index.
b = sq.commodity.basis(
cashPrice=185.00,
referencePrice=188.00, # SIP-AHI-001 composite
market='lethbridge_ab',
grade='premium_bale_14pct_moisture',
)
print(f"Basis: {b['basis']:.2f} $/tonne")
print(f"Basis bps: {b['basisBps']:.0f}")
Parameters
cashPrice— float. Observed local cash price.referencePrice— float. Benchmark/futures/composite price.market— str, optional. Market label stored in output.grade— str, optional. Grade label stored in output.
Returns — dict with keys: basis, basisBps, cashPrice, referencePrice, market, grade.
gradeAdjustment()
Quality-adjusted price computed as base price plus the sum of all grade factor adjustments. Grade factors can be passed as a dict (with descriptive keys) or as a plain list/array of float adjustments.
grade_factors = {
'moisture_premium_14pct': +2.50,
'dockage_discount_2pct': -1.80,
'test_weight_premium': +1.20,
}
adj = sq.commodity.gradeAdjustment(
basePrice=185.0,
gradeFactors=grade_factors,
)
print(f"Adjusted price: {adj['adjustedPrice']:.2f}")
print(f"Total adjustment: {adj['totalAdjustment']:+.2f}")
Parameters
basePrice— float. Unadjusted reference price.gradeFactors— dict or array-like. Dict keys are labels; values (or array elements) are float adjustments in price units.
Returns — dict with keys: adjustedPrice, totalAdjustment, gradeFactors.
Cluster guide
Cluster A: moisture %, test weight, dockage, mould
Cluster C: certification premium (organic, fair trade, ceremonial)
Cluster L: protein %, amino acid profile, GMO status
Cluster Q: genetic performance metrics (EPD, EBV)
Cluster V: certified vs uncertified seed premium
transportDifferential()
Delivered price at destination by adding logistics costs to the origin price. Transport cost is 30–60% of value in Cluster A markets — this function is critical for basis calculations between origin and destination.
delivered = sq.commodity.transportDifferential(
originPrice=185.0,
freightCost=8.50, # $/tonne truck freight Lethbridge → Calgary
handlingCost=1.20, # feedlot receiving fee
insuranceCost=0.30,
)
print(f"Delivered price: {delivered['deliveredPrice']:.2f}")
print(f"Total logistics cost: {delivered['totalLogisticsCost']:.2f}")
Parameters
originPrice— float. Price at origin.freightCost— float. Freight/shipping cost per unit.handlingCost— float. Terminal/handling charges per unit.insuranceCost— float. Cargo insurance per unit.
Returns — dict with keys: deliveredPrice, originPrice, totalLogisticsCost, breakdown.
localForwardCurve()
Builds a local forward price curve using the cost-of-carry model adjusted
for local basis. This is the primary input to OTC pricing via schema.ForwardCurve.
where \(y\) is the convenience yield, \(u\) is storage cost, and basis adjustment shifts the spot price to reflect the local market.
tenors = np.array([0.0, 0.25, 0.50, 0.75, 1.0])
fwd = sq.commodity.localForwardCurve(
spotPrice=187.50,
tenor=tenors,
r=0.046,
convYield=0.028,
storageCost=0.020,
basisAdjustment=-2.50, # local market at -$2.50 to benchmark
)
# Wrap in ForwardCurve schema for OTC pricing
curve = sq.schema.ForwardCurve(
tenors=fwd['tenors'],
prices=fwd['forwards'],
baseDate='2026-03-14',
market='alberta_hay_premium',
)
Parameters
spotPrice— float. Current spot price S.tenor— array-like. Forward tenors in years.r— float. Risk-free rate.convYield— float. Convenience yield (fromconvenienceYield()or assumed).storageCost— float. Annualised storage cost rate.basisAdjustment— float. Local basis adjustment to spot.
Returns — dict with keys: tenors, forwards, netCarry, impliedConvenienceYield.
rollingRollCost()
Estimates roll costs from the shape of the forward curve at specified roll indices. Used in index methodology to quantify the cost of maintaining a rolling futures exposure.
roll_dates = np.array([0, 1, 2, 3]) # quarterly roll indices
roll = sq.commodity.rollingRollCost(fwd, roll_dates)
print(f"Quarterly roll costs: {roll['rollCosts']}")
print(f"Annualised roll cost: {roll['annualizedRollCost']:.4f}")
Parameters
forwardCurve— dict. Must contain'forwards'or'prices'array and'tenors'.rollDates— array-like of int. Indices into the forwards array at which rolls occur.
Returns — dict with keys: rollCosts, totalRollCost, annualizedRollCost.