-
Notifications
You must be signed in to change notification settings - Fork 226
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
Conversation
4d72a49
to
9b8d043
Compare
c875fde
to
c615489
Compare
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
* | ||
* @param {ContractFacet} zcf | ||
* @param {Record<PositionKind,PromiseRecord<ZCFSeat>>} seatPromiseKits | ||
* @param {ZCFSeat} collateralSeat | ||
* @returns {PayoffHandler} | ||
*/ | ||
/** @type {MakePayoffHandler} */ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
* @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 | ||
* |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* 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.
There was a problem hiding this comment.
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' }} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
const totalCollateral = terms.settlementAmount; | ||
const collateralShare = floorDivide( | ||
multiply(totalCollateral.value, sharePercent), | ||
PERCENT_BASE, | ||
); | ||
const seatPortion = collateralMath.make(collateralShare); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this 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.
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. |
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. |
10563bf
to
4c58a3f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
4c58a3f
to
3469bb5
Compare
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.
3469bb5
to
251bc97
Compare
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