Skip to main content

Panoptic Specs

PanopticPool Balances

The amount of funds deposited in the Panoptic pool can be used to buy and sell options. Specifically, funds can be moved from the PanopticPool to the UniswapPool to create a short option payoff. Funds in the Uniswap pool can be "shorted" and moved back to the PanopticPool to create a long option payoff.

Total Assets

The amount of assets that have been desposited inside the Panoptic is tracked using the totalAssets(). This amount depends on the token balance inside the Panoptic pool, the amount of funds moved from the PanopticPool to Uniswap (_inAMM()), and the amount of fees that have been collected but are currently locked (_lockedFees()).

def totalAssets():
return balanceOf['panopticPool'] - _lockedFees() + _inAMM()

Pool Utilization

The instantaneous pool utilization is the fraction of all funds deposited that have been moved to the Uniswap pool.

The _poolUtilization() are computed as follow:

def _poolUtilization():
if totalAssets() == 0:
return 0
return _inAMM() / totalAssets()

Commission fee

The commission fee is paid by the option traders whenever they sell or buy an option. The commission fee is proportional to the notional value of the options.

In traditional brokerage firms, a fixed commission is charged when a position is opened AND closed. While no commission is paid if the user allows the option to expire, a perhaps perverse incentive of this model is that users may keep their position open for longer because they do not want to pay that commission fee.

In Panoptic, since options never expire, commissions are only paid when a new position is minted. We believe that this will eliminate the impact of the commission fee on the user's decision-making process when closing a position.

The value of the commission to be paid is the commission rate multiplied by the notional value of the option (i.e. the amount of token moved to/from the Uniswap pool). Note that the commission will always be paid in terms of the tokentype of the position.

Commission fee = _commissionRate() * notionalValue

Buying Power Requirement

The amount of capital available to place a trade is called the Buying Power. The minting of any option in Panoptic will reduce the account's Buying Power, and the Buying Power Reduction (BPR) of an option depends on:

  1. Notional value of that option
  2. Price of the underlying asset
  3. Risks associated with trading the underlying assets

Determining the buying power reduction from points (1) and (2) can be computed in a fairly straightforward manner (see sections below). The impact of point (3), on the other hand, is not as clearly defined. In TradFi, for instance, selling calls on GameStop (GME) may have a 100% collateral requirement, whereas Apple (AAPL) may only have a 20% collateral requirement. The choice to change the collateral requirement of a specific asset resides entirely within the power of centralized actors at each brokerage firms.

At Panoptic, the protocol evaluates the relative risk of a specific asset by looking at the pool utilization at the time the position was minted. The rationale behind this is as follows: If an asset is in high demand then there will be a lot of trading activity, and most liquidity will be actively traded. This means that liquidity that is normally available to trade/adjust/roll positions will be reduced. Hence, it will be more difficult for traders to respond to market moves, thereby increasing the risk for those specific pools. So, increasing the collateral requirement for pools with high utilization will help mitigate those risks.

BPR - long options

The buying power requirement for LONG options ensures that enough funds are available to cover any potential premium to be paid for purchasing that option. It is based on the pool utilization that was determined at the time a position was minted and this initial buying power requirement will not change over time (although the buying power requirement will increase as the position accumulates owed premium).

BUYING_POWER_REQUIREMENT = buyCollateralRatio() * notionalValue

Parameters that can change:
  • base_ratio
  • min_ratio
  • Umin
  • Umid
def _buyCollateralRatio():    
# linear from BUY to BUY/2 between 50% and 90%
# @dev the buy ratio is on a straight line defined between two points (x0,y0) and (x1,y1):
# (x0,y0) = (targetPoolUtilization,buyCollateralRatio) and
# (x1,y1) = (saturatedPoolUtilization,buyCollateralRatio / 2)
# note that y1<y0 so the slope is negative:
# aka the buy ratio starts high and drops to a lower value with increased utilization; the sell ratio does the opposite (slope is positive)
# the line's formula: y = a * (x - x0) + y0, where a = (y1 - y0) / (x1 - x0)
# but since a<0, we rewrite as:
# y = a' * (x0 - x) + y0, where a' = (y0 - y1) / (x1 - x0)
#
# BUY
# _COLLATERAL
# _RATIO ^
# | base_ratio = 10%
# 10% - |----------__ min_ratio = 5%
# 5% - | . . . . . ¯¯¯--______
# | . . .
# +---------+-------+-+---> POOL_
# 50% 90% 100% UTILIZATION
# Umin Umid
utilization = _poolUtilization()
if utilization < Umin:
return base_ratio
elif utilization > Umid:
return min_ratio
else:
return base_ratio - (base_ratio - min_ratio)*(utilization - Umin) / (Umid - Umin)


buyingPowerRequirement = {}

def deposit(amount):
balanceOf[msg.sender] -= amount
balanceOf[panopticPool] += amount

buyingPowerRequirement[msg.sender] += amount


def mintPutOption(strike, positionSize, optionType):

#do stuff

# lock collected fees
fees = collect(strike) # collect all fees at that strike

lockedFees += _fees

# update inAMM
notionalValue = strike * positionSize
if optionType == 'short':
inAMM += notionalValue

elif optionType == 'long':
buyingPowerRequirement[msg.sender] -= notionalValue * _buyCollateralRatio()
inAMM += notionalValue

# Pay commission.
# msg.sender is the address of the user that sold that option
balanceOf[msg.sender] -= notionalValue*_commissionRate()
balanceOf[PanopticPool] += notionalValue*_commissionRate()

INITIAL BPR - short options

The Buying power requirement is the amount of collateral needed to be deposited in order to sell/buy an option. The buying power requirement for SHORT options ensures that enough funds are available to cover any potential intrinsic value that results from positions becoming in-the-money.

It is based on the pool utilization that was determined at the time a position was minted and the buying power requirement will not change over time.

BUYING_POWER_REQUIREMENT = sellCollateralRatio() * notionalValue

Parameters that can change:
  • base_ratio
  • Umin
  • Umid
def _sellCollateralRatio(utilization):    
# @dev the sell ratio is on a straight line defined between two points (x0,y0) and (x1,y1):
# (x0,y0) = (targetPoolUtilization,min_sell_ratio) and
# (x1,y1) = (saturatedPoolUtilization,max_sell_ratio)
# the line's formula: y = a * (x - x0) + y0, where a = (y1 - y0) / (x1 - x0)
#
# SELL
# _COLLATERAL
# _RATIO ^
# | max ratio = 100%
# 100% - | _------
# | _-¯ . .
# | _-¯ . .
# 20% - |---------¯ base_ratio = 20%
# | . . .
# +---------+-------+-+---> POOL_
# 50% 90% 100% UTILIZATION
# Umin Umid

if utilization < Umin:
return base_ratio
elif utilization > Umid:
return max_ratio
else:
return base_ratio + (max_ratio - base_ratio)*(Umin - utilization) / (Umin - Umid)


buyingPowerRequirement = {}

def deposit(amount):
balanceOf[msg.sender] -= amount
balanceOf[panopticPool] += amount

buyingPowerRequirement[msg.sender] += amount


def mintPutOption(strike, positionSize, optionType):

#do stuff

# lock collected fees
fees = collect(strike) # collect all fees at that strike

lockedFees += _fees

# update inAMM
notionalValue = strike * positionSize
if optionType == 'short':
buyingPowerRequirement[msg.sender] -= notionalValue * _buyCollateralRatio(_poolUtilization())
inAMM += notionalValue

elif optionType == 'long':
buyingPowerRequirement[msg.sender] -= notionalValue * _sellCollateralRatio(_poolUtilization())
inAMM += notionalValue

# Pay commission.
# msg.sender is the address of the user that sold that option
balanceOf[msg.sender] -= notionalValue*_commissionRate()
balanceOf[PanopticPool] += notionalValue*_commissionRate()

Margin Requirement

In traditional finance, some types of accounts such as IRA or Level 1 trading accounts require all options to be fully collateralized. Specifically, users in IRA accounts can only sell cash-secured puts or covered calls, which means they must deposit the notional value of the underlying position in cash (for cash-secured puts) or own the underlying shares (for covered calls).

Undercollateralization is handled by reducing the buying power requirement of an asset. A Level 4 trading account in a TradFi brokerage firm allows users to sell naked puts and naked calls by posting 5x less collateral than users in a Level 1 account. For portfolio margin accounts, the collateral requirements could be even smaller, requiring about 10-15x less collateral than a Level 1 account.

Panoptic makes use of built-in leverage similar to Level 4 trading accounts to enable the minting of undercollateralized options. The collateralization requirements follows the guidelines outlined by CBOE and FINRA and are summarized below.

Maintenance margin requirement - short options

The buying power requirement for SHORT options is initially set by the pool utilization, but the requirement will change as the price changes and the position becomes in-the-money.

The expression for the BUYING_POWER_REQUIREMENT is given by:

BUYING_POWER_REQUIREMENT = sellCollateralRatio(poolUtilizationAtMint) * notionalValue + inTheMoneyRequirement(price)

A special case for the inTheMoneyRequirement has to be handled when the position is in-range (or at-the-money). This is because the ITM amount will be zero between the strike and the upper tick Pb, but the actual Uniswap v3 position will be composed of some asset. To accurately compute the in-the-money requirement for ATM options, we perform a linear interpolation between the lower price Pa and the upper price Pb (see graph below).

Parameters that can change:
  • none
def checkBuyingPowerRequirement(price, strike, positionSize, initialUtilization):    
#
# Short put BPR = 100% - (100% - SCR)*(price/strike)
#
# BUYING
# _POWER
# _REQUIREMENT ^
# 100% - | <- ITM . ATM . OTM ->
# | . . .
# | - . . .
# | ¯_ . . .
# | - . . .
# | ¯_. . .
# | -¯-_ . .
# | .¯_ ¯-_. .
# | . - ¯-_ .
# | . ¯_ . ¯-_ .
# SCR - | . -._____¯-_____
# | . . .
# +------------+------+------+--> current
# 0 Pa strike Pb price
#

notionalValue = strike * positionSize

sellRatio = _sellCollateralRatio(initialUtilization)

if (price > Pb): ## Position is OTM
collateralRequirement = notionalValue * sellRatio
elif (price < Pa): ## Position is ITM and Out-of-range (price is lower that lower bound)
collateralRequirement = notionalValue * (sellRatio + (1-sellRatio) * (1 - price/strike))
else: ## Position is ARM and in-range
collateralRequirement = notionalValue * (sellRatio + (1-sellRatio) * (1 - Pa/strike)*(Pb - price)/(Pb - Pa))