Getting Started
sipQuant implements the full SIP Global pricing pipeline: trade data → price series → seasonality → forward curve → index → OTC instrument → dealer book → risk.
All inputs are validated schema objects. All outputs are plain dicts or NumPy arrays.
Installation
pip install sipQuant
Requires Python 3.10+ and NumPy 1.23+. No other dependencies.
Import Convention
import sipQuant as sq
import numpy as np
All 17 modules are available as sq.<module>.
The Full Pipeline
The following example walks through building a pricing index for an Alberta hay market (Cluster A — Seasonal Agricultural Bulk) and structuring an OTC swap against it.
Step 1 — Ingest Physical Trade Data
Physical trades are the primary data source for thin commodity markets. Each trade is validated before entering the calculation pipeline.
trade = sq.schema.TradeRecord(
date='2026-03-14',
price=187.50,
volume=500.0,
grade='premium_bale_14pct_moisture',
origin='lethbridge_ab',
destination='red_deer_ab',
counterpartyId='CP_ANON_004',
)
errors = sq.schema.validate(trade)
assert errors == [], f"Trade validation failed: {errors}"
For markets with broker quotes rather than physical trades, use QuoteSheet:
quote = sq.schema.QuoteSheet(
date='2026-03-14',
bid=186.00,
ask=189.00,
mid=187.50,
source='broker_prairie_ag',
market='alberta_hay_premium',
grade='premium_bale_14pct_moisture',
tenor=0.25,
)
Step 2 — Build a Price Series
Assemble weekly broker quotes into a validated price series:
dates = np.array([
'2026-01-06', '2026-01-13', '2026-01-20', '2026-01-27',
'2026-02-03', '2026-02-10', '2026-02-17',
], dtype='datetime64')
prices = np.array([182.0, 184.5, 187.0, 186.0, 185.5, 183.0, 187.50])
ps = sq.schema.PriceSeries(
dates=dates,
values=prices,
source='broker_prairie_ag',
market='alberta_hay_premium',
grade='premium_bale_14pct_moisture',
)
For markets with irregular observations (most SIP thin markets), use SparsePriceSeries:
sparse_ps = sq.schema.SparsePriceSeries(
dates=dates[:4],
values=prices[:4],
source='broker_prairie_ag',
market='alberta_hay_premium',
maxGapDays=14, # flags gaps > 2 weeks
)
Step 3 — Decompose Seasonality
For Cluster A markets, the harvest cycle creates a strong annual seasonal pattern. Use a 52-week period for weekly observations:
# Need at least 2 full years for robust seasonal decomposition
# Using synthetic 2-year weekly series for illustration
n = 104 # 2 years of weekly data
t = np.arange(n)
long_prices = 185.0 + 10 * np.sin(2 * np.pi * t / 52) + np.random.normal(0, 2, n)
decomp = sq.commodity.seasonality(
dates=t,
values=long_prices,
period=52,
method='stl',
)
trend = decomp['trend'] # underlying price trend
seasonal = decomp['seasonal'] # harvest-cycle component
residual = decomp['residual'] # unexplained noise
Step 4 — Extract Convenience Yield
When futures prices are available, extract the implied convenience yield:
cy = sq.commodity.convenienceYield(
spotPrice=187.50,
futuresPrice=192.00,
tenor=0.25, # 3-month futures
r=0.046,
storageCost=0.02, # 2% annualised bale storage
)
print(f"Convenience yield: {cy['convenienceYield']:.4f}")
print(f"Net carry: {cy['netCarry']:.4f}")
Step 5 — Compute Local Basis
The local basis is the cash price minus the benchmark (e.g. the SIP composite index):
b = sq.commodity.basis(
cashPrice=185.00,
referencePrice=188.00,
market='lethbridge_ab',
grade='premium_bale_14pct_moisture',
)
print(f"Basis: {b['basis']:.2f} $/tonne ({b['basisBps']:.0f} bps)")
Step 6 — Build the Local Forward Curve
The forward curve drives all OTC pricing:
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=cy['convenienceYield'],
storageCost=0.02,
basisAdjustment=-2.50,
)
curve = sq.schema.ForwardCurve(
tenors=fwd['tenors'],
prices=fwd['forwards'],
baseDate='2026-03-14',
market='alberta_hay_premium',
)
Step 7 — Price an OTC Commodity Swap
Price a fixed-float swap where the counterparty pays fixed at $190/tonne:
swap = sq.otc.commoditySwap(
fixedPrice=190.0,
indexCurve=curve['prices'],
notional=1000.0, # tonnes
schedule=curve['tenors'],
r=0.046,
)
print(f"Swap NPV: ${swap['npv']:,.2f}")
print(f"Fixed leg PV: ${swap['fixedLegPV']:,.2f}")
print(f"Float leg PV: ${swap['floatLegPV']:,.2f}")
print(f"Delta: {swap['greeks']['delta']:.4f}")
Step 8 — Register the Position in the Dealer Book
pos = sq.schema.OTCPosition(
instrumentType='commodity_swap',
direction='receive_fixed',
notional=1000.0,
strikeOrFixed=190.0,
expiry='2026-12-31',
counterpartyId='CP_ANON_004',
greeks={
'delta': swap['greeks']['delta'],
'gamma': 0.0,
'vega': 0.0,
'theta': -0.05,
'rho': swap['greeks']['dv01'],
},
)
positions = [pos]
ng = sq.book.netGreeks(positions)
print(f"Book net delta: {ng['delta']:.2f}")
Step 9 — Calculate the IOSCO Index
spec = sq.schema.IndexSpec(
name='SIP-AHI-001',
version='1.0',
constituents=['premium_bale_14pct_moisture', 'feed_grade'],
weightsMethod='volume',
rollRule='monthly_last_business_day',
effectiveDate='2026-01-01',
)
# Multiple trade records needed for a real index
trades = [
sq.schema.TradeRecord('2026-03-14', 187.50, 500.0,
'premium_bale_14pct_moisture', 'lethbridge_ab', 'red_deer_ab', 'CP001'),
sq.schema.TradeRecord('2026-03-13', 185.00, 300.0,
'premium_bale_14pct_moisture', 'ponoka_ab', 'calgary_ab', 'CP002'),
sq.schema.TradeRecord('2026-03-12', 162.00, 400.0,
'feed_grade', 'medicine_hat_ab', 'lethbridge_ab', 'CP003'),
]
result = sq.index.calculateIndex(trades, spec, '2026-03-14')
audit = sq.index.auditTrail(result, spec)
print(f"Index value: {result['indexValue']:.2f}")
print(f"Trades used: {result['nTrades']}")
print(f"Checksum: {audit['checksum']}")
Step 10 — Assess Thin-Market Liquidity
Before sizing a position, score the market’s liquidity:
score = sq.liquidity.thinMarketScore(trades, window=30)
print(f"Liquidity score: {score['score']:.3f} (n={score['nTrades']} trades in 30 days)")
# Compute LVAR for position sizing
returns = np.diff(np.log(np.array([t['price'] for t in trades])))
volumes = np.array([t['volume'] for t in trades[:-1]])
if len(returns) > 0:
lvar = sq.liquidity.liquidityAdjustedVar(returns, volumes, alpha=0.05)
print(f"LVAR: {lvar['lvar']:.4f} (VaR: {lvar['var']:.4f} LiqCost: {lvar['liquidityCost']:.4f})")
Next Steps
See cluster_taxonomy for the full 26-cluster reference and module mapping
See index — IOSCO-Aligned Index Infrastructure for the IOSCO methodology and restatement procedures
See otc — OTC Instrument Pricing for all OTC instrument types and when to use each
See sim — Price Process Simulation for process selection by cluster type
See liquidity — Thin-Market Risk for thin-market risk management