Skip to content

Optino Formulae

BokkyPooBah edited this page May 16, 2020 · 10 revisions

Formulae for the calculation of the payoff and collateral



Table Of Contents



Optino Payoff Formulae

Vanilla Call Payoff

vanillaCallPayoff = max(spot - strike, 0)

Capped Call Payoff

cappedCallPayoff = max(min(spot, cap) - strike, 0)
                 = max(spot - strike, 0) - max(spot - cap, 0)

Vanilla Put Payoff

vanillaPutPayoff = max(strike - spot, 0)

Floored Put Payoff

flooredPutPayoff = max(strike - max(spot, floor), 0)
                 = max(strike - spot, 0) - max(floor - spot, 0)


Algorithms

Decimal Places

  • optinoDecimals - for Optino and Cover tokens, set to 18
  • decimals0 for token0 (or baseToken), e.g. 18 decimals for WETH in WETH/USDx
  • decimals1 for token1 (or quoteToken), e.g. 6 decimals for USDx in WETH/USDx
  • rateDecimals for the rate feed. e.g. 18 for MakerDAO's feeds

Call Payoff

  • strike must be > 0
  • bound must be 0 for vanilla calls or > strike for capped calls
callPayoff = 0
if (spot > 0 && spot > strike) {
  if (bound > strike && spot > bound) {
    callPayoff = [(bound - strike) / spot] x [tokens / (10^optinoDecimals)] x (10^decimals0)
  } else {
    callPayoff = [(spot - strike) / spot] x [tokens / (10^optinoDecimals)] x (10^decimals0)
  }
}

Call Collateral

  • strike must be > 0
  • bound must be 0 for vanilla calls or > strike for capped calls
  • Collateral is in the token0 (or baseToken)
if (bound <= strike) {
  callCollateral = [tokens / (10^optinoDecimals)] x (10^decimals0)
} else {
  callCollateral = [(bound - strike) / bound] x [tokens / (10^optinoDecimals)] x (10^decimals0)
}

Put Payoff

  • strike must be > 0
  • bound must be 0 for vanilla puts or < strike for floored puts
if (bound == 0 || (bound > 0 && spot >= bound)) {
  putPayoff = [(strike - spot) / (10^rateDecimals)] x [tokens / (10^optinoDecimals)] x (10^decimals1)
} else {
  putPayoff = [(strike - bound) / (10^rateDecimals)] x [tokens / (10^optinoDecimals)] x (10^decimals1)
}

Put Collateral

  • strike must be > 0
  • bound must be 0 for vanilla puts or < strike for floored puts
  • Collateral is in the token1 (or quoteToken)
putCollateral = [(strike - bound) / (10^rateDecimals)] x [tokens / (10^optinoDecimals)] x (10^decimals1)


Solidity Implementation

  • Using 256 bit unsigned integers
  • Divisions are performed last to reduce loss of precision
  • computeCollateral(...) calculates the collateral as the maximum payoff
  • computePayoff(...) calculates the payoff depending on the spot price, after expiry
  • Optino and Cover tokens can close(...) off against each other to release calculated collateral in proportion to the tokens closed/netted
  • Optino token holders execute settle() after expiry to receive the calculated payoff in proportion to the token holdings
  • Cover token holders execute settle() after expiry to receive the calculated (collateral - payoff) in proportion to the token holdings

From https://github.com/bokkypoobah/Optino/blob/master/contracts/OptinoFactory.sol:

contract OptinoV1 {
    using SafeMath for uint;

    function shiftRightThenLeft(uint amount, uint8 right, uint8 left) internal pure returns (uint _result) {
        if (right == left) {
            return amount;
        } else if (right > left) {
            return amount.mul(10 ** uint(right - left));
        } else {
            return amount.div(10 ** uint(left - right));
        }
    }
    function computeCollateral(uint[5] memory _seriesData, uint tokens, uint8[4] memory decimalsData) internal pure returns (uint _collateral) {
        (uint callPut, uint strike, uint bound) = (_seriesData[uint(OptinoFactory.SeriesDataFields.CallPut)], _seriesData[uint(OptinoFactory.SeriesDataFields.Strike)], _seriesData[uint(OptinoFactory.SeriesDataFields.Bound)]);
        (uint8 decimals, uint8 baseDecimals, uint8 quoteDecimals, uint8 rateDecimals) = (decimalsData[0], decimalsData[1], decimalsData[2], decimalsData[3]);
        require(strike > 0, "strike must be > 0");
        if (callPut == 0) {
            require(bound == 0 || bound > strike, "Call bound must = 0 or > strike");
            if (bound <= strike) {
                return shiftRightThenLeft(tokens, baseDecimals, decimals);
            } else {
                return shiftRightThenLeft(bound.sub(strike).mul(tokens).div(bound), baseDecimals, decimals);
            }
        } else {
            require(bound < strike, "Put bound must = 0 or < strike");
            return shiftRightThenLeft(strike.sub(bound).mul(tokens), quoteDecimals, decimals).div(10 ** uint(rateDecimals));
        }
    }
    function computePayoff(uint[5] memory _seriesData, uint spot, uint tokens, uint8[4] memory decimalsData) internal pure returns (uint _payoff) {
        (uint callPut, uint strike, uint bound) = (_seriesData[uint(OptinoFactory.SeriesDataFields.CallPut)], _seriesData[uint(OptinoFactory.SeriesDataFields.Strike)], _seriesData[uint(OptinoFactory.SeriesDataFields.Bound)]);
        return _computePayoff(callPut, strike, bound, spot, tokens, decimalsData);
    }
    function _computePayoff(uint callPut, uint strike, uint bound, uint spot, uint tokens, uint8[4] memory decimalsData) internal pure returns (uint _payoff) {
        (uint8 decimals, uint8 baseDecimals, uint8 quoteDecimals, uint8 rateDecimals) = (decimalsData[0], decimalsData[1], decimalsData[2], decimalsData[3]);
        if (callPut == 0) {
            require(bound == 0 || bound > strike, "Call bound must = 0 or > strike");
            if (spot > 0 && spot > strike) {
                if (bound > strike && spot > bound) {
                    return shiftRightThenLeft(bound.sub(strike).mul(tokens), baseDecimals, decimals).div(spot);
                } else {
                    return shiftRightThenLeft(spot.sub(strike).mul(tokens), baseDecimals, decimals).div(spot);
                }
            }
        } else {
            require(bound < strike, "Put bound must = 0 or < strike");
            if (spot < strike) {
                 if (bound == 0 || (bound > 0 && spot >= bound)) {
                     return shiftRightThenLeft(strike.sub(spot).mul(tokens), quoteDecimals, decimals + rateDecimals);
                 } else {
                     return shiftRightThenLeft(strike.sub(bound).mul(tokens), quoteDecimals, decimals + rateDecimals);
                 }
            }
        }
    }
}