Series Creation
How option series are generated with expiry surfaces, strike ladders, and on-chain registration
This document explains how Diffusal generates its full options surface -- the expiry schedule, strike price selection, on-chain registration, and availability for trading. For a high-level overview, see Options Creation.
What is a Series?
A series is a unique options contract defined by four parameters:
| Parameter | Type | Example |
|---|---|---|
pairId | bytes32 | keccak256("ETH-USDT") |
strike | uint256 (WAD, 18 decimals) | 2500000000000000000000 ($2,500) |
optionExpiry | uint256 (unix seconds) | 1711612800 (2024-03-28 08:00 UTC) |
isCall | bool | true = call, false = put |
The seriesId is a deterministic hash of these four values:
Supported Pairs
| Category | Pairs |
|---|---|
| Crypto | ETH-USDT, BTC-USDT |
| Commodities | CMD:GC-USDT (gold), CMD:SI-USDT (silver), CMD:BZ-USDT (oil) |
Series Lifecycle
createSeries() expiry passes settle()
Non-existent ----------------> Active -----------------> Expired ---------> Settled
| |
| trades, quotes, positions | locked, settlement
| open interest accrues | price recorded- Active -- series is tradeable on the order book and via RFQ
- Expired -- trading halted, awaiting settlement
- Settled -- final settlement price recorded, positions can be closed
Expiry Surface
All options expire at 08:00 UTC. The expiry surface follows industry-standard conventions used by major derivatives exchanges.
Expiry Tiers
Today +1d +2d +3d +4d +5d +6d +7d
| | | | | | | |
Daily: D1 D2 D3 D4 D5 D6 D7
^
|
Weekly: W1 (Fri) W2 (Fri) W3 (Fri)
^
|
Monthly: M1 (last Fri) M2 (last Fri) M3 (last Fri)
^
|
Quarterly: Q1 (last Fri of Mar/Jun/Sep/Dec) ...| Tier | Count | Rule | Example Dates |
|---|---|---|---|
| Daily | 7 | Next 7 consecutive days at 08:00 UTC | Mar 31, Apr 1, ..., Apr 6 |
| Weekly | 3 | Next 3 Fridays at 08:00 UTC | Apr 4, Apr 11, Apr 18 |
| Monthly | 3 | Last Friday of next 3 months at 08:00 UTC | Apr 25, May 30, Jun 27 |
| Quarterly | 3 | Last Friday of Mar/Jun/Sep/Dec at 08:00 UTC | Jun 27, Sep 26, Dec 26 |
Deduplication
When expiry dates overlap across tiers, the higher tier takes precedence:
For example, if monthly-1 falls on a Friday that is also weekly-2, the expiry is labeled monthly-1 only. Each timestamp appears exactly once in the surface.
Minimum Buffer
The earliest valid expiry must be at least 65 minutes in the future:
- 60 minutes: on-chain contract minimum (ensures time for TWAP price accumulation)
- 5 minutes: headroom for RFQ auction latency
Expiry Kind Labels
Each expiry carries a kind label used throughout the system:
daily-1, daily-2, ..., daily-7
weekly-1, weekly-2, weekly-3
monthly-1, monthly-2, monthly-3
quarterly-1, quarterly-2, quarterly-3Strike Ladder Generation
Strikes are generated using a graduated zone system that provides dense coverage near the money and wider spacing in the wings. Step sizes snap to human-readable "nice numbers" for clean strike prices.
Nice Number System
All step sizes are rounded to the nearest value in the set :
... 0.1 0.2 0.25 0.5 1 2 2.5 5 10 20 25 50 100 200 250 500 1000 2000 2500 5000 10000 ...Algorithm:
- Compute magnitude:
- Normalize:
- Find closest value from
- Result = closest magnitude
Example -- BTC at $70,700 with 0.7% step:
| Step | Computation | Result |
|---|---|---|
| Raw step | ||
| Magnitude | ||
| Normalized | ||
| Nearest | (distance ) | $500 |
More examples:
| Input | Magnitude | Normalized | Nearest | Output |
|---|---|---|---|---|
| 494.9 | 100 | 4.949 | 5 | 500 |
| 15.1 | 10 | 1.51 | 1 | 10 |
| 32.4 | 10 | 3.24 | 2.5 | 25 |
| 60 | 10 | 6.0 | 5 | 50 |
| 7000 | 1000 | 7.0 | 5 | 5000 |
| 3500 | 1000 | 3.5 | 2.5 | 2500 |
Canonical Strike Filtering
Strikes displayed in the trading interface must pass a canonical check: at most 1 significant decimal digit after removing trailing zeros.
| Strike | Canonical? |
|---|---|
| 2000 | Yes |
| 2100 | Yes |
| 2125 | Yes |
| 100.5 | Yes |
| 2130.86 | No |
| 70437.88 | No |
Zone Configuration
Each expiry type uses exclusive zones with graduated step sizes. Zones do not overlap -- each zone owns a non-overlapping distance band from ATM. The upside range is wider than the downside (options skew).
Daily (2 zones)
Zone 1 Zone 2
step: 0.7% of spot step: 1.5% of spot
-17% -------- -5% ============ ATM ============ +5% -------- +17%
<-- Zone 1 --> (x1.3 upside)
<-------------- Zone 2 (exclusive) ------------->Weekly (3 zones)
Zone 3 Zone 2 Zone 1 Zone 2 Zone 3
step: 3.0% step: 1.5% step: 0.7% step: 1.5% step: 3.0%
-30% ------- -15% ------- -5% ==== ATM ==== +5% ------- +15% ------- +30%
(x1.4 upside)Monthly (4 zones)
-60% ---- -30% ---- -15% ---- -5% == ATM == +5% ---- +15% ---- +30% ---- +60%
Zone 4 Zone 3 Zone 2 Zone 1 Zone 1 Zone 2 Zone 3 Zone 4
5.0% 3.0% 1.5% 0.7% 0.7% 1.5% 3.0% 5.0%
(x1.5 upside)Quarterly (5 zones)
-150% --- -60% --- -30% --- -15% --- -5% = ATM = +5% --- +15% --- +30% --- +60% --- +150%
Zone 5 Zone 4 Zone 3 Zone 2 Z1 Z1 Zone 2 Zone 3 Zone 4 Zone 5
10.0% 5.0% 3.0% 1.5% 0.7% 0.7% 1.5% 3.0% 5.0% 10.0%
(x2.0 upside)Full Configuration
| Expiry | Zone | Step (% of spot) | Band (downside) | Band (upside) |
|---|---|---|---|---|
| Daily | 1 | 0.7% | 0 - 5% | 0 - 6.5% |
| 2 | 1.5% | 5 - 17% | 6.5 - 22.1% | |
| upside: 1.3x | ||||
| Weekly | 1 | 0.7% | 0 - 5% | 0 - 7% |
| 2 | 1.5% | 5 - 15% | 7 - 21% | |
| 3 | 3.0% | 15 - 30% | 21 - 42% | |
| upside: 1.4x | ||||
| Monthly | 1 | 0.7% | 0 - 5% | 0 - 7.5% |
| 2 | 1.5% | 5 - 15% | 7.5 - 22.5% | |
| 3 | 3.0% | 15 - 30% | 22.5 - 45% | |
| 4 | 5.0% | 30 - 60% | 45 - 90% | |
| upside: 1.5x | ||||
| Quarterly | 1 | 0.7% | 0 - 5% | 0 - 10% |
| 2 | 1.5% | 5 - 15% | 10 - 30% | |
| 3 | 3.0% | 15 - 30% | 30 - 60% | |
| 4 | 5.0% | 30 - 60% | 60 - 120% | |
| 5 | 10.0% | 60 - 150% | 120 - 300% | |
| upside: 2.0x |
Exclusive Zone Bands
Zones are designed with non-overlapping distance bands. This prevents outer zones from adding strikes in inner zones' territory, which would fragment liquidity across too many price points.
WRONG (overlapping):
Zone 1: |==========| Zone 2: |==================|
ATM ----> 5% ATM -----------> 17%
^^^^ Zone 2 adds strikes here,
fragmenting Zone 1's grid
RIGHT (exclusive):
Zone 1: |==========| Zone 2: |==========|
ATM ----> 5% 5% -------> 17%
Zone 2 starts where Zone 1 endsWith non-multiple step sizes (e.g., Zone 1 = $10, Zone 2 = $25 for ETH), overlapping zones would create strikes like $2,125 between Zone 1's $2,120 and $2,130. This spreads the same market maker depth across more strikes, resulting in wider spreads and less depth per strike.
Concrete Examples
BTC @ $70,700, Daily
Zone 1 (0.7% step, 0-5% band):
Step size: $500
Downside max: $3,535
Strikes (downside): $70,500 $70,000 $69,500 $69,000 $68,500 $68,000 $67,500
Strikes (upside): $71,000 $71,500 $72,000 $72,500 $73,000 $73,500 $74,000 $74,500
(1.3x upside = $4,595)
Zone 2 (1.5% step, 5-17% band):
Step size: $1,000
Downside max: $12,019
Strikes (downside): $67,000 $66,000 $65,000 ... $59,000
Strikes (upside): $75,000 $76,000 $77,000 ... $83,000
Total: ~35 strikes
$500 grid near ATM | $1,000 grid in wingsETH @ $2,159, Daily
Zone 1 (0.7% step, 0-5% band):
Step size: $10
Strikes: $2,050 $2,060 $2,070 ... $2,160 ... $2,260 $2,270
<-- $10 steps, dense near ATM -->
Zone 2 (1.5% step, 5-17% band):
Step size: $25
Strikes: $2,025 $2,000 $1,975 ... $1,800
$2,300 $2,325 $2,350 ... $2,525
<-- $25 steps, exclusive to zone 2 region -->
Total: ~25 strikes
$10 grid near ATM | $25 grid in wings
No $25-step strikes (like $2,125) contaminate the $10 gridBTC @ $70,700, Quarterly
Zone 1: $500 steps | 0-5% | ~15 strikes near ATM
Zone 2: $1,000 steps | 5-15% | ~14 strikes
Zone 3: $2,000 steps | 15-30% | ~11 strikes
Zone 4: $5,000 steps | 30-60% | ~8 strikes
Zone 5: $5,000 steps | 60-150% | ~28 strikes (2x upside = large range)
Total: ~76 strikes
Coverage from ~$28,000 to ~$177,000+Output Characteristics
| Expiry | Typical Strike Count | Range |
|---|---|---|
| Daily | 25 - 50 | ~35% of spot |
| Weekly | 35 - 60 | ~60% of spot |
| Monthly | 45 - 70 | ~120% of spot |
| Quarterly | 50 - 76 | ~300% of spot |
On-Chain Contract Rules
Series Registration
Series are created on-chain via createSeries(seriesId, params). This function is restricted to admin wallets.
Validation rules:
seriesIdmust equalkeccak256(abi.encodePacked(pairId, strike, optionExpiry, isCall))strikemust be greater than zerooptionExpirymust be at least 1 hour in the future (>= block.timestamp + minTimeToExpiry)- Series must not already exist (reverts with
SeriesAlreadyExists) - Minimum premium validation for the pair's USDT expressibility
Series Data Structure
| Field | Type | Description |
|---|---|---|
pairId | bytes32 | Trading pair identifier |
strike | uint256 | Strike price (WAD, 18 decimals) |
expiry | uint256 | Option expiry timestamp |
isCall | bool | Call (true) or put (false) |
isSettled | bool | Whether settlement has occurred |
settlementPrice | uint256 | Final settlement price (WAD) |
Emitted Events
When a series is created:
event SeriesCreated(
bytes32 indexed seriesId,
bytes32 indexed pairId,
bool indexed isCall,
uint256 strike,
uint256 expiry
);Permission Model
Owner (deployer)
|
+-- can add/remove Admins (2-step transfer)
|
v
Admins (protocol operator)
|
+-- createSeries() -- register new option series
+-- forceSettle() -- fallback settlement if oracle unavailable
+-- setOperators() -- grant operator role to trading contracts
|
v
Operators (OrderBook, RFQ contracts)
|
+-- updateOpenInterest() -- track position changes
+-- getSeries() -- validate series during trades
|
v
Users (traders)
|
+-- Cannot create series
+-- Trade on any existing active series via OrderBook or RFQOnly admin wallets can create series. Users interact with series exclusively through the trading contracts (OrderBook and RFQ), which validate series existence and tradability on every operation.
Settlement
After a series expires at 08:00 UTC, it enters the settlement process:
Expiry (08:00 UTC)
|
+-- Price oracle collects snapshots during the hour before expiry
| (requires 12+ snapshots over the 1-hour window for TWAP)
|
+-- Anyone calls settle(seriesId) <-- permissionless
| |
| +-- Reads Time-Weighted Average Price (TWAP) from oracle
| +-- Records settlementPrice on-chain
| +-- Marks isSettled = true
| +-- Series is now locked (no new trades)
|
+-- FALLBACK: Admin calls forceSettle(seriesId, price)
(used if TWAP is unavailable due to oracle downtime)The TWAP-based settlement ensures the final price reflects sustained market conditions rather than a single spot observation. The 12-snapshot minimum prevents manipulation through sparse oracle submissions.
For more on the settlement process, see Options Settlement.
Frontend Display
The trading interface presents all active series in an options chain layout:
Options Chain (T-Matrix)
+------ CALLS ------+--- STRIKE ---+------ PUTS -------+
| Bid Ask IV OI | $67,000 | Bid Ask IV OI | <- OTM put
| Bid Ask IV OI | $68,000 | Bid Ask IV OI |
| Bid Ask IV OI | $69,000 | Bid Ask IV OI |
| Bid Ask IV OI | [$70,500] | Bid Ask IV OI | <- ATM (highlighted)
| Bid Ask IV OI | $71,000 | Bid Ask IV OI |
| Bid Ask IV OI | $72,000 | Bid Ask IV OI |
| Bid Ask IV OI | $73,000 | Bid Ask IV OI | <- OTM call
+--------------------+-------------+--------------------+Data Flow
The chain loads data in parallel for fast rendering:
- Pairs -- available trading pairs
- Expiries -- active expiry dates for the selected pair
- Markets -- series filtered to canonical strikes only
- Tickers -- best bid/ask, last price, 24h volume per series
- Oracle -- current spot price for ATM determination
- Volume -- aggregate volume analytics (optional)
This data is then transformed into the T-matrix:
- Grouped by expiry timestamp
- Sorted by strike (ascending)
- Call and put sides paired per strike row
- ITM/OTM classification based on spot price
- ATM strike highlighted
Expiry Section Headers
Each collapsible expiry section displays:
- Expiry date (e.g., "28 Mar 2026")
- Days to expiry (DTE)
- Strike count
- Aggregate open interest (calls and puts)
- Total volume
The nearest expiry is pre-loaded. Other expiries load on demand when expanded.
See Also
- Options Creation -- High-level overview of series creation
- Options Settlement -- Settlement mechanics and TWAP pricing
- Margin System -- How margin is calculated for options positions
- DiffusalOptionsSeriesRegistry -- Smart contract reference