-
Notifications
You must be signed in to change notification settings - Fork 8
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
Experimental re-write, but using Transducers #151
Conversation
Codecov Report
@@ Coverage Diff @@
## master #151 +/- ##
==========================================
- Coverage 96.34% 0.00% -96.35%
==========================================
Files 9 11 +2
Lines 438 299 -139
==========================================
- Hits 422 0 -422
- Misses 16 299 +283
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
I think through a |
struct CashflowProjection <: ProjectionKind end | ||
|
||
@inline function Transducers.__foldl__(rf, val, p::Projection{C,M,K}) where {C<:Cashflow,M,K} | ||
for i in 1:1 |
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.
This confuses me a bit
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 just pushed an update to that file with a bunch of commentary, hopefully that helps?
I updated the original post to include:
Actually, right now a |
If you find an API you really like for it then looking forward to using it. If it isn't clear how to handle it after some consideration then it is ok to ship something simple and save dates for a minor release after more usage reveals pain points in usage. Did my best to look through all the code. Some of the things like I probably don't have much time for review beyond the comments already made. If I ever write a reasonable actuarial model, I will post in the discussion and ask about what types of analysis are necessary on the produced CashFlows. |
These thoughts aren't warranting any response, just thinking out loud. Overall the full example helped me see how to use the software. May be unable to respond for some time, this is too interesting and is making it hard to do homework. TransducersYou say it isn't user-facing, but if I define a contract like -
I assume that all of LifeContingencies.jl will become transducers?
My takeaway is that you think it is a good way to do things and want people to learn it. Transducers in lifecontingencies
I guess aggregating over the time dimension looks like this - 1:3 |> Zip(Map(identity), Map(x -> 10x), Map(x -> 100x)) |> Map(sum) |> collect Now I'm considering what modeling new business would look like while doing efficient aggregations for each timestep, unsure.
AccessibleOptimization
section 6
|
Interesting thoughts. It is late so pardon the messy response. Transducers, GenerallyTransducers are a very rich and elegant concept, but the surface exposed here is small:
Transducers are more complex conceptually than basic iterators, but I have found them to be vastly superior to iterators. E.g. trying to write how a I think it's a bit of a risk but I think that the flexibility and expressiveness will be worth it. Part of my motivation for the simple examples above is to introduce enough that users aren't scared by it if they want to extend functionality. Transducers for LiabilitiesEarlier versions of LIfeContingencies did use Transducers for, you guessed it, the parts that did not require an exclusive Scan operation. The rest of the package just used arrays passed around. I found that I could improve performance by using generators instead and lazily pass around interim calculations in a Transducers-lite way. However, the generators can have type stability issues (there is no Misc
I added it, thanks for catching. |
Good to see this moving forward, and to me it looks like the right direction. I'll go by the numbering in the OP.
Loads of interesting ideas here :-). I'll be back with more later |
Thanks for your feedback @kasperrisager !
Thanks for the feedback here. I'm open to suggestions, I would just prefer to ensure that things don't get overly verbose. One defense of the current names are that it's all circular: a quote is a price someone was willing to transact at -> implies a valuation model -> calculates a price one would be willing to transact at. Related: one thing I thought about but did not implement (yet?) was providing a
Yes, I agree with your observation that there is some inconsistency:
If I understand correctly,
You mean dates and floating point timepoints? This is another area I have to give some more thought, but with whatever With that point of view, you could have heterogeneously typed timepoint contracts. One may question why one would want the precision of
That's how it's handled under the hood - contracts without any other information needed are wrapped in a |
Yes. But when I come to think about it, there might be a reason to have a
Yes and no. I can see use cases where the cash flows are given at I think I was arguing for the requirement that the two different ways were not mixed in the same composite, but you are probably right that it should not be constrained unless absolutely necessary. |
Btw, when I try to run this locally, I get the same package resolution error as CI does, do you have a known fix for that? |
You need to |
It seems like the convention has converged (pun intended) around the |
I tried to consolidate some of the outstanding questions into the checklist in the OP. Over the coming days I'm going to try to wrap this up and merge the various PRs to the master branches and then in the coming weeks try to synchronize a version bump along with a blog post highlighting major changes and how to migrate if necessary. |
I inserted a new section 7 which describes what/why/how for the |
Merging this but not releasing yet, let me know if anyone has feedback on the outstanding questions. Check out the new docs (dev, not stable until release is tagged) Will release after some more review and I get a blog post associated with the ecosystem changes. With the merging of this PR, the master branches of ActuaryUtilities, FinanceCore, and Yields/FinanceModels should all be in sync (still need to be |
This first section is a draft of a blog-post disucssing ecosystem updates.
FinanceModels.jl: The Evolution of Yields.jl
Yields.jl is now FinanceModels.jl
This re-write accomplishes three primary things:
Quotes
Cashflow
via a flexibily definedProjection
fit
with a new unified API:fit(model_type,quotes,fit_method)
1.
Cashflow
- a fundamental financial typeSay you wanted to model a contract that paid quarterly payments, and those payments occurred starting 15 days from the valuation date (first payment time = 15/365 = 0.057)
Previously, you had two options:
[1,0,0,1,0,0...]
cfs = [1,1,...];
times =[0.057, 0.307...]
The former has inaccuracies due to the simplified timing and logical complication related to mapping the contracts natural periodicity into an arbitrary modeling choice. The latter becomes unwieldy and fails to take advantage of Julia's type system.
The new solution:
Cashflow
s. Our example above would become:[Cashflow(1,0.057), Cashflow(1,0.307),...]
2. Contracts - A composable way to represent financial instruments
Contracts are a composable way to represent financial instruments. They are, in essence, anything that is a collection of cashflows. Contracts can be combined to represent more complex instruments. For example, a bond can be represented as a collection of cashflows that correspond to the coupon payments and the principal repayment.
Examples:
Cashflow
Bond
s:Bond.Fixed
,Bond.Floating
Option
s:Option.EuroCall
andOption.EuroPut
Forward
to represent an instrument that is relative to a forward point in time.Composite
to represent the combination of two other instruments.In the future, this notion may be extended to liabilities (e.g. insurance policies in LifeContingencies.jl)
Creating a new Contract
A contract is anything that creates a vector of
Cashflow
s whencollect
ed. For example, let's create a bond which only pays down principle and offers no coupons.That's it! then we can use this fitting models, projections, quotes, etc. Here we simply collect the bond into an array of cashflows:
Note that all contracst in FinanceModels.jl are currently unit contracts in that they assume a unit par value.
More complex Contracts
When the cashflow depends on a model. An example of this is a floating bond where the coupon paid depends on a view of forward rates. See section 6 on projections for how this is handled.
3.
Quote
s - The observed price we need to fit a model toQuotes are the observed prices that we need to fit a model to. They represent the market prices of financial instruments, such as bonds or swaps. In the context of the package, a quote is defined as a pair of a contract and a price.
For example, a par yield bond paying a 4% coupon (paid as 2% twice per annum) implies a price at par (i.e.
1.0
):A number of convenience functions are included to construct a
Quote
:ZCBPrice
andZCBYield
ParYield
CMTYield
OISYield
ForwardYields
4. Models - Not just yield curves anymore
BlackScholesMerton
option pricing and one can use constant or spline volatility modelsCreating a new model
Here we'll do a complete implementation of a yield curve model where the discount rate is approximated by a straight line (often called an AB line from the
y=ax+b
formula.Now,
m
is a model like any of the other yield curve models provided and can be used in that context. For example, calculating the price of the bonds contained within ourquotes
where we indeed recover the prices for our contrived example:5.
fit
- The standardized API for all models, quotes, and methodsSpline.Linear()
,Yield.NelsonSiegelSvensson()
,Equity.BlackScholesMerton(...)
, etc.CMTYield
s,ParYield
s,Option.Eurocall
, etc.Fit.Loss(x->x^2)
,Fit.Loss(x->abs(x))
,Fit.Bootstrap()
, etc.The benefit of this versus the old Yields.jl API is:
fit
method, no obvious way to expose different curve construction methods (e.g. choice of model and method)fit
is extensible. Users or other packages could define their own Models, Quotes, or Methods and integrate into the JuliaActuary ecosystem.fit
formulation is very generic: the required methods are minimal to integrate in order to extend the functionality.Customizing model fitting
Model fitting can be customized:
fit
:fit(ABDiscountLine(), quotes, FIt.Loss(x -> abs(x))
Fit.Loss(x->x^2)
FinanceModels.__default_optim__(m::ABDiscountLine) = OptimizationOptimJL.Newton()
__default_optic
to be unbounded (simply omit the=>
and subsequent bounds)Spline.Linear()
,Spline.Cubic()
,...) are defined to use boostrap by default:fit(mod0::Spline.BSpline, quotes, method::Fit.Bootstrap)
6.
Projection
sA
Projection
is a generic way to work with various data that you can project forward. For example, getting the series of cashflows associated with a contract.What is a
Projection
?contract
is obvious, so let's talk more about the second two:model
is the same kind of thing we discussed above. Some contracts (e.g. a floating rate bond). We can still decompose a floating rate bond into a set of cashflows, but we need a model.NullModel()
kind
defines what we'll return from the projection.CashflowProjection()
says we just want aCashflow[...]
vectorAssetDetailProjection()
CashflowProjection()
is defined by FinanceModels.jlContracts that depend on the model (or multiple models)
For example, the cashflows you generate for a floating rate bond is the current reference rate. Or maybe you have a stochastic volatilty model and want to project forward option values. This type of dependency is handled like this:
model
as a relation that maps a key to a model. E.g. a Dict("SOFR" => NelsonSiegelSvensson(...))`Projection.model
by the assocated key.Here's how a floating bond is implemented:
The contract struct. The
key
would be "SOFR" in our example above.And how we can reference the assocated model when projecting that contract. This is very similar to the definition of
__foldl__
for ourPrincipleOnlyBond
, except we are paying a coupon and referencing the scenario rate.In this post we've now defined two assets that can work seamlessly with projecting cashflows, fitting models, and determining valuations :)
7.
ProjectionKind
sWhile
CashflowProjection
is the most common (and the only one built into the inital release of FinanceModels), aProjection
can be created which handles different kinds of outputs in the same manner as projecting just basic cashflows. For example, you may want to output an amortization schedule, or a financial statement, or an account value roll-forward. TheProjection
is able to handle these custom outputs by dispatching on the third element in aProjection
.Let's extend the example of a principle-only bond from section 2 above. Our goal is to create a basic amortization schedule which shows the payment made and outstanding balance.
First, we create a new subtype of
ProjectionKind
:And then define the loop for the amortization schedule output:
We can now define the projection:
And then collect the values:
8. Post-script
FAQ
Transducers vastly simplified the iteration and state handling needed when projecting the contracts (see dead PR Experimental re-write and rename to FinanceModels.jl #150). The performance remains excellent and made a lot of the internals much simpler.
For the most part, this isn't user facing but where it is (e.g. extending to the functionality
PrincpleOnlyBond
, there's actually very little, very straightforward logic to implement for the user.Work-in-progress notes
closes #145, #144, #42, #50
Same thing as #150 but using Transducers instead of iteration interface
Supersedes #146, which took a type-based approach to
Composite
structs while this takes more of avector
based approach to combining atomic cashflows.Currently requires the associated dev branches of FinanceCore, Yields (aka FinanceModels), and ActuaryUtilities to work together. WIll be breaking change for Yields, TBD on ActuaryUtilities and FinanceCore. Associated PRs:
TODOs
bond.frequency.frequency
is awkward, will make a getter functionDate
s #155 )ProjectionKind
To get input on:
should the name be
Instruments
orObservables
orContracts
for the provided financial instruments?Should the quotes (e.g.
ParYield
orZCBPrice
) have a quote in the name since they create quotes?How to distinguish/indicate "the thing that needs to be fit"
E.g. when you want to specify what kind of volatility model to fit a BSM to option quotes?
Use a non-initialized type?
fit(BlackScholesMerton(0.01,0.02,ConstantVolatility),qs)
Use some form of "concrete" type but the model to be fit is represented by some "uninitialized" type (e.g. like
SumTypes.jl
Ergonomics:
Package design:
pv
to FinanceCore given its utility hereCashflow
up to FCMaybe:
FinanceCore.CompoundingFrequency
to justFrequency
since it's used byInstruments
for payment frequency?Bootstrap
(created Move some code to extensions #153)UnitCashflow
as a specializedCashflow
? (TBD Later if desired)