Last active
November 14, 2024 16:57
-
-
Save CHatmaker/5dcb3de4ad9766d1baf0b95338ebdbf3 to your computer and use it in GitHub Desktop.
5G functions for Excel for Financial Models: deprecated on 2024-05-29
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* FUNCTION NAME: Aboutλ | |
DESCRIPTION:*//**Displays the URL to this module's Gist which includes documentation*/ | |
/* REVISIONS: Date Developer Description | |
Jul 13 2023 Craig Hatmaker Original Development | |
May 13 2024 Craig Hatmaker Spring 2024 Version | |
May 18 2024 Craig Hatmaker Added RollingAvgλ, RollingMinλ and RollingMaxλ | |
May 28 2024 Craig Hatmaker See SumContainsλ | |
May 29 2024 Craig Hatmaker See LabelAmortizeλ | |
*/ | |
Aboutλ = TRIM(TEXTSPLIT( | |
"ABOUT: →BXL's Financial Models module. Suggested module name: BXF¶" & | |
"VERSION: →May 29 2024¶" & | |
"GIST URL: →https://gist.github.com/CHatmaker/5dcb3de4ad9766d1baf0b95338ebdbf3 ¶" & | |
"WEBSITE: →https://sites.google.com/site/beyondexcel/home/excel-library/ ¶" & | |
"→¶" & | |
"FUNCTION →DESCRIPTION¶" & | |
"Aboutλ →Produces this table¶" & | |
"Corkscrewλ →Creates a simple corkscrew where the closing balance is the sum of independent flows plus opening balance¶" & | |
"CorkScrewReversalλ →Create a corkscrew where the opening balance is reversed in the next period based on a timing row¶" & | |
"Cumulativeλ →Creates a row or column of cumulative totals from a total row or column¶" & | |
"IntOnIntλ →Calculate Interest on Interest. Use to determine amount needed to cover debt plus interest on debt¶" & | |
"IRRλ →Calculates IRR, correcting for when the first investment is not in the first period¶" & | |
"Movementλ →Create a row of differences from column to column¶" & | |
"Reversalλ →Create a row that reverses input values in the next period.¶" & | |
"RollingAvgλ →Finds the maximum value the n preceeding values in a set moving from beginning to end over a row of values.¶" & | |
"RollingMaxλ →Finds the maximum value the n preceeding values in a set moving from beginning to end over a row of values.¶" & | |
"RollingMinλ →Finds the minimum value the n preceeding values in a set moving from beginning to end over a row of values.¶" & | |
"RollingSumλ →Creates totals for preceeding values of a set size moving from beginning to end over a row of values.¶" & | |
"SumContainsλ →Creates a row of totals for each row in an array where its labels contain a unique letter, word, or phrase.¶" & | |
"SumPeriodsλ →Groups and totals all columns in a Values array by period resulting in one column for each period.¶" & | |
"→¶" & | |
"AMORTIZATION SUITE→¶" & | |
"Amortizeλ →Creates a corkscrew amortization schedule.¶" & | |
"LabelAmortizeλ →Create row labels for Amortizeλ result¶" & | |
"SumAmortizeλ →Create row totals for payments, interest, and principal portion in Amortizeλ results¶" & | |
"→¶" & | |
"DEPRECIATION SUITE→¶" & | |
"Depreciateλ →Create a block of CAPEX, Opening Balance, Depreciation Values, and Book Value for each asset¶" & | |
"LabelDepreciateλ →Create row labels for Depreciateλ result¶" & | |
"SumDepreciateλ →Create row totals for CAPEX, Depreciation, Book Value, Salvage Value, and Disposal costs in Depreciateλ results¶" & | |
"Allocateλ →Divide one or more amounts equally equally across smaller periods¶" & | |
"SLNλ →Straight Line depreciation method for one asset or asset class.¶" & | |
"SYDλ →Sum-of-years' digits depreciation method for one asset or asset class.¶" & | |
"DBλ →Fixed declining balance depreciation method for one asset or asset class.¶" & | |
"DDBλ →Double-declining balance depreciation method for one asset or asset class.¶" & | |
"VDBλ →Variable declining balance depreciation method for one asset or asset class.¶" & | |
"→¶" & | |
"SUBROUTINES →Functions used by other functions or general purpose functions.¶" & | |
"FilterContainsλ →Filter an array by another array that contains specifice text¶" & | |
"RangeToDAλ →Create a dynamic array from a static range¶" & | |
"TimelineOffsetλ →Determines how many columns a date is offset from a timeline's first date¶" & | |
"TimelinePositionλ →Places an array or value appropriately within a model's timeline.¶" & | |
"PeriodStartλ →Determine when a period containing a date of interest starts.¶" & | |
"PeriodDiffλ →Determine the number of periods between two dates.", | |
"→","¶" | |
) | |
); | |
/* FUNCTION NAME: Corkscrewλ | |
DESCRIPTION:*//**Create a corkscrew section that sums the opening balance and up to 4 independent flows.*/ | |
/* REVISIONS: Date Developer Description | |
Mar 06 2023 Craig Hatmaker Original Development | |
Jun 13 2023 Craig Hatmaker Added Help | |
May 01 2924 Craig Hatmaker Modified Help | |
*/ | |
Corkscrewλ = LAMBDA( | |
// Parameter Declarations | |
[Opening], | |
[Flow1], | |
[Flow2], | |
[Flow3], | |
[Flow4], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →Corkscrewλ( Opening, FLow1, [Flow2], [Flow3], [Flow4])¶" & | |
"DESCRIPTION: →Create a corkscrew section that sums the opening balance and up to 4 independent flows¶" & | |
"WEBPAGE: →<Coming Soon>¶" & | |
"VERSION: →Oct 20 2023¶" & | |
"PARAMETERS:→¶" & | |
"Opening →(Required) First periods opening balance.¶" & | |
"Flow1 →(Required) First row (or array of rows) of numbers to include in period totals.¶" & | |
"Flow2 →(Optional) Second row (or array of rows) of numbers to include in period totals.¶" & | |
"Flow3 →(Optional) Third row (or array of rows) of numbers to include in period totals.¶" & | |
"Flow4 →(Optional) Fourth row (or array of rows) of numbers to include in period totals.¶" & | |
"NOTE! →All flows are added. To subtract a flow make it negative.¶" & | |
"→¶" & | |
"EXAMPLES: →NOTE! BXF is assumed to be the module's name¶" & | |
"Formula: →=BXF.Corkscrewλ(0, Sequence(1,5))¶" & | |
"Result: →00, 01, 03, 06, 10¶" & | |
"→01, 02, 03, 04, 05¶" & | |
"→01, 03, 06, 10, 15", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Opening), | |
ISOMITTED( Flow1) | |
), | |
// Processing | |
Cols, Columns(Flow1), | |
Rows, IF(ISOMITTED(Flow2), 1, | |
IF(ISOMITTED(Flow3), 2, | |
IF(ISOMITTED(Flow4), 3, 4) | |
) | |
), | |
PeriodArray, SWITCH(Rows, | |
1, Flow1, | |
2, VSTACK(Flow1, Flow2), | |
3, VSTACK(Flow1, Flow2, Flow3), | |
4, VSTACK(Flow1, Flow2, Flow3, Flow4) | |
), | |
PeriodTotal, BYCOL(PeriodArray, LAMBDA(Col, SUM(Col))), | |
ClosingTotal, SCAN(Opening, PeriodTotal, LAMBDA(Opn, PrdTot, Opn + PrdTot)), | |
OpeningTotal, MAP( SEQUENCE(, Cols), LAMBDA( Col, IF( Col = 1, Opening, INDEX( ClosingTotal, Col - 1)))), | |
Result, VSTACK(OpeningTotal, PeriodArray, ClosingTotal), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: CorkScrewReversalλ | |
DESCRIPTION:*//**Create a corkscrew where the opening balance is reversed in the next period based on a timing parameter.*/ | |
/* REVISIONS: Date Developer Description | |
Apr 19 2024 Craig Hatmaker Copyright - Thank you Finomatics for the suggestion | |
*/ | |
CorkScrewReversalλ=LAMBDA( | |
// Parameter Declarations | |
[Opening], | |
[ReversalFlags], | |
[Flow1], | |
[Flow2], | |
[Flow3], | |
[Flow4], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →Corkscrewλ( Opening, FLow1, [Flow2], [Flow3], [Flow4])¶" & | |
"DESCRIPTION: →Create a corkscrew where the opening balance is reversed in the next period based on a timing parameter¶" & | |
"WEBPAGE: →<Coming Soon>¶" & | |
"VERSION: →Apr 19 2024¶" & | |
"PARAMETERS:→¶" & | |
"Opening →(Required) First periods opening balance.¶" & | |
"ReversalFlags →(Required) A row of ones and blanks (or zeros) indicating when to reverse period totals.¶" & | |
"Flow1 →(Required) First row (or array of rows) of numbers to include in period totals.¶" & | |
"Flow2 →(Optional) Second row (or array of rows) of numbers to include in period totals.¶" & | |
"Flow3 →(Optional) Third row (or array of rows) of numbers to include in period totals.¶" & | |
"Flow4 →(Optional) Fourth row (or array of rows) of numbers to include in period totals.¶" & | |
"NOTE! →All flows are added. To subtract a flow make it negative.", | |
"→", "¶")), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Opening), | |
ISOMITTED( ReversalFlags), | |
ISOMITTED( Flow1)), | |
// Set defaults and constants | |
Cols, COLUMNS(Flow1), | |
Rows, IF(ISOMITTED(Flow2), 1, | |
IF(ISOMITTED(Flow3), 2, | |
IF(ISOMITTED(Flow4), 3, 4) | |
) | |
), | |
// Procedure | |
PeriodArray, SWITCH(Rows, | |
1, Flow1, | |
2, VSTACK(Flow1, Flow2), | |
3, VSTACK(Flow1, Flow2, Flow3), | |
4, VSTACK(Flow1, Flow2, Flow3, Flow4) | |
), | |
PeriodTotal, BYCOL(PeriodArray, LAMBDA(Col, SUM(Col))), | |
Result, REDUCE( 0, SEQUENCE( COLUMNS( Flow1)), | |
LAMBDA( Acc, Col, | |
LET( | |
Opening, IF(Col=1, Opening, TAKE( Acc, -1, -1)), | |
Reversal, -INDEX( ReversalFlags, 1, Col) * Opening, | |
ArrayColumn, CHOOSECOLS( PeriodArray, Col), | |
Stack, VSTACK( | |
Opening, | |
REversal, | |
ArrayColumn, | |
SUM(Opening, Reversal, ArrayColumn)), | |
Result, IF(Col = 1, Stack, HSTACK( Acc, Stack)), | |
Result | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: IntOnIntλ | |
DESCRIPTION:*/ /**Calculate Interest on Interest. Use to determine amount needed to cover debt plus interest on debt*/ | |
/* NOTE: Math source from Diarmuid Early's post: https://www.youtube.com/watch?v=k5rG_MvIWWs&t=787s | |
REVISIONS: Date Developer Description | |
Dec 29 2023 Craig Hatmaker Copyright on LAMBDA | |
*/ | |
IntOnIntλ = LAMBDA( | |
[Principal], | |
[Rate], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →IntOnIntλ( Principal, Rate)¶" & | |
"DESCRIPTION: →Calculate Interest on Interest (Interest only). Use to determine amount ¶" & | |
"→needed to cover borrowed interest¶" & | |
"WEBSITE: →<Coming soon>¶" & | |
"VERSION: →Dec 29 2023¶" & | |
"PARAMETERS: →¶" & | |
"Principal →(Required) Principal amount¶" & | |
"Rate →(Required) Interest percentage rate. For years, APR. For months, APR/12¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"52.63 →=BXF.IntOnIntλ( 1000, 5%)", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Principal), | |
ISOMITTED( Rate) | |
), | |
// Procedure | |
Result, Principal /(1-Rate) - Principal, | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: IRRλ | |
DESCRIPTION:*/ /**Calculates IRR, correcting for when the first investment is not in the first period */ | |
/* REVISIONS: Date Developer Description | |
Oct 07 2023 Craig Hatmaker Initial development | |
*/ | |
IRRλ = LAMBDA( | |
[Values], | |
[Dates], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →IRRλ( Values, Dates)¶" & | |
"DESCRIPTION: →Calculates IRR, correcting for when the first investment is not in the first period¶" & | |
"WEBSITE: →<Coming soon>¶" & | |
"VERSION: →Dec 07 2023¶" & | |
"PARAMETERS: →¶" & | |
"Values →(Required) A row of investments (negative) and distributions (positive)¶" & | |
"→The first entry, whether 0 or an amount, must be for the first period in the timeline¶" & | |
"→and subsequent entries must correspond to periods in the timeline; however,¶" & | |
"→the timeline may extend beyond the end of values.¶" & | |
"Dates →(Required) A timeline for when each investment is made¶" & | |
"→¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"Dates →=EDATE(""1/1/23"", SEQUENCE(,24, 0))¶" & | |
"Values →={0,0,-150,0,-100,10,20,30,40,50,60,70}¶" & | |
"→¶" & | |
"Result →Formula¶" & | |
"24% →=BXF.IRRλ( Values, Dates)", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Values), | |
ISOMITTED( Dates) | |
), | |
// Set Defaults | |
Values, EXPAND(VALUES, 1, COUNTA(Dates), 0), | |
// Procedure | |
FilterBy, Values <> 0, | |
FilteredValues, FILTER( Values, FilterBy), | |
FilteredDates, FILTER( Dates, FilterBy), | |
Result, XIRR( FilteredValues, FilteredDates), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: Movementλ | |
DESCRIPTION:*//**Create a row of differences from column to column*/ | |
/* REVISIONS: Date Developer Description | |
Jun 21 2023 Craig Hatmaker Copyright | |
Apr 16 2024 Craig Hatmaker Handles multiple rows | |
*/ | |
Movementλ = LAMBDA( | |
// Parameter Declarations | |
[BeginningValues], | |
[Values], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →Movementλ( [BeginningValue], Values)¶" & | |
"DESCRIPTION: →Create a row of differences from column to column¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Jun 21 2023¶" & | |
"PARAMETERS: →¶" & | |
"BeginningValues→(Optional) Defaults to 0¶" & | |
"Values →(Required) A row of values to compare column to column¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"100,10,20,-30 →=BXF.Movementλ(,{100,110,130,100}, 4)", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, ISOMITTED( Values), | |
// Set Defaults | |
BeginningValues, IF(OR(ISOMITTED(BeginningValues), BeginningValues=""), 0, BeginningValues), | |
// Procedure | |
Rows, ROWS( Values), | |
Cols, Columns( Values), | |
Seq, SEQUENCE(, Cols), | |
Result, REDUCE(0, SEQUENCE( Rows), | |
LAMBDA( Acc, R, | |
LET( | |
Begin, If( ROWS(BeginningValues) = 1, BeginningValues, INDEX( BeginningValues, R)), | |
Row, CHOOSEROWS( Values, R), | |
NewRow, SCAN( Begin, Seq, | |
LAMBDA( A, C, | |
INDEX( Row, C ) - IF( C = 1, A, INDEX( Row, C - 1)) | |
) | |
), | |
Result, IF( R = 1, NewRow, VSTACK(Acc, NewRow)), | |
Result | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: Reversalλ | |
DESCRIPTION:*//**Create a row that reverses input values in the next period.*/ | |
/* REVISIONS: Date Developer Description | |
Mar 25 2024 Craig Hatmaker Copyright | |
*/ | |
Reversalλ = LAMBDA( | |
// Parameter Declarations | |
[OpeningValue], | |
[RowToReverse], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →Reversalλ( [OpeningValue], RowToReverse)¶" & | |
"DESCRIPTION: →Create a row that reverses input values in the next period.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Mar 25 2024¶" & | |
"PARAMETERS: →¶" & | |
"OpeningValue →(Optional) Defaults to 0¶" & | |
"RowToReverse →(Required) A row of values to reverse the following periods¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"-100,-110,-130 →=BXF.Reversalλ( , {100,110,130})", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, ISOMITTED( RowToReverse), | |
// Set Defaults | |
BeginningValue, IF( ISOMITTED( OpeningValue), 0, OpeningValue), | |
// Procedure | |
Counter, SEQUENCE( , COLUMNS(RowToReverse)), | |
Result, MAP( Counter, | |
LAMBDA(Col, IF(Col = 1, -OpeningValue, -CHOOSECOLS( RowToReverse, Col-1))) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: Cumulativeλ | |
DESCRIPTION:*//**Creates a row or column of cumulative totals from a total row or column*/ | |
/* REVISIONS: Date Developer Description | |
Jan 05 2043 Craig Hatmaker Original Development | |
*/ | |
Cumulativeλ = LAMBDA( | |
// Parameter Declarations | |
[Totals], | |
[OpeningBalance], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →Cumulativeλ( Totals, [OpeningBalance])¶" & | |
"DESCRIPTION: →Creates a row or column of cumulative totals from a total row or column¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Jan 05 2023¶" & | |
"PARAMETERS: →¶" & | |
"Totals →(Required) A row or column of totals from which to run cumulative totals¶" & | |
"OpeningBalance →(Optional) A starting balance. This defaults to 0¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"1,3,6 →=BXF.Cumulativeλ(Sequence(,3))", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, ISOMITTED( Totals), | |
// Procedure | |
Result, SCAN( OpeningBalance, Totals, LAMBDA(PreviousAmount, CurrentAmount, PreviousAmount + CurrentAmount)), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: RollingAvgλ | |
DESCRIPTION:*//**Creates Averages for preceeding values of a set size moving from beginning to end over a row of values.*/ | |
/* REVISIONS: Date Developer Description | |
Jan 30 2024 Craig Hatmaker Copyright | |
*/ | |
RollingAvgλ = LAMBDA( | |
// Parameter Declarations | |
[Values], | |
[Size], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →RollingAvgλ( Values, Size)¶" & | |
"DESCRIPTION: →Creates averages for preceeding values of a set size moving¶" & | |
"→from beginning to end over a row of values¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Jan 30 2024¶" & | |
"PARAMETERS: →¶" & | |
"Values →(Required) A row of values to be averaged.¶" & | |
"Size →(Required) The number of values to average that ¶" & | |
"→include and preceed the current value¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"1,3,6,9,12 →=BXF.RollingAvgλ(SEQUENCE(, 5), 3)", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Values), | |
ISOMITTED( Size)), | |
// Procedure | |
Counter, SEQUENCE(,COUNTA( Values)), | |
Result, SCAN( | |
0, | |
Counter, | |
LAMBDA( | |
Acc, | |
Val, | |
LET( | |
Start, IF((Val - Size) < 1, 0, Val - Size), | |
Right, TAKE(Values, , Val), | |
Block, IF( Start > 0, DROP( Right, , Start ), Right), | |
AVERAGE(Block) | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: RollingMaxλ | |
DESCRIPTION:*//**Creates the maximum for preceeding values of a set size moving from beginning to end over a row of values.*/ | |
/* REVISIONS: Date Developer Description | |
May 18 2024 Craig Hatmaker Copyright w/Finomatic | |
*/ | |
RollingMaxλ = LAMBDA( | |
// Parameter Declarations | |
[Values], | |
[Size], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →RollingMaxλ( Values, Size)¶" & | |
"DESCRIPTION: →Creates the maximum for preceeding values of a set size moving from beginning to end over a row of values.¶" & | |
"→from beginning to end over a row of values¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →May 18 2024¶" & | |
"PARAMETERS: →¶" & | |
"Values →(Required) A row of values to be maxed.¶" & | |
"Size →(Required) The number of values to find maximum that ¶" & | |
"→include and preceed the current value¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"5,5,5,6,6,7 →=BXF.RollingMaxλ({5,3,4,6,3,7}, 3)", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Values), | |
ISOMITTED( Size)), | |
// Procedure | |
Counter, SEQUENCE(,COUNTA( Values)), | |
Result, SCAN( | |
0, | |
Counter, | |
LAMBDA( | |
Acc, | |
Val, | |
LET( | |
Start, IF((Val - Size) < 1, 0, Val - Size), | |
Right, TAKE(Values, , Val), | |
Block, IF( Start > 0, DROP( Right, , Start ), Right), | |
MAX(Block) | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: RollingMinλ | |
DESCRIPTION:*//**Creates the Minimum for preceeding values of a set size moving from beginning to end over a row of values.*/ | |
/* REVISIONS: Date Developer Description | |
May 18 2024 Craig Hatmaker Copyrightw/Finomatic | |
*/ | |
RollingMinλ = LAMBDA( | |
// Parameter Declarations | |
[Values], | |
[Size], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: RollingMinλ( Values, Size)¶" & | |
"DESCRIPTION: →Creates the minimum for preceeding values of a set size moving from beginning to end over a row of values.¶" & | |
"→from beginning to end over a row of values¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →May 18 2024¶" & | |
"PARAMETERS: →¶" & | |
"Values →(Required) A row of values to be minned.¶" & | |
"Size →(Required) The number of values to find maximum that ¶" & | |
"→include and preceed the current value¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"5,3,3,3,2,2 →=BXF.RollingMaxλ({5,3,4,6,2,7}, 3)", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Values), | |
ISOMITTED( Size)), | |
// Procedure | |
Counter, SEQUENCE(,COUNTA( Values)), | |
Result, SCAN( | |
0, | |
Counter, | |
LAMBDA( | |
Acc, | |
Val, | |
LET( | |
Start, IF((Val - Size) < 1, 0, Val - Size), | |
Right, TAKE(Values, , Val), | |
Block, IF( Start > 0, DROP( Right, , Start ), Right), | |
MIN(Block) | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: RollingSumλ | |
DESCRIPTION:*//**Creates totals for preceeding values of a set size moving from beginning to end over a row of values.*/ | |
/* REVISIONS: Date Developer Description | |
Jan 30 2024 Craig Hatmaker Copyright | |
May 02 2024 Craig Hatmaker Changed COUNTA(Values) to COLUMNS( Values) | |
*/ | |
RollingSumλ = LAMBDA( | |
// Parameter Declarations | |
[Values], | |
[Size], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →RollingSumλ( Values, Size)¶" & | |
"DESCRIPTION: →Creates totals for preceeding values of a set size moving¶" & | |
"→from beginning to end over a row of values¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Jan 30 2024¶" & | |
"PARAMETERS: →¶" & | |
"Values →(Required) A row of values to be totalled.¶" & | |
"Size →(Required) The number of values to total that ¶" & | |
"→include and preceed the current value¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"1,3,6,9,12 →=BXF.RollingMinλ(SEQUENCE(, 5), 3)", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Values), | |
ISOMITTED( Size)), | |
// Procedure | |
Counter, SEQUENCE(,COLUMNS( Values)), | |
Result, SCAN( | |
0, | |
Counter, | |
LAMBDA( | |
Acc, | |
Val, | |
LET( | |
Start, IF((Val - Size) < 1, 0, Val - Size), | |
Right, TAKE(Values, , Val), | |
Block, IF( Start > 0, DROP( Right, , Start ), Right), | |
SUM(Block) | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: →SumContainsλ | |
DESCRIPTION:*//**→Creates a row of totals for each row in an array where | |
its labels contain a letter, word, or phrase*/ | |
/* REVISIONS: Date Developer Description | |
Jan 19 2024 Craig Hatmaker Copyright | |
May 28 2024 Craig Hatmaker Added: Labels, CHOOSECOLS(ValueLabels, 1), | |
*/ | |
SumContainsλ = LAMBDA( | |
// Parameter Declarations | |
[ValuesToFilter], | |
[ValueLabels], | |
[FilterByText], | |
[IgnoreCase?], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →SumContainsλ(ValuesToFilter, ValueLabels, FilterByText, [IgnoreCase?])¶" & | |
"DESCRIPTION: →Creates a row of column totals for each row in an array where¶" & | |
"→its labels contain a unique letter, word, or phrase.¶" & | |
"WEBPAGE: →<Coming soon>¶" & | |
"VERSION: →Jan 19 2024¶" & | |
"PARAMETERS: →¶" & | |
"ValuesToFilter →(Required) An array of values to be filtered¶" & | |
"ValueLabels →(Required) An array containing the values' labels¶" & | |
"FilterByText →(Required) A unique letter, word or phrase to find contained in ValueLabels¶" & | |
"IgnoreCase? →(Optional) A flag to determine if Text's case is important. Default is TRUE¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
" →=BXF.SumContainsλ( Array, Labels, ""CAPEX"" )", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( ValuesToFilter), | |
ISOMITTED( ValueLabels), | |
ISOMITTED( FilterByText)), | |
// Check inputs - Set defaults | |
IgnoreCase?, IF( ISLOGICAL( IgnoreCase?), IgnoreCase?, TRUE), | |
Labels, CHOOSECOLS(ValueLabels, 1), | |
// Procedure | |
Matches, IF( IgnoreCase?, | |
SEARCH( FilterByText, Labels), | |
FIND( FilterByText, Labels) | |
), | |
FilteredRows, FILTER( ValuesToFilter, ISNUMBER( Matches)), | |
Result, BYCOL(FilteredRows, LAMBDA( Col, SUM(Col))), | |
// Return Result or help | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: SumPeriodsλ | |
DESCRIPTION:*//**Groups and totals all columns in a Values array by period resulting in one column for each period*/ | |
/* REVISIONS: Date Developer Description | |
Mar 04 2022 Craig Hatmaker Original Development | |
Apr 10 2023 Craig Hatmaker Remove SUMIFS to be compatible with arrays. Added Help. | |
*/ | |
SumPeriodsλ = LAMBDA( | |
// Parameter Declarations | |
[Values], | |
[Dates], | |
[PeriodStarts], | |
// Procedure | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →SumPeriodsλ( Values, Dates, PeriodStarts)¶" & | |
"DESCRIPTION: →Summarizes values where each value column is for one day ¶" & | |
"→into one column for each period. ¶" & | |
"VERSION: →Apr 10 2023¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"PARAMETERS:→¶" & | |
"Values →(Required) A two dimensional array/range containing values to be summed where" & | |
"each column is for a specific day¶" & | |
"Dates →(Required) A row of dates providing the date for each Values column¶" & | |
"PeriodStarts →(Required) A row of period start dates¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"03, 07 →=BXF.SumPeriodsλ({1,2,3,4;4,3,2,1;10,20,30,40}, {1,2,3,4}, {1,3})¶" & | |
"07, 03 →¶" & | |
"30, 70 →", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Values), | |
ISOMITTED( Dates), | |
ISOMITTED( PeriodStarts)), | |
// Procedure | |
PrdNum, MATCH(Dates, PeriodStarts, 1), | |
Result, MAKEARRAY( | |
ROWS(Values), | |
COLUMNS(PeriodStarts), | |
LAMBDA( | |
Row, | |
Col, | |
SUM(INDEX(Values, Row, ) * (Col = PrdNum)) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
// Amortization Suite | |
/* FUNCTION NAME: Amortizeλ | |
DESCRIPTION:*//**Creates a corkscrew amortization schedule*/ | |
/* REVISIONS: Date Developer Description | |
Jul 13 2023 Craig Hatmaker Original Development | |
Dec 20 2023 Craig Hatmaker Payments are assumed to be paid monthly | |
Jan 18 2024 Craig Hatmaker Added Debt Issuance | |
May 02 2024 Craig Hatmaker Converted to anonymous recursion | |
May 11 2024 Craig Hatmaker Corrected issue with month to year aggregation | |
*/ | |
Amortizeλ = LAMBDA( | |
// Parameter Declarations | |
[Principals], | |
[APRs], | |
[Terms], | |
[StartDates], | |
[Timeline], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →Amortizeλ( Principals, APRs, Terms, StartDates, [Timeline])¶" & | |
"DESCRIPTION: →Creates an amortization schedule array for one or more loans.¶" & | |
"→It assumes all payments are made monthly.¶" & | |
"→Use LableAmortizeλ() to apply labels to this function's results:¶" & | |
"→Use SumAmortizeλ() to apply totals to this function's results:¶" & | |
"WEBPAGE: →https://sites.google.com/site/beyondexcel/home/5g-modeling/5g-component-libraries/5g-amortize%CE%BB¶" & | |
"VERSION: →May 11 2024¶" & | |
"PARAMETERS:→¶" & | |
"Principals →(Required) Loan/debt amounts.¶" & | |
"APRs →(Required) Annual Percentage Rates for interest.¶" & | |
"Terms →(Required) Number of months for loans/debts.¶" & | |
"StartDates →(Required) Dates debts are issued.¶" & | |
"Timeline →(Optional) An array of dates. If omitted, a row of months starting on start date will be added¶" & | |
"EXAMPLES:→¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.Amortizeλ(10000, 5%, 4 * 12, ""2023-01-01"")", | |
"→", "¶" ) | |
), | |
// Constants | |
MpY, 12, //Months Per Year | |
ADpM, 30.42, //Average Days Per Month | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Principals), | |
ISOMITTED( APRs), | |
ISOMITTED( Terms), | |
ISOMITTED( StartDates) | |
), | |
// Set defaults and apply convenience conversions | |
Timeline, IF( ISOMITTED( Timeline), | |
LET(Counter, ROWS( Principals), | |
MaxDate, MAX(MAP( Counter, | |
LAMBDA( n, | |
EDATE( INDEX( StartDates, n), INDEX( Terms, n) - 1)))), | |
MinDate, MIN( StartDates), | |
MaxTerm, DATEDIF(MinDate, MaxDate, "M") + 1, | |
Result, EDATE( MinDate, Sequence(1, MaxTerm, 0)), | |
Result), | |
Timeline | |
), | |
// Determine timeline's interval (length of periods) and orientation (horizontal or vertical) | |
FirstPeriod, INDEX( Timeline, 1), | |
SecondPeriod, INDEX( TImeline, 2), | |
MpP, @ROUND(( SecondPeriod - FirstPeriod) / ADpM, 0), | |
PpY, MpY / MpP, | |
// Recursive function (fn) definition | |
Recursion, | |
LAMBDA( fn, Principals, APRs, Terms, StartDates, Timeline, Asset, | |
LET( | |
// Create individual Values from array | |
Principal, INDEX( Principals, Asset), | |
APR, INDEX( APRs, Asset), | |
Term, INDEX( Terms, Asset), | |
StartDate, INDEX( StartDates, Asset), | |
// Adjust inputs to match timeline | |
Periods, ROUNDUP(Term / MpP, 0), | |
Rows, 1, | |
Cols, Periods, | |
// Calculate for each month | |
Rate, APR / MpY, | |
MonthlyPayment, PMT( Rate, Term, Principal), | |
MonthlyPrincipal, -FV(Rate, SEQUENCE( 1, Term, 0), MonthlyPayment, Principal), | |
MonthlyInterest, MonthlyPrincipal * Rate, | |
// Convert to Timeline Periods | |
DebtIssueArray, EXPAND(Principal, 1, Periods, 0), | |
PrincipalArray, IF( MpP = 1, | |
MonthlyPrincipal, | |
MAKEARRAY( 1, Periods, | |
LAMBDA(R, C, | |
LET(Idx, (C - 1) * MpP + 1, | |
INDEX( MonthlyPrincipal, Idx) | |
) | |
) | |
) | |
), | |
InterestArray, IF( MpP = 1, | |
MonthlyInterest, | |
LET( | |
MoInt, EXPAND( MonthlyInterest, 1, ROUNDUP( Term/ Mpp, 0) * Mpp, 0), | |
MAKEARRAY( 1, Periods, | |
LAMBDA(R, C, | |
LET(Idx, (C - 1) * MpP + 1, | |
SUM( CHOOSECOLS(MoInt, SEQUENCE(1, MpP, Idx))) | |
) | |
) | |
) | |
) | |
), | |
PaymentArray, LET(MoPmt, EXPAND( SEQUENCE(1, Term, MonthlyPayment, 0), 1, ROUNDUP( Term/ Mpp, 0) * Mpp, 0), | |
MAKEARRAY( 1, Periods, | |
LAMBDA(R, C, | |
LET(Idx, (C - 1) * MpP + 1, | |
SUM( CHOOSECOLS(MoPmt, SEQUENCE(1, MpP, Idx))) | |
) | |
) | |
) | |
), | |
ClosingArray, PrincipalArray + InterestArray + PaymentArray, | |
PortionArray, -PaymentArray - InterestArray, | |
AmortizationSched, VSTACK( | |
DebtIssueArray, | |
PrincipalArray, | |
InterestArray, | |
PaymentArray, | |
ClosingArray, | |
PortionArray), | |
// Position in timeline | |
Offset, TimelineOffsetλ(StartDate, Timeline), | |
NewBlock, TimelinePositionλ(AmortizationSched, Timeline, Offset), | |
Result, IF( Asset = ROWS( Principals), | |
NewBlock, | |
VSTACK( | |
NewBlock, | |
EXPAND({" "}, 1, COUNTA(Timeline), " "), | |
fn( fn, Principals, APRs, Terms, StartDates, Timeline, Asset + 1) | |
) | |
), | |
Result | |
) | |
), | |
Result, Recursion( Recursion, Principals, APRs, Terms, StartDates, Timeline, 1), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: LabelAmortizeλ | |
DESCRIPTION:*//**Create labels for Amortizeλ*/ | |
/* REVISIONS: Date Developer Description | |
Dec 19 2023 Craig Hatmaker Original Development | |
Jan 18 2024 Craig Hatmaker Added Debt Issuance | |
May 29 2024 Craig Hatmaker Added optional parameters | |
*/ | |
LabelAmortizeλ = LAMBDA( | |
// Parameter Declarations | |
[LoanNames], | |
[LoanAmounts], | |
[LoanAPRs], | |
[LoanTerms], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →LabelAmortizeλ([LoanNames])¶" & | |
"DESCRIPTION: →Create row labels for the amortization schedule array produced by Amortizeλ¶" & | |
"WEBPAGE: →https://sites.google.com/site/beyondexcel/home/5g-modeling/5g-component-libraries/5g-amortize%CE%BB¶" & | |
"VERSION: →May 29 2024¶" & | |
"PARAMETERS: →¶" & | |
"LoanNames →(Required) Names or descriptions of each loan in the amortization schedule produced by Amortizeλ¶" & | |
"LoanAmounts →(Optional) Amount borrowed¶" & | |
"LoanAPR →(Optional) Annual Percentage Interest Rate¶" & | |
"LoanTerm →(Optional) Number of months to repay¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.LabelAmortizeλ(tblLoans[Loan Name])", | |
"→", "¶" | |
) | |
), | |
// Check required inputs - Omitted required arguments | |
Help?, ISOMITTED( LoanNames), | |
// Check optional inputs | |
Optionals, NOT( OR( | |
ISOMITTED( LoanAmounts), | |
ISOMITTED( LoanAPRs), | |
ISOMITTED( LoanTerms) | |
)), | |
// Procedure | |
Result, REDUCE( "", SEQUENCE( ROWS( LoanNames) * 7 - 1), | |
LAMBDA(Acc, Row, | |
LET(Idx, QUOTIENT( Row - 1, 7) + 1, | |
Line, MOD( Row - 1, 7) + 1, | |
LoanName, INDEX( LoanNames, Idx), | |
LoanAmount, IF(ISOMITTED( LoanAmounts), 0, INDEX( LoanAmounts, Idx)), | |
LoanAPR, IF(ISOMITTED( LoanAPRs), 0, INDEX( LoanAPRs, Idx)), | |
LoanTerm, IF(ISOMITTED( LoanTerms), 0, INDEX( LoanTerms, Idx)), | |
Description, SWITCH( Line, | |
1, HSTACK(LoanName & " Debt Issuance", LoanAmount, ""), | |
2, HSTACK(LoanName & " Debt Balance", "",""), | |
3, HSTACK(LoanName & " Interest", Text(LoanAPR, "0.00%"), "APR"), | |
4, HSTACK(LoanName & " Debt Services", LoanTerm, "Months"), | |
5, HSTACK(LoanName & " Closing Balance","",""), | |
6, HSTACK(LoanName & " Debt Repayment", "", ""), | |
7, HSTACK("","","") | |
), | |
Values, IF( Optionals, Description, TAKE( Description, , 1)), | |
Result, IF( Row = 1, Values, VSTACK(Acc, Values)), | |
Result | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: SumAmortizeλ | |
DESCRIPTION:*//**Create row totals for Amortizeλ*/ | |
/* REVISIONS: Date Developer Description | |
Dec 19 2023 Craig Hatmaker Original Development | |
Jan 18 2024 Craig Hatmaker Added Debt Issuance | |
*/ | |
SumAmortizeλ = LAMBDA( | |
// Parameter Declarations | |
[AmoritizationSchedule], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →SumAmortizeλ(AmoritizationSchedule)¶" & | |
"DESCRIPTION: →Create row totals for Amortizeλ¶" & | |
"WEBPAGE: →https://sites.google.com/site/beyondexcel/home/5g-modeling/5g-component-libraries/5g-amortize%CE%BB¶" & | |
"VERSION: →Dec 19 2023¶" & | |
"PARAMETERS: →¶" & | |
"AmoritizationSchedule→(Required) An array produced by Amoritizeλ¶" & | |
"EXAMPLES:→¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.SumAmoritizeλ(AmoritizationSchedule)", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, ISOMITTED( AmoritizationSchedule), | |
// Procedure | |
Result, MAKEARRAY(ROWS(AmoritizationSchedule), 1, | |
LAMBDA( R, C, | |
SWITCH( MOD( R, 7), | |
3, SUM(CHOOSEROWS( AmoritizationSchedule, R)), | |
4, SUM(CHOOSEROWS( AmoritizationSchedule, R)), | |
6, SUM(CHOOSEROWS( AmoritizationSchedule, R)), | |
0 | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
// Depreciation Suite | |
/* FUNCTION NAME: Depreciateλ | |
DESCRIPTION:*//**Create a block of CAPEX, Opening Balance, Depreciation Values, and Book Value for each asset*/ | |
/* REVISIONS: Date Developer Description | |
Oct 24 2023 Craig Hatmaker Original Development | |
May 13 2024 Craig Hatmaker Spring Version | |
*/ | |
Depreciateλ = LAMBDA( | |
// Parameter Declarations | |
[InitialValues], | |
[InServiceDates], | |
[LifeInYears], | |
[Timeline], | |
[SalvageValues], | |
[DisposalDates], | |
[DisposalCosts], | |
[Methods], | |
[Factors], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →Depreciateλ(InitialValues, InServiceDates, LifeInYears, Timeline, ¶" & | |
" →[SalvageValues], [DisposalDates], [DisposalCosts], [Methods], [Factor])¶" & | |
"DESCRIPTION: →Creates an array of CAPEX, depreciation, and book values for a model's timeline¶" & | |
"WEBPAGE: →https://sites.google.com/site/beyondexcel/home/5g-modeling/5g-component-libraries/5g-depreciate%CE%BB ¶" & | |
"VERSION: →Apr 10 2024¶" & | |
"NOTES: →The methods used here differs from Excel's functions in several ways. Please see webpage for details.¶" & | |
"PARAMETERS: →¶" & | |
"InitialValues →(Required) The purchase values of each asset to depreciate¶" & | |
"InServiceDates →(Required) The dates from which to start depreciating each asset¶" & | |
"LifeInYears →(Required) The number of years with which to depreciate each asset¶" & | |
"Timeline →(Required) The model's timeline or a row of period start dates. ¶" & | |
" →Timeline can be in Months, Quarters, or Years¶" & | |
"SalvageValues →(Optional: Default = 0) The book values of each asset after fully depreciated.¶" & | |
"DisposalDates →(Optional: Default = End of life) The dates on which the assets are removed from the books.¶" & | |
"DisposalCosts →(Optional: Default = 0) The book values of each asset after fully depreciated.¶" & | |
"Methods →(Optional: Default = SLN) The desired depreciation method. Must be one of these Excel function names: ¶" & | |
"→SLN=Straight Line Depreciation¶" & | |
"→SYD=Sum of Year's Digits¶" & | |
"→DB =Declining Balance¶" & | |
"→DDB=Double Declining Balance¶" & | |
"→VDB=Variable Declining Balance¶" & | |
"→MACRS=Modified Accelerated Cost Recovery System. NOTE: Salvage value ignored¶" & | |
"Factors →(Optional: Default = 2) Only applicable to DDB and VDB methods.", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( InitialValues), | |
ISOMITTED( LifeInYears), | |
ISOMITTED( InServiceDates), | |
ISOMITTED( Timeline) | |
), | |
// Set Constants | |
MethodArray, {"SLN","SYD","DB","DDB","VDB", "MACRS"}, | |
Mpy, 12, //Months Per Year | |
// Set defaults and apply convenience conversions | |
SalvageValues, IF( ISOMITTED( SalvageValues), | |
MAP( InitialValues, LAMBDA( Ele, 0)), | |
MAP( SalvageValues, LAMBDA(v, IFERROR( VALUE( v), 0)))), | |
CvtSrvDates, IF( NOT( ISOMITTED( InServiceDates)), | |
IF( ISNUMBER( InServiceDates), | |
InServiceDates, | |
DATEVALUE( InServiceDates))), | |
DisposalDates, IF( ISOMITTED( DisposalDates), | |
EDATE( +CvtSrvDates, LifeinYears * 12), | |
MAKEARRAY( ROWS( DisposalDates), 1, | |
LAMBDA(R, C, | |
IF( INDEX( DisposalDates, R) = "", | |
EDATE( INDEX( CvtSrvDates, R), INDEX(LifeinYears, R) * 12), | |
INDEX( DisposalDates, R) | |
) | |
) | |
) | |
), | |
DisposalCosts, IF( ISOMITTED( DisposalCosts), | |
MAP( InitialValues, LAMBDA( Ele, 0)), | |
DisposalCosts), | |
Methods, IF( ISOMITTED( Methods), | |
MAP( InitialValues, LAMBDA( Ele, "SLN")), | |
MAP( Methods, LAMBDA(v, IF( TRIM( v) = "", "SLN", v)))), | |
Factors, IF( ISOMITTED( Factors), | |
MAP( InitialValues, LAMBDA( Ele, 2)), | |
MAP( Factors, LAMBDA(v, IF( TRIM( v) = "", 2, v)))), | |
// Create individual Values from timeline array | |
TimelineCols, COLUMNS( TImeline), | |
FirstPeriod, INDEX( Timeline, 1), | |
SecondPeriod, INDEX( TImeline, 2), | |
MpP, @ROUND(( SecondPeriod - FirstPeriod) / 30.5, 0), //Months Per Period | |
Interval, SWITCH( MpP, 1, "M", 3, "Q", 12, "Y"), | |
PpY, SWITCH( MpP, 1, 12, 3, 4, 12, 1), //Periods Per Year | |
EndDates, HSTACK( MAP( SEQUENCE(, TimelineCols - 1), LAMBDA( n, INDEX( Timeline, n + 1) - 1)), EDATE( MAX( Timeline), MpP) -1 ), | |
// Process each asset | |
Result, REDUCE( 0, SEQUENCE( ROWS( InitialValues)), | |
LAMBDA(Block, Asset, | |
LET( | |
// Create individual values from arguments | |
Method, @INDEX( Methods, Asset), | |
Years, @INDEX( LifeInYears, Asset) + N(Method = "MACRS"), | |
InitialValue, @INDEX( InitialValues, Asset), | |
SalvageValue, IF( Method = "MACRS", 0, @INDEX( SalvageValues, Asset)), | |
InServiceDate, @INDEX( CvtSrvDates, Asset), | |
DisposalDate, IF( Method = "MACRS", | |
MAX(EDATE( InserviceDate, Years * MpY), @INDEX( DisposalDates, Asset)), | |
@INDEX( DisposalDates, Asset)), | |
DisposalCost, @INDEX( DisposalCosts, Asset), | |
Factor, @INDEX( Factors, Asset), | |
LifeInPeriods, Years * PpY, | |
LifeInMonths, PeriodDiffλ( InserviceDate, DisposalDate, "M"), | |
LastPeriod, PeriodDiffλ( InserviceDate, DisposalDate, Interval), | |
// Procedure | |
// Depreciation is calculated annually then allocated equally over the next 12 months | |
Depreciate, CHOOSE( MATCH( @Method, MethodArray, 0), //CHOOSE is more efficient than SWITCH | |
// 1. Straight line | |
SLNλ( InitialValue, SalvageValue, Years), | |
// 2. Sum-of-years' digits | |
SYDλ( InitialValue, SalvageValue, Years), | |
// 3. Declining balance | |
DBλ( InitialValue, SalvageValue, Years), | |
// 4. Double declining balance | |
DDBλ( InitialValue, SalvageValue, Years, Factor), | |
// 5. Variable declining balance | |
VDBλ( InitialValue, SalvageValue, Years, Factor), | |
// 6. Modified accelerated cost recovery system | |
MACRSλ( InitialValue, Years - 1), | |
), | |
// Place Disposal and Salvage in the asset's last month | |
Disposal, HSTACK( SEQUENCE( 1, LifeInMonths - 1, 0, 0) , DisposalCost), | |
Salvage, HSTACK( SEQUENCE( 1, LifeInMonths - 1, 0, 0) , SalvageValue), | |
// Allocating annual values to months | |
Allocate, HSTACK( EXPAND( Allocateλ( Depreciate, "Y", "M"), 1, LifeInMonths - 1, 0), SalvageValue), | |
Opening, DROP( HSTACK( InitialValue, SCAN( InitialValue, Allocate, LAMBDA( Acc, Val, Acc - Val))), 0, -1), | |
// Create a monthly timeline for the asset | |
AssetTimeline, EDATE( InServiceDate, SEQUENCE( , LifeInMonths, 0)), | |
// Aggregate monthly allocations by model's period intervals and position in timelne | |
Depreciation, MAP( SEQUENCE( , TimelineCols), | |
LAMBDA( n, | |
SUM((Allocate) * ( AssetTimeline <= INDEX(EndDates, n)) * ( AssetTimeline >= INDEX( Timeline, n))))), | |
CAPEX, InitialValue * (Timeline <= InServiceDate) * (EndDates >= InServiceDate), | |
OpeningAmount, MAP( SEQUENCE( , TimelineCols), | |
LAMBDA( n, | |
IFERROR(INDEX( Opening, MATCH(1,(AssetTimeline >= INDEX( Timeline, n))*(AssetTimeline<=INDEX( EndDates, n)),0)),0))), | |
BookValue, OpeningAmount - Depreciation, | |
SalesRevenue, SalvageValue * (Timeline <= DisposalDate) * (EndDates >= DisposalDate), | |
DisposalExp, DisposalCost * (Timeline <= DisposalDate) * (EndDates >= DisposalDate), | |
// Assemble this assets rows into a block | |
NewBlock, VSTACK( CAPEX, OpeningAmount, Depreciation, BookValue, SalesRevenue, DisposalExp), | |
// Add this block to prior blocks (if any) | |
Result, IF( Asset = 1, | |
NewBlock, | |
VSTACK( | |
Block, | |
EXPAND("", 1, TimelineCols, ""), | |
NewBlock | |
) | |
), | |
Result | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: LabelDepreciateλ | |
DESCRIPTION:*//**Create labels for Depreciateλ*/ | |
/* REVISIONS: Date Developer Description | |
Oct 24 2023 Craig Hatmaker Original Development | |
*/ | |
LabelDepreciateλ = LAMBDA( | |
// Parameter Declarations | |
[AssetNames], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →LabelDepreciateλ( AssetNames)¶" & | |
"DESCRIPTION: →Create row labels for the depreciation schedule array produced by Depreciateλ¶" & | |
"WEBPAGE: →https://sites.google.com/site/beyondexcel/home/5g-modeling/5g-component-libraries/5g-depreciate%CE%BB¶" & | |
"VERSION: →Oct 24 2023¶" & | |
"PARAMETERS: →¶" & | |
"AssetNames →(Required) The names or descriptions of each asset in teh depreciation schedule produced by Depreciateλ¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.LabelDepreciateλ(tblAssets[Asset Name])", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, ISOMITTED( AssetNames), | |
// Procedure | |
Result, MAKEARRAY( ROWS(AssetNames) * 7 - 1, 1, | |
LAMBDA(R, C, | |
LET(AssetName, INDEX( AssetNames, QUOTIENT(R-1, 7) + 1), | |
SWITCH( MOD(R-1, 7) + 1, | |
1, AssetName & " CAPEX", | |
2, AssetName & " Opening Value", | |
3, AssetName & " Depreciation", | |
4, AssetName & " Book Value", | |
5, AssetName & " Revenue from Sale", | |
6, AssetName & " Disposal Costs", | |
7, "" | |
) | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: SumDepreciateλ | |
DESCRIPTION:*//**Create row totals for Depreciateλ*/ | |
/* REVISIONS: Date Developer Description | |
Dec 21 2023 Craig Hatmaker Original Development | |
*/ | |
SumDepreciateλ = LAMBDA( | |
// Parameter Declarations | |
[DepreciationSchedule], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →SumDepreciateλ(DepreciationSchedule)¶" & | |
"DESCRIPTION: →Create row totals for Depreciateλ¶" & | |
"WEBPAGE: →https://sites.google.com/site/beyondexcel/home/5g-modeling/5g-component-libraries/5g-depreciate%CE%BB¶" & | |
"VERSION: →Dec 21 2023¶" & | |
"PARAMETERS: →¶" & | |
"DepreciationSchedule→(Required) An array produced by Depreciateλ¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.SumDepreciateλ(DepreciationSchedule)", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, ISOMITTED( DepreciationSchedule), | |
// Procedure | |
Result, MAKEARRAY(ROWS(DepreciationSchedule), 1, | |
LAMBDA( R, C, | |
SWITCH( MOD( R, 7), | |
1, SUM(CHOOSEROWS( DepreciationSchedule, R)), | |
3, SUM(CHOOSEROWS( DepreciationSchedule, R)), | |
5, SUM(CHOOSEROWS( DepreciationSchedule, R)), | |
6, SUM(CHOOSEROWS( DepreciationSchedule, R)), | |
0 | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: Allocateλ | |
DESCRIPTION:*//**→Divide one or more amounts equally equally across smaller periods*/ | |
/* REVISIONS: Date Developer Description | |
Apr 17 2024 Craig Hatmaker Copyright | |
*/ | |
Allocateλ = LAMBDA( | |
// Parameter Declaration | |
[Amounts], | |
[FromInterval], | |
[ToInterval], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →Allocateλ(Amounts, [FromInterval], [ToInterval])¶" & | |
"DESCRIPTION: →Divide one or more amounts equally equally across smaller periods¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 17 2024¶" & | |
"PARAMETERS: →¶" & | |
"Amounts →(Required) One amount or a row of amounts to allocate.¶" & | |
"FromInterval →(Optional Default=Y) The larger interval in Q=Quarters or Y=Years¶" & | |
"ToInterval →(Optional Default=M) The smaller interval in W=Weeks, M=Months, or Q=Quarters¶" & | |
"NOTE! →¶" & | |
"→When a From Period's amount does not divide evenly to the 1/100 for all To Periods',¶" & | |
"→the last To Period contains the necessary adjustment.¶" & | |
"EXAMPLES: →¶" & | |
"→Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.Allocateλ({999.99, 2000}, ""Y"", ""Q"")¶" & | |
"→Result¶" & | |
"→250,250,250,249.99,500,500,500,500", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, ISOMITTED( Amounts), | |
// Set defaults for optional values | |
FromInterval, IF( ISOMITTED( FromInterval), "Y", FromInterval), | |
ToInterval, IF( ISOMITTED( ToInterval), "M", ToInterval), | |
// Procedure | |
From, SWITCH(FromInterval, "Y", 1, "Q", 4), | |
To, SWITCH(ToInterval, "Q", 4, "M", 12, "W", 52), | |
FromCount, COUNT( Amounts), | |
ToCount, To / From, | |
Result, REDUCE( 0, SEQUENCE( , FromCount), | |
LAMBDA( Acc, n, | |
LET( | |
FromAmount, INDEX( Amounts, n), | |
ToAmount, ROUND( FromAmount * From / To, 2), | |
BaseArray, EXPAND( ToAmount, 1, ToCount, ToAmount), | |
ToArray, IF( ToCount = 1, | |
BaseArray, | |
HSTACK( | |
TAKE(BaseArray, 1, ToCount - 1), | |
FromAmount - SUM( TAKE(BaseArray, 1, ToCount - 1)) | |
) | |
), | |
Result, IF( n = 1, ToArray, HSTACK( Acc, ToArray )), | |
Result | |
) | |
) | |
), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: PeriodDiffλ | |
DESCRIPTION:*//**Determine the number of periods between two dates.*/ | |
/* REVISIONS: Date Developer Description | |
Apr 15 2024 Craig Hatmaker Copyright | |
*/ | |
PeriodDiffλ = LAMBDA( | |
// Parameter Declaration | |
[StartDate], | |
[EndDate], | |
[Interval], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: PeriodDiffλ(StartDate, EndDate, Interval)¶" & | |
"DESCRIPTION: →Determine the number of periods between two dates.¶" & | |
"NOTES! →This improves on, and corrects problems w/Excel's deprecated DATEDIF() function.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 15 2024¶" & | |
"PARAMETERS: →¶" & | |
"StartDate →(Required) Start Date.¶" & | |
"EndDate →(Required) End Date¶" & | |
"Interval →(Required) W=Week, M=Month, Q=Quarter, Y=Year.¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"1 →=BXF.PeriodDiffλ(""2024-01-01"", ""2024-04-15"", ""Q"")", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( StartDate), | |
ISOMITTED( EndDate), | |
ISOMITTED( Interval) | |
), | |
// Constants | |
MpP, SWITCH( Interval, "M", 1, "Q", 3, "Y", 12, 0), | |
// Procedure | |
Months, DATEDIF( StartDate, EndDate, "M") + 1, | |
Periods, INT( Months / MpP), | |
PartialPeriod, N( MOD( Months, MpP) > 0), | |
EOMCorrection, N( AND( EndDate = EOMONTH( EndDate, 0), DAY( StartDate) > Day( EndDate))), | |
Result, IF( Interval = "W", | |
INT((EndDate - StartDate + 1) / 7), | |
Periods + OR(PartialPeriod, EOMCorrection)), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: SLNλ | |
DESCRIPTION:*//**Straight Line depreciation method for one asset or asset class.*/ | |
/* REVISIONS: Date Developer Description | |
Apr 17 2024 Craig Hatmaker Copyright | |
*/ | |
SLNλ = LAMBDA( | |
// Parameter Declaration | |
[Cost], | |
[Salvage], | |
[Life], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →SLNλ(Cost, Salvage, Life)¶" & | |
"DESCRIPTION: →Straight Line Depreciation Method for one asset or asset class.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 17 2024¶" & | |
"PARAMETERS: →¶" & | |
"Cost →(Required) Asset's initial cost.¶" & | |
"Salvage →(Required) Asset's value and end of life¶" & | |
"Life →(Required) Asset's useful life in years.¶" & | |
"EXAMPLES: →¶" & | |
"→Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.SLNλ(1000, 100, 5)¶" & | |
"→Result¶" & | |
"→180,180,180,180,180", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Cost), | |
ISOMITTED( Salvage), | |
ISOMITTED( Life) | |
), | |
// Procedure | |
Depn, SLN(Cost, Salvage, Life), | |
Result, EXPAND(Depn, 1, Life, Depn), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: SYDλ | |
DESCRIPTION:*//**Sum-of-years' digits depreciation method for one asset or asset class.*/ | |
/* REVISIONS: Date Developer Description | |
Apr 17 2024 Craig Hatmaker Copyright | |
*/ | |
SYDλ = LAMBDA( | |
// Parameter Declaration | |
[Cost], | |
[Salvage], | |
[Life], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →SYDλ(Cost, Salvage, Life)¶" & | |
"DESCRIPTION: →Sum-of-years' digits depreciation method for one asset or asset class.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 17 2024¶" & | |
"PARAMETERS: →¶" & | |
"Cost →(Required) Asset's initial cost.¶" & | |
"Salvage →(Required) Asset's value and end of life¶" & | |
"Life →(Required) Asset's useful life in years.¶" & | |
"EXAMPLES: →¶" & | |
"→Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.SYDλ(1000, 100, 5)¶" & | |
"→Result¶" & | |
"→300,240,180,120,60", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Cost), | |
ISOMITTED( Salvage), | |
ISOMITTED( Life) | |
), | |
// Procedure | |
Result, SYD(Cost, Salvage, Life, SEQUENCE( , Life)), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: DBλ | |
DESCRIPTION:*//**Fixed declining balance depreciation method for one asset or asset class.*/ | |
/* REVISIONS: Date Developer Description | |
Apr 17 2024 Craig Hatmaker Copyright | |
*/ | |
DBλ = LAMBDA( | |
// Parameter Declaration | |
[Cost], | |
[Salvage], | |
[Life], | |
[Months], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →DBλ(Cost, Salvage, Life, [Month])¶" & | |
"DESCRIPTION: →Fixed declining balance depreciation method for one asset or asset class¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 17 2024¶" & | |
"PARAMETERS: →¶" & | |
"Cost →(Required) Asset's initial cost.¶" & | |
"Salvage →(Required) Asset's value and end of life. Salvage cannot be 0.¶" & | |
"Life →(Required) Asset's useful life in years.¶" & | |
"Months →(Optional) Number of months in the first year to depreciate.¶" & | |
"EXAMPLES: →¶" & | |
"→Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.DBλ(1000, 100, 5)¶" & | |
"→Result¶" & | |
"→369.00,232.84,146.92,92.71,58.53", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Cost), | |
ISOMITTED( Salvage), | |
ISOMITTED( Life) | |
), | |
// Set defaults for optional values | |
Months, IF( ISOMITTED( Months), 12, Months), | |
// Procedure | |
Depn, DB( Cost, Salvage, Life, SEQUENCE( , Life - 1 ), Months), | |
Result, HSTACK( Depn, Cost - Sum(Depn) - Salvage), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: DDBλ | |
DESCRIPTION:*//**Double-declining balance depreciation method for one asset or asset class.*/ | |
/* REVISIONS: Date Developer Description | |
Apr 17 2024 Craig Hatmaker Copyright | |
*/ | |
DDBλ = LAMBDA( | |
// Parameter Declaration | |
[Cost], | |
[Salvage], | |
[Life], | |
[Factor], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →DBλ(Cost, Salvage, Life, [Factor])¶" & | |
"DESCRIPTION: →Double-declining balance depreciation method for one asset or asset class.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 17 2024¶" & | |
"PARAMETERS: →¶" & | |
"Cost →(Required) Asset's initial cost.¶" & | |
"Salvage →(Required) Asset's value and end of life. Salvage cannot be 0.¶" & | |
"Life →(Required) Asset's useful life in years.¶" & | |
"Factor →(Optional Default=2) Rate at which the balance declines.¶" & | |
"EXAMPLES: →¶" & | |
"→Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.DDBλ(1000, 100, 5)¶" & | |
"→Result¶" & | |
"→400.00,240.00,144.00,86.40,29.60", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Cost), | |
ISOMITTED( Salvage), | |
ISOMITTED( Life) | |
), | |
// Set defaults for optional values | |
Factor, IF( ISOMITTED( Factor), 2, Factor), | |
// Procedure | |
Depn, DDB( Cost, Salvage, Life, SEQUENCE( , Life -1), Factor), | |
Result, HSTACK( Depn, Cost - Sum(Depn) - Salvage), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: VDBλ | |
DESCRIPTION:*//**Variable declining balance depreciation method for one asset or asset class.*/ | |
/* REVISIONS: Date Developer Description | |
Apr 17 2024 Craig Hatmaker Copyright | |
*/ | |
VDBλ = LAMBDA( | |
// Parameter Declaration | |
[Cost], | |
[Salvage], | |
[Life], | |
[Factor], | |
[No_Switch], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →VDBλ(Cost, Salvage, Life, [Factor], [No_Switch])¶" & | |
"DESCRIPTION: →Variable declining balance depreciation method for one asset or asset class.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 17 2024¶" & | |
"PARAMETERS: →¶" & | |
"Cost →(Required) Asset's initial cost.¶" & | |
"Salvage →(Required) Asset's value and end of life. Salvage cannot be 0.¶" & | |
"Life →(Required) Asset's useful life in years.¶" & | |
"Factor →(Optional Default=2) Rate at which the balance declines.¶" & | |
"No_Switch →(Optional Default=FALSE) Switch to straight-line depreciation when¶" & | |
"→ depreciation is greater than the declining balance calculation.¶" & | |
"EXAMPLES: →¶" & | |
"→Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.VDBλ(1000, 100, 5, 1.5, TRUE)¶" & | |
"→Result¶" & | |
"→300.00,210.00,147.00,121.50,121.50", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Cost), | |
ISOMITTED( Salvage), | |
ISOMITTED( Life) | |
), | |
// Set defaults for optional values | |
Factor, IF( ISOMITTED( Factor), 2, Factor), | |
No_Switch, IF( ISOMITTED( No_Switch), FALSE, No_Switch), | |
// Procedure | |
Result, VDB( Cost, Salvage, Life, SEQUENCE( , Life, 0), SEQUENCE( , Life), Factor), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: MACRSλ | |
DESCRIPTION:*//**Modified Accelerated Cost Recovery System depreciation method for one asset or asset class.*/ | |
/* REVISIONS: Date Developer Description | |
Apr 17 2024 Craig Hatmaker Copyright | |
*/ | |
MACRSλ = LAMBDA( | |
// Parameter Declaration | |
[Cost], | |
[Life], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →MACRSλ(Cost, Life)¶" & | |
"DESCRIPTION: →Modified Accelerated Cost Recovery System depreciation method for one asset or asset class.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 17 2024¶" & | |
"PARAMETERS: →¶" & | |
"Cost →(Required) Asset's initial cost.¶" & | |
"Life →(Required) Asset's useful life in years.¶" & | |
"EXAMPLES: →¶" & | |
"→Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.MACRSλ(1000, 5)" & | |
"→Result¶" & | |
"→300.00,210.00,147.00,121.50,121.50", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Cost), | |
ISOMITTED( Life) | |
), | |
// Set defaults for optional values | |
Percentage, SWITCH( | |
Life, | |
03, {33.33,44.45,14.81,7.41}, | |
05, {20.00,32.00,19.20,11.52,11.52,5.76}, | |
07, {14.29,24.49,17.49,12.49,8.93,8.92,8.93,4.46}, | |
10, {10.00,18.00,14.40,11.52,9.22,7.37,6.555,6.55,6.56,6.55,3.25}, | |
15, {5.00,9.50,8.55,7.70,6.93,6.23,5.90,5.90,5.91,5.90,5.91,5.90,5.91,5.90,5.91,2.95}, | |
20, {3.75,7.22,6.68,6.18,5.71,5.29,4.89,4.52,4.46,4.46,4.46,4.46,4.46,4.46,4.46,4.46,4.46,2.23} | |
)/100, | |
// Procedure | |
Result, Cost * INDEX(Percentage, SEQUENCE( , Life + 1)), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
// Diagnostic Routines | |
/* FUNCTION NAME: AmortizeλDV | |
DESCRIPTION:*//**Provides extended data validation of arguments for Amortizeλ()*/ | |
/* REVISIONS: Date Developer Description | |
Oct 24 2023 Craig Hatmaker Original Development | |
*/ | |
AmortizeλDV = LAMBDA( | |
// Parameter Declarations | |
[Principal], | |
[APR], | |
[Term], | |
[StartDate], | |
[Timeline], | |
LET( | |
// Check inputs - Errors in argument values | |
ErrorsInArgs, VSTACK( | |
OR( ISERROR( Principal)), | |
OR( ISERROR( APR)), | |
OR( ISERROR( Term)), | |
OR( ISERROR( StartDate)), | |
OR( ISERROR( Timeline)) | |
), | |
// Check inputs - Data validation errors | |
DVArg1, IF( OR( ISOMITTED( Principal), ISERROR( Principal)), | |
FALSE, | |
OR( IFERROR( VALUE(Principal) < 1, TRUE))), | |
DVArg2, IF( OR( ISOMITTED( APR), ISERROR( APR)), | |
FALSE, | |
OR( IFERROR( VALUE(APR) <= 0, TRUE))), | |
DVArg3, IF( OR( ISOMITTED( Term), ISERROR( Term)), | |
FALSE, | |
OR( IFERROR( VALUE(Term) < 1, TRUE))), | |
DVArg4, IF( OR( ISOMITTED( StartDate), ISERROR( StartDate)), | |
FALSE, | |
OR( IFERROR( VALUE(StartDate) < MIN( Timeline), TRUE))), | |
DVArg5, IF( OR( ISOMITTED( Timeline), ISERROR( Timeline)), | |
FALSE, | |
OR( IFERROR( VALUE(Timeline) < 1, TRUE))), | |
DVArg6, IF( OR( ISOMITTED( Timeline), ISERROR( Timeline), DVArg5), | |
FALSE, | |
COUNTA(Timeline) < 2), | |
DVArg7, IF( OR( ISOMITTED( Timeline), ISERROR( Timeline), DVArg5, DVArg6), | |
FALSE, | |
(INDEX(Timeline, 2) - INDEX(Timeline, 1)) < 30 ), | |
DVErrors, VSTACK( DVArg1, DVArg2, DVArg3, DVArg4, DVArg5, DVArg6, DVArg7), | |
// Any input errors detected? | |
AnyErrors?, OR(ErrorsInArgs, DVErrors), | |
// Assemble Error Messages Array (if errors found) | |
Errors2Show, VSTACK( ErrorsInArgs, DVErrors), | |
ErrMsgs, { | |
// Errors in arguments | |
"Amount contains errors in cells. Resolve errors first."; | |
"APR contains errors in cells. Resolve errors first."; | |
"Term contains errors in cells. Resolve errors first."; | |
"StartDate contains errors in cells. Resolve errors first."; | |
"Timeline contains errors in cells. Resolve errors first."; | |
// Data validation errors | |
"Amount must be greater than 0"; | |
"APR must be a percentage greater than 0"; | |
"Term must be the number of timeline periods until the debt is satisfied."; | |
"StartDate must be a valid date."; | |
"Timeline must a row of period start dates with every period spanning a month, quarter or year."; | |
"Timeline must a row of period start dates with more than 1 entry."; | |
"Timeline must a row of period start dates spanning no less than a month."}, | |
Messages, FILTER(ErrMsgs, Errors2Show, ""), | |
// Handle Error | |
Return, IF( OR( Errors2Show), 2, IF( AnyErrors?, 3, 1)), | |
// Return Result | |
CHOOSE(Return, TRUE, Messages, #VALUE!) | |
) | |
); | |
/* FUNCTION NAME: CorkscrewλDV | |
DESCRIPTION:*//**Provides extended data validation of arguments for Depreciateλ.*/ | |
/* REVISIONS: Date Developer Description | |
Oct 24 2023 Craig Hatmaker Original Development | |
*/ | |
CorkscrewλDV = LAMBDA( | |
// Parameter Declarations | |
[Opening], | |
[Flow1], | |
[Flow2], | |
[Flow3], | |
[Flow4], | |
[Diagnostics], | |
LET( | |
// Check inputs - Errors in argument values | |
ErrorsInArgs, VSTACK( | |
OR( ISERROR( Opening)), | |
OR( ISERROR( Flow1)), | |
OR( ISERROR( Flow2)), | |
OR( ISERROR( Flow3)), | |
OR( ISERROR( Flow4)) | |
), | |
// Check inputs - Data validation errors | |
DVArg1, IF( OR( ISOMITTED( Opening), ISERROR( Opening)), | |
FALSE, | |
NOT( ISNUMBER( Opening ))), | |
DVArg2, IF( OR( ISOMITTED( Flow1), ISERROR( Flow1)), | |
FALSE, | |
NOT( AND(ISNUMBER( Flow1 )))), | |
DVArg3, IF( OR( ISOMITTED( Flow2), ISERROR( Flow2)), | |
FALSE, | |
NOT( AND(ISNUMBER( Flow2 )))), | |
DVArg4, IF( OR( ISOMITTED( Flow3), ISERROR( Flow3)), | |
FALSE, | |
NOT( AND(ISNUMBER( Flow3 )))), | |
DVArg5, IF( OR( ISOMITTED( Flow4), ISERROR( Flow4)), | |
FALSE, | |
NOT( AND(ISNUMBER( Flow4 )))), | |
DVErrors, VSTACK( DVArg1, DVArg2, DVArg3, DVArg4, DVArg5), | |
// Any input errors detected? | |
AnyErrors?, OR(ErrorsInArgs, DVErrors), | |
// Assemble Error Messages Array (if errors found) | |
Errors2Show, VSTACK( Diagnostics * ErrorsInArgs, Diagnostics * DVErrors), | |
ErrMsgs, {"Opening contains errors in cells. Resolve errors first."; | |
"Flow1 contains errors in cells. Resolve errors first."; | |
"Flow2 contains errors in cells. Resolve errors first."; | |
"Flow3 contains errors in cells. Resolve errors first."; | |
"Flow4 contains errors in cells. Resolve errors first."; | |
"Set Opening must to contain only numeric values."; | |
"Set Flow1 to contain only numeric values."; | |
"Set Flow2 to contain only numeric values."; | |
"Set Flow3 to contain only numeric values."; | |
"Set Flow4 to contain only numeric values."}, | |
Messages, FILTER(ErrMsgs, Errors2Show, ""), | |
// Handle Error | |
Return, IF( OR( Errors2Show), 2, IF( AnyErrors?, 3, 1)), | |
// Return Result | |
CHOOSE(Return, TRUE, Messages, #VALUE!) | |
) | |
); | |
/* FUNCTION NAME: DepreciateλDV | |
DESCRIPTION:*//**Provides extended data validation of arguments for Depreciateλ*/ | |
/* REVISIONS: Date Developer Description | |
Oct 24 2023 Craig Hatmaker Original Development | |
*/ | |
DepreciateλDV = LAMBDA( | |
// Parameter Declarations | |
[InitialValues], | |
[InServiceDates], | |
[LifeInYears], | |
[Timeline], | |
[SalvageValues], | |
[DisposalDates], | |
[DisposalCosts], | |
[Methods], | |
[Factors], | |
LET( | |
// Check inputs - Errors in argument values | |
ErrorsInArgs, VSTACK( | |
OR( ISERROR( InitialValues)), | |
OR( ISERROR( InServiceDates)), | |
OR( ISERROR( LifeInYears)), | |
OR( ISERROR( Timeline)), | |
OR( ISERROR( SalvageValues)), | |
OR( ISERROR( DisposalDates)), | |
OR( ISERROR( DisposalCosts)), | |
OR( ISERROR( Methods)), | |
OR( ISERROR( Factors)) | |
), | |
// Check inputs - Data validation errors | |
DVArg1, IF( OR( ISERROR(InitialValues)), | |
FALSE, | |
OR( IFERROR( VALUE(InitialValues) < 1, TRUE))), | |
DVArg2, IF( OR( ISERROR( InServiceDates), ISERROR( Timeline)), | |
FALSE, | |
OR( IFERROR( VALUE(InServiceDates) < MIN( Timeline), TRUE))), | |
DVArg3, IF( OR( ISERROR( LifeInYears)), | |
FALSE, | |
OR( IFERROR( VALUE(LifeInYears) < 1, TRUE))), | |
DVArg4, IF( OR( ISERROR( Timeline)), | |
FALSE, | |
COUNTA( Timeline) < 2), | |
DVArg4b, IF( OR( OR(ErrorsInArgs), DVArg4), | |
FALSE, | |
(INDEX(Timeline, 2) - INDEX(Timeline, 1)) < 28 ), | |
DVArg5, IF( OR( ISOMITTED( SalvageValues), OR(ISERROR(SalvageValues))), | |
FALSE, | |
OR( IFERROR( VALUE(SalvageValues) < 0, TRUE))), | |
DVArg6, IF( OR( ISOMITTED( DisposalDates), OR(ErrorsInArgs)), | |
FALSE, | |
OR( DisposalDates < InServiceDates)), | |
//OR( IFERROR( VALUE(DisposalDates) < InServiceDates, TRUE))), | |
DVArg7, IF( OR( ISOMITTED( DisposalCosts), OR(ISERROR(DisposalCosts))), | |
FALSE, | |
OR( IFERROR( VALUE(DisposalCosts) < 0, TRUE))), | |
DVArg8, IF( OR( ISOMITTED( Methods), ISERROR( Methods)), | |
FALSE, | |
OR(ISERROR( SEARCH( Methods, "SLN,SYD,DB,DDB,VDB,MACRS")))), | |
DVArg9, IF( OR( ISOMITTED( Factors), ISERROR( Factors)), | |
FALSE, | |
OR(IFERROR( VALUE( Factors) < 0, TRUE ))), | |
DVErrors, VSTACK( DVArg1, DVArg2, DVArg3, DVArg4, DVArg4b, DVArg5, DVArg6, DVArg7, DVArg8, DVArg9 ), | |
// Any input errors detected? | |
AnyErrors?, OR(ErrorsInArgs, DVErrors), | |
// Assemble Error Messages Array (if errors found) | |
Errors2Show, VSTACK( ErrorsInArgs, DVErrors), | |
ErrMsgs, { | |
// Errors in arguments | |
"InitialValues contains errors in cells. Resolve errors first."; | |
"InServiceDates contains errors in cells. Resolve errors first."; | |
"LifeInYears contains errors in cells. Resolve errors first."; | |
"Timeline contains errors in cells. Resolve errors first."; | |
"SalvageValues contains errors in cells. Resolve errors first."; | |
"DisposalDates contains errors in cells. Resolve errors first."; | |
"DisposalCosts contains errors in cells. Resolve errors first."; | |
"Methods contains errors in cells. Resolve errors first."; | |
"Factors contains errors in cells. Resolve errors first."; | |
// Data validation errors | |
"InitialValues must be an amount greater than 0"; | |
"InServiceDates must be valid dates"; | |
"LifeInYears must be a number greater than 0"; | |
"Timeline must be a row of period start dates with every period spanning one month, quarter or year."; | |
"Each period in the timeline must span one month, quarter or year."; | |
"SalvageValues must be an amount not less than 0 or omitted"; | |
"DisposalDates must be a date greater than InServiceDates or omitted"; | |
"DisposalCosts must be an amount not less than 0 or omitted"; | |
"Methods must be omitted or one of: SLN, SYD, DB, DDB, MACRS, or VDB."; | |
"Factors must be a value greater than 0"}, | |
Messages, FILTER(ErrMsgs, Errors2Show, ""), | |
// Handle Error | |
Return, IF( OR( Errors2Show), 2, IF( AnyErrors?, 3, 1)), | |
// Return Result | |
CHOOSE(Return, TRUE, Messages, #VALUE!) | |
) | |
); | |
// SubRoutines | |
/* FUNCTION NAME: TimelineOffsetλ | |
DESCRIPTION:*//**Determines how many periods one array is offset from the timeline*/ | |
/* DISCUSSION: The possibility exists requiring calculations before a model's timeline. An example is | |
depreciation of assets in an existing company wanting to forecast some activity. | |
In such cases, it can be advantageous to perform the calculations within their own timeline | |
and then place the results properly within the model's timeline. | |
This routine determine where such calculation results fit within the model's timeline and | |
crops those results that do not belong. */ | |
/* REVISIONS: Date Developer Description | |
Dec 12 2023 Craig Hatmaker Original Development | |
Dec 20 2023 Craig Hatmaker Made sure date parameter is numeric, not text. | |
Dec 21 2023 Craig Hatmaker Added @ to Date | |
*/ | |
TimelineOffsetλ = LAMBDA( | |
// Parameter Declarations | |
[Date], | |
[Timeline], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →TimelineOffsetλ(ArrayStart, Timeline)¶" & | |
"DESCRIPTION: →Determines in which period, relative to the timeline's start and interval,¶" & | |
"→a date falls within.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Dec 12 2023¶" & | |
"PARAMETERS: →¶" & | |
"Date →(Required) A starting date¶" & | |
"Timeline →(Required) A model's timline (Row of period start dates)¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.TimelineOffsetλ(""2/15/2022"", EDATE(""1/1/2023"", SEQUENCE( , 12, 0)", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Date), | |
ISOMITTED( Timeline) | |
), | |
// Check inputs - If date is in text, convert to value | |
Date, @IF( ISTEXT( Date), DATEVALUE( Date), Date), | |
/* Premise: Create a short timeline that is in sync with the model's timeline that spans | |
from the timeline start to the date in question plus one period on either side. | |
No we can use MATCH() to find which timeline period */ | |
// Determine timeline's interval (length of periods) | |
Period1, INDEX( Timeline, 1), | |
Period2, INDEX( Timeline, 2), | |
MpP, ROUND( (Period2 - Period1)/30.5, 0), //Months Per Period | |
// Determine if Prior to timeline or after | |
Direction, IF(Date < Period1, -1, 1), | |
// Determine the number of months between timeline's start and the date's period start | |
MonthDiff, @DATEDIF( MIN( Period1, Date), MAX( Period1, Date), "M"), | |
// Convert to number of periods | |
PeriodDiff, QUOTIENT( MonthDiff, MpP), | |
PeriodSeq, SEQUENCE( 1, PeriodDiff + 2, IF( Direction = 1, 0, -( PeriodDiff + 1))), | |
SearchTimeline, EDATE( Period1, PeriodSeq * MpP), | |
Result, MATCH( Date, SearchTimeline, 1) - IF( Direction = 1, 1, PeriodDiff + 2), | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: TimelinePositionλ | |
DESCRIPTION:*//**Crops an array based on a model's timeline*/ | |
/* REVISIONS: Date Developer Description | |
Dec 12 2023 Craig Hatmaker Original Development | |
Dec 21 2023 Craig Hatmaker Added @ to Offset | |
*/ | |
TimelinePositionλ = LAMBDA( | |
[Array], | |
[Timeline], | |
[Offset], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →TimelinePositionλ(Array, Timeline, Offset)¶" & | |
"DESCRIPTION: →Position an array relative to the model's timeline¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Dec 12 2023¶" & | |
"PARAMETERS: →¶" & | |
"Array →(Required) The array to be positioned relative to a model's timeline¶" & | |
"Timeline →(Required) A model's timline¶" & | |
"Offset →(Required) The number of rows or columns to start placing the array within the timeline¶" & | |
"NOTES! →¶" & | |
"→Timelines can be vertical or horizontal. Whichever way the timeline goes, the array must match.¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"→=BXF.TimelinePositionλ(SEQUENCE(3,3), EDATE(""1/1/2023"", SEQUENCE( , 12, 0)), 12)", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( Array), | |
ISOMITTED( Timeline), | |
ISOMITTED( Offset) | |
), | |
// Check inputs - Make sure scalars are not arrays | |
Offset, @Offset, | |
// Precedure | |
Rows, ROWS(Array), | |
Cols, COLUMNS(Array), | |
Result, IF( COLUMNS(TIMELINE) > 1, | |
// Horizontal Timeline | |
MAKEARRAY( Rows, COLUMNS( Timeline), | |
LAMBDA(R, C, | |
IF( ((C - Offset) >= 1) * (( C - Offset) <= Cols) = 1, | |
INDEX( Array, R, C - Offset), | |
0) | |
) | |
), | |
// Vertical Timeline | |
MAKEARRAY( ROWS( Timeline), Cols, | |
LAMBDA(R, C, | |
IF( ((R - Offset) >= 1) * (( R - Offset) <= Rows) = 1, | |
INDEX( Array, R - Offset, C), | |
0) | |
) | |
) | |
), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: FilterContainsλ | |
DESCRIPTION:*//**Filter an array by another array that contains specifice text*/ | |
/* REVISIONS: Date Developer Description | |
Jan 16 2024 Craig Hatmaker Original Development | |
*/ | |
FilterContainsλ = LAMBDA( | |
// Parameter Declarations | |
[ArrayToFilter], | |
[FilterByArray], | |
[FilterByText], | |
[IgnoreCase?], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: FilterContainsλ( ArrayToFilter, FilterByArray, Text, [IgnoreCase?] )¶" & | |
"DESCRIPTION: →Filter an array by another array that contains specifice text.¶" & | |
"WEBPAGE: →<Coming soon>¶" & | |
"VERSION: →Jan 16 2024¶" & | |
"PARAMETERS: →¶" & | |
"ArrayToFilter →(Required) An array to be filtered¶" & | |
"FilterByArray →(Required) An array containing text to use as a filter¶" & | |
"FilterByText →(Required) A text string to find in FilterByArray¶" & | |
"IgnoreCase? →(Optional) A flag to determine if Text's case is important. Default is TRUE¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"""Cat"" →=BXF.FilterContainsλ( {""Joe"", ""Sally"", ""Cat""}, {""H1"", ""H2"", ""A1""}, ""A"" )", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( ArrayToFilter), | |
ISOMITTED( FilterByArray), | |
ISOMITTED( FilterByText) | |
), | |
// Check inputs - Set defaults | |
IgnoreCase?, IF( ISLOGICAL( IgnoreCase?), IgnoreCase?, TRUE), | |
// Procedure | |
Matches, IF( IgnoreCase?, | |
SEARCH( FilterByText, FilterByArray), | |
FIND( FilterByText, FilterByArray) | |
), | |
Result, FILTER( ArrayToFilter, ISNUMBER( Matches)), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: RangeToDAλ | |
DESCRIPTION:*//**Create a dynamic array from a static range*/ | |
/* REVISIONS: Date Developer Description | |
Jan 15 2024 Craig Hatmaker Original Development | |
*/ | |
RangeToDAλ = LAMBDA( | |
// Parameter Declarations | |
[Cell], | |
[Rows], | |
[Columns], | |
// Help | |
LET(Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →RangeToDAλ( Cell, [Rows], [Columns])¶" & | |
"DESCRIPTION: →Create a dynamic array from a static range¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Jan 15 2024¶" & | |
"PARAMETERS: →¶" & | |
"Cell →(Required) The upper left cell of a range to convert¶" & | |
"Rows →(Optional) The number of rows for dynamic array. Defaults to 1.¶" & | |
"Columns →(Optional) The number of columns for dynamic array. Defaults to 1.¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
" →=BXF.RangeToDAλ( A1, , 12)", | |
"→", "¶" ) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, ISOMITTED( Cell), | |
// Set Defaults | |
Rows, IF(ISOMITTED(Rows), 1, IF(Rows = 0, 1, Rows)), | |
Columns, IF(ISOMITTED(Columns), 1, IF(Columns = 0, 1, Columns)), | |
// Procedure | |
Result, OFFSET(Cell, 0, 0, Rows, Columns), | |
// Return Result | |
CHOOSE(Help? + 1, Result, Help) | |
) | |
); | |
/* FUNCTION NAME: PeriodStartλ | |
DESCRIPTION:*//**Determine when the period containing a date of interest starts*/ | |
/* REVISIONS: Date Developer Description | |
Apr 13 2024 Craig Hatmaker Copyright | |
*/ | |
PeriodStartλ = LAMBDA( | |
// Parameter Declaration | |
[AnyPeriodStart], | |
[MonthsPerPeriod], | |
[DateOfInterest], | |
LET( | |
// Help | |
Help, TRIM(TEXTSPLIT( | |
"FUNCTION: →PeriodStartλ(AnyPeriodStart, MonthsPerPeriod, DateOfInterest)¶" & | |
"DESCRIPTION: →Determine when the period containing a date of interest starts.¶" & | |
"WEBPAGE: →<coming soon>¶" & | |
"VERSION: →Apr 13 2024¶" & | |
"PARAMETERS: →¶" & | |
"AnyPeriodStart →(Required) Any start date within a timeline.¶" & | |
"MonthsPerPeriod→(Required) Number of months in each period¶" & | |
"DateOfInterest →(Required) A date for which we want to know the start of the period containing it.¶" & | |
"EXAMPLES: →¶" & | |
"Result →Formula (BXF is assumed to be the module's name)¶" & | |
"2024-04-01 →=BXF.PeriodStartλ(""2024-01-01"", 3, ""2024-04-15"")", | |
"→", "¶" | |
) | |
), | |
// Check inputs - Omitted required arguments | |
Help?, OR( ISOMITTED( AnyPeriodStart), | |
ISOMITTED( MonthsPerPeriod), | |
ISOMITTED( DateOfInterest) | |
), | |
// Constants | |
MpY, 12, //Months Per Year | |
MpP, MonthsPerPeriod, | |
// Procedure | |
DateDay, DAY(DateOfInterest), | |
PeriodDay, Day(AnyPeriodStart), | |
DateMonth, MONTH(DateOfInterest), | |
PeriodMonth, MONTH(AnyPeriodStart), | |
MonthModDifference, MOD(DateMonth - PeriodMonth, MpP), | |
NeedPriorPeriod?, AND( MonthModDifference = 0, DateDay < PeriodDay) * - MpP, | |
NewPeriodMonth, MOD(DateMonth - MonthModDifference + NeedPriorPeriod? -1, MpY ) + 1, | |
YearCorrection, -((DateMonth * 100 + DateDay) < (NewPeriodMonth * 100 + PeriodDay )), | |
PeriodDate, DATE(YEAR(DateOfInterest) + YearCorrection, NewPeriodMonth, PeriodDay), | |
LastDayOfMonthAdj, PeriodDate - (MONTH(PeriodDate) <> NewPeriodMonth), | |
Result, LastDayOfMonthAdj, | |
// Return Result or Help | |
CHOOSE( Help? + 1, Result, Help) | |
) | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment