Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 2 parties can buy callSpread positions separately #2019

Merged
merged 1 commit into from
Nov 19, 2020

Conversation

Chris-Hibbert
Copy link
Contributor

@Chris-Hibbert Chris-Hibbert commented Nov 13, 2020

This is a separate Zoe contract from the previous call spread, which
is renamed to fundedCallSpread (because the first party funds the
entire payout). The new one is pricedCallSpread (the first party
decides the prices, and gets two invitations to buy each position
separately.)

Common code is mostly factored out. There are 30 lines of boilerplate
at the beginning of each contract that seems better visible there
rather than hidden behind function calls.

I made a start at generating typescript declarations, but don't see
what eslint doesn't like about the current state. (The error messages
just say "syntax error", which doesn't help.

Also there's a weirdness in fundedCallSpread (marked with a TODO). I
want to replace sequential await calls with a single Promise.all(),
but the replacement code doesn't work as I expect it to. If anyone can
spot my mistake, I'd appreciate it.

fixes: #1961

@Chris-Hibbert Chris-Hibbert added the Zoe package: Zoe label Nov 13, 2020
@Chris-Hibbert Chris-Hibbert added this to the Beta Launch milestone Nov 13, 2020
@Chris-Hibbert Chris-Hibbert self-assigned this Nov 13, 2020
@Chris-Hibbert Chris-Hibbert marked this pull request as draft November 13, 2020 21:25
@Chris-Hibbert Chris-Hibbert force-pushed the 1961-callSpreadPairs branch 4 times, most recently from 4d72a49 to 9b8d043 Compare November 17, 2020 17:38
@Chris-Hibbert Chris-Hibbert marked this pull request as ready for review November 17, 2020 17:49
Copy link
Contributor

@katelynsills katelynsills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments about types. I also made this draft PR with the ability to alienate payouts that we had talked about, to further discussion: #2028

});
}

function makeInvitationPair(buyPercent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think buyPercent should be part of the terms

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, buyPercent should probably be named longCollateralShare or something like that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think buyPercent should be part of the terms

If it were part of the terms, it would be visible to the internal options. (and any alienables that result.) I don't think they belong there. The call spread options that result have value based only on their payout, and how they were funded is irrelevant.

buyPercent should probably be named longCollateralShare or something like that

you're right that it should say long rather than buy. I'll add collateral and think about how share's succintness compares to percent's clarity.

Comment on lines 18 to 24
*
* @param {ContractFacet} zcf
* @param {Record<PositionKind,PromiseRecord<ZCFSeat>>} seatPromiseKits
* @param {ZCFSeat} collateralSeat
* @returns {PayoffHandler}
*/
/** @type {MakePayoffHandler} */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we have a typedef and a type, but we should only have the type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 15 to 20
* @param {AmountMath} strikeMath the math to use
* @param {Amount} price the value of the underlying asset at closing that
* determines the payouts to the parties
* @param {Amount} strikePrice1 the lower strike price
* @param {Amount} strikePrice2 the upper strike price
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to types.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

@katelynsills katelynsills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly small changes in regards to types, function signatures, and wording.

* This contract implements a fully collateralized call spread. This is a
* combination of a call option bought at one strike price and a second call
* option sold at a higher price. The invitations are produced in pairs, and the
* purchaser pays the entire amount that will be paid out. The individual
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* purchaser pays the entire amount that will be paid out. The individual
* instance creator escrows upfront all collateral that will be paid out. The individual

We shouldn't be talking about "purchasing" or "buy" since no buying is happening in any of the call spread contracts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/**
* Constants for long and short positions.
*
* @type {{ LONG: 'long', SHORT: 'short' }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this type definition into types and use a reference here to that type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm following the model of MathKind.

AmountMathKind is defined in ERTP/src/types.js, and PositionKind is defined in callSpread/types.js.
The Enum is MathKind, defined in ERTP/src/amountMath.js, which has an @type annotation.
Here, the Enum is Position, defined in callSpread/position.js, with an @type annotation.

The use of 'Kind' in the two cases isn't consistent, but the type declarations are consistent. The enum is MathKind and its type is AmountMathKind. Here the enum is Position, and the type is PositionKind.

The enum is declared in a code file, with an @type that enumerates the keys and values. The type is declared as a list of alternatives. Code that wants to use a value uses the enum name, dot, and a value name: Position.SHORT or MathKind.SET. Declarations use the type name: Position or MathKind.


const denominator = strikeMath.subtract(strikePrice2, strikePrice1).value;
const numerator = strikeMath.subtract(price, strikePrice1).value;
return floorDivide(multiply(PERCENT_BASE, numerator), denominator);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we return both the long share and the short share here? Only returning the long share doesn't match the name of the function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

function payoffOptions(priceQuoteAmount) {
const { amountOut } = priceQuoteAmount.value[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this just take an amount rather than a priceQuote? I think it's nicer to decouple the payout function from how the price is derived?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +43 to +47
const totalCollateral = terms.settlementAmount;
const collateralShare = floorDivide(
multiply(totalCollateral.value, sharePercent),
PERCENT_BASE,
);
const seatPortion = collateralMath.make(collateralShare);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than calculate this for each seat, why not calculate once and pass in the Collateral amount for each seat rather than the sharePercent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order for payoffOptions() to pass in a different amount in each call to reallocateToSeat(), it would have to calculate collateralShare twice. This way, we only have to write the call to calculate it once.

}

function reallocateToSeat(position, sharePercent) {
seatPromiseKits[position].promise.then(seat => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we pass in this seat instead of getting it within the function? It seems cleaner to me to not have this function rely on seatPromiseKits being in scope

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/

/**
* @typedef {(strikeMath:AmountMath, price:Amount, strikePrice1:Amount,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the @callback rather than @typedef notation for functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

/**
* @typedef {Object} PayoffHandler
* @property {() => void} schedulePayoffs
* @property {(positionKind: PositionKind) => Promise<Payment>} makeOptionInvitation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this out into it's own definition so we can use in line 29 of payoffHandler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

want: { Option: longOption },
give: { Collateral: bucks(longOptionValue.collateral) },
});
const bobPurchaseSeat = await zoe.offer(await longInvitation, bobProposal, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a purchase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense from a Zoe perspective, but trades who specialize in financial derivatives would speak of buying or selling a call spread. This is the seat that Bob uses to buy exposure to the long position of the call spread.

want: { Option: shortOption },
give: { Collateral: bucks(shortOptionValue.collateral) },
});
const carolPurchaseSeat = await zoe.offer(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. I wouldn't call this a purchase. It's escrowing margin.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@katelynsills katelynsills self-requested a review November 19, 2020 01:57
Copy link
Contributor

@katelynsills katelynsills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't know how I missed this earlier, but there is a fundamental problem in how we are describing the pricedCallSpread. The code seems fine, but when I was reading through the documentation, the words purchase, buy and price kept coming up, even though there is no purchase occurring in either of these contracts. I think we are confusing the adding of collateral with a purchase.

Because of that, the second contract shouldn't be called priced. It should be called selfFunded or something of that sort. We really need to take out any mention of purchasing or price. The users will think that there is a sale of an invitation, but there is not. That happens in a different contract.

@Chris-Hibbert
Copy link
Contributor Author

when I was reading through the documentation, the words purchase, buy and price kept coming up, even though there is no purchase occurring in either of these contracts. I think we are confusing the adding of collateral with a purchase.

From a Zoe point of view I can see how this makes sense, but people who are trading in call spread options or other financial instruments do think of what they're doing as "buying" and "selling" options. They explicitly talk about buying exposure to a particular combination of risk and reward.

I agreed with you in some cases earlier: I had changed from speaking of the two positions as 'buy' and 'sell' to being 'long' and 'short', but I think it's reasonable to speak of both parties as "buying" an option.

@katelynsills
Copy link
Contributor

From a Zoe point of view I can see how this makes sense, but people who are trading in call spread options or other financial instruments do think of what they're doing as "buying" and "selling" options. They explicitly talk about buying exposure to a particular combination of risk and reward.

Yes, but in those cases, they are actually buying it, aren't they? It would be the level at which money is traded for an invitation.

Copy link
Contributor

@katelynsills katelynsills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

This is a separate Zoe contract from the previous call spread, which
is renamed to fundedCallSpread (because the first party funds the
entire payout). The new one is pricedCallSpread (the first party
decides the prices, and gets two invitations to buy each position
separately.)

Common code is mostly factored out. There are 30 lines of boilerplate
at the beginning of each contract that seems better visible there
rather than hidden behind function calls.

I made a start at generating typescript declarations, but don't see
what eslint doesn't like about the current state. (The error messages
just say "syntax error", which doesn't help.

Also there's a weirdness in fundedCallSpread (marked with a TODO). I
want to replace sequential await calls with a single Promise.all(),
but the replacement code doesn't work as I expect it to. If anyone can
spot my mistake, I'd appreciate it.
@Chris-Hibbert Chris-Hibbert merged commit 2b19988 into master Nov 19, 2020
@Chris-Hibbert Chris-Hibbert deleted the 1961-callSpreadPairs branch November 19, 2020 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Zoe package: Zoe
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable Exchanges to sell paired invitations for Call-Spread
2 participants