Manage Collateral

Partially collateralized positions can go underwater as spot price and time to expiry change. In this example, we build an on-chain collateral manager to reduce risk of liquidations of multiple large options positions. If a portfolio contains 1x short call and 1x short put and ETH price goes increases from $3,000 -> $3500 we have an opportunity to rebalance collateral from short put -> short call without requiring any extra funds.
In this guide, we will create an example collateral manager contract that interact with the Lyra markets via the LyraAdapter.sol:

1. Import LyraAdapter.sol

As in the Trading example, to greatly simplify integration, our manager contract can inherit the LyraAdapter.sol which contains all the standard Lyra functions in one place.
Install the @lyrafinance/protocol package and follow the setup instructions.
pragma solidity 0.8.9;
import {LyraAdapter} from "@lyrafinance/protocol/contracts/periphery/LyraAdapter.sol";
contract CollateralManagerExample is LyraAdapter {
constructor() LyraAdapter();
function initAdapter(
address _lyraRegistry,
address _optionMarket,
address _curveSwap,
address _feeCounter
) external onlyOwner {
// set addresses for LyraAdapter
setLyraAddresses(_lyraRegistry, _optionMarket, _curveSwap, _feeCounter);
}
}
Call getMarketDeploys()/getGlobalDeploys() via @lyrafinance/protocol to get deployment addresses.

2. Transfer position ownership to manager

Assuming you already have open positions, we must first transfer ownership of these positions to the manager:
import { getMarketDeploys } from '@lyrafinance/protocol';
let lyraMarket = getMarketDeploys('kovan-ovm', 'sETH');
const optionToken = new Contract(lyraMarket.OptionToken.address, lyraMarket.OptionToken.abi, deployer);
// may need to add your own routine (try/catch, gas limit, etc)
await optionToken["transferFrom"](deployer.address, collateralManagerAddress, positionId);

3. Only track short positions

After transfering ownership, we record the positions in the manager contract. LyraAdapter._getPositions() can be used to get all position details. As we are inheriting LyraAdapter we can also use the built-in structs.
uint[10] public trackedPositionIds; // setting hard 10x position limit
mapping(uint => OptionPosition) public trackedPositions;
uint public positionHead = 0;
function trackPositions(uint[] positionIds) external onlyOwner {
require(positionHead + positionIds.length <= trackedPositions.length,
"exceeded max # of tracked positions");
OptionPosition[positionIds.length] positions = _getPositions(positionIds);
for (uint i = 0; i < positionIds.length; i++) {
// screen out long positions
if (positions[i].state == PositionState.ACTIVE
&& positions[i].optionType != OptionType.LONG_CALL
&& positions[i].optionType != OptionType.LONG_PUT
) {
trackedPositionIds[positionHead] = positionIds[i];
trackedPositions[positionIds[i]] = positions[i];
positionHead++
}
}
}
For brevity, we skip over functions that allow manual removal of positions.

4. Calculate minimum collateral

To decide whether we want to topOff or take excess collateral from a position, we calculate the minCollateral using LyraAdapter._getMinCollateralForPosition() and add a 50% buffer. The direct alternative to this is OptionGreekCache.getMinCollateral() but requires more cross-contract calls.
function _getTargetCollateral(uint positionId)
internal returns (uint targetCollateral) {
targetCollateral = _getMinCollateralForPosition(positionId).multiplyDecimal(1e18 + 50 * 1e16);
}
If we wanted to estimate the minCollatateral if price jumped 50% we could use LyraAdapter._getMinCollateral() which takes in manual params such as spotPrice, expiry.

5. Gather excess collateral and flag "risky" positions

Now that we can calculate the targetCollateral for each position, we can gather collateral from excess positions and flag positions below our target collateral. We create flaggedPositionIds and neededCollat arrays to keep track of "risky" positions and gatheredCollat to track total funds held by the manager.
uint[] flaggedPositionIds;
mapping(uint => uint) neededCollat;
uint gatheredCollat;
function gatherAndFlag()
external {
delete flaggedPositionIds; // clear out outdated flags/collateral
TradeInputParameters tradeParams;
for (uint i; i < positionHead; i++) {
OptionPosition currentPosition = trackedPositions[trackedPositionIds[i]];
uint currentCollat = currentPosition.collateral;
uint targetCollat = _getTargetCollateral(trackedPositionIds[i]);
if (currentCollat > targetCollat) { // if there is excess collateral
tradeParams = TradeInputParameters({
strikeId: currentPosition.strikeId,
positionId: currentPosition.positionId,
iterations: 1, // no need to optimize iterations as amount does not change
optionType: currentPosition.optionType,
amount: 0,
setCollateralTo: targetCollat, // returns any excess collateral
minTotalCost: 0,
maxTotalCost: type(uint).max
});
_openPosition(tradeParams); // using _closePosition() would have the same effect
gatheredCollat += currentCollat - targetCollat;
// update position records
trackedPositions[trackedPositionIds[i]] = _getPositions([trackedPositionIds[i]])[0];
} else { // if collateral below target, flag position
neededCollat[trackedPositionIds[i]] = targetCollateral - currentCollat;
flaggedPositionIds.push(trackedPositionIds[i]);
}
}
}
To change collateral we can use LyraAdapter._openPosition(). Setting the setCollateralTo param to targetCollat returns any excess collateral to msg.sender.
To avoid dealing with ETH/USD conversions, we assume the portfolio only uses quote collateral.

6. Topoff or close "risky" positions

We can topoff positions the same way we gathered excess collateral. If our manager runs out of funds, the contract reverts to simply closing the position.
function topoffOrClose() external {
OptionPosition currentPosition;
TradeInputParameters tradeParams;
for (uint i; i < flaggedPositionIds.length; i++) {
currentPosition = trackedPositions[flaggedPositionIds[i]];
tradeParams = TradeInputParameters({
strikeId: currentPosition.strikeId,
positionId: currentPosition.positionId,
iterations: 1,
optionType: currentPosition.optionType,
amount: 0,
setCollateralTo: _getTargetCollateral(currentPosition.positionId),
minTotalCost: 0,
maxTotalCost: type(uint).max
});
if (gatheredCollat >= neededCollat[i]) {
_openPosition(tradeParams);
gatheredCollat -= neededCollat[i];
} else { // fully close position if not enough collateral
tradeParams.setCollateral = 0;
tradeParams.amount = currentPosition.amount;
if (_needsForceClose(OptionPosition position)) {
_forceClosePosition(tradeParams);
} else {
_closePosition(tradeParams);
}
}
neededCollat[i] = 0;
}
delete flaggedPositionIds;
}

7. Decide between closePosition and forceClose

When closing positions in the above function, we had to determine whether we need to use closePosition() and forceClosePosition() as sometimes positions may be outside of the delta cutoff range or too close to expiry. We can use the built-in LyraAdapter.closeOrForceClosePosition() function to make this decision:
function _closeOrForceClosePosition(TradeInputParameters memory params)
internal
returns (TradeResult memory tradeResult) {
if (!_isOutsideDeltaCutoff(params.strikeId) && !_isWithinTradingCutoff(params.strikeId)) {
return _closePosition(params);
} else {
// will pay less competitive price to close position but bypasses Lyra delta/trading cutoffs
return _forceClosePosition(params);
}

Trading Rewards

Refer to trading rewards