@trebco/treb 27.1.2 → 27.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/treb.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -23,6 +23,8 @@ import type { FunctionMap } from '../descriptors';
|
|
|
23
23
|
import { type CellValue, type UnionValue, ValueType } from 'treb-base-types';
|
|
24
24
|
import { FlattenUnboxed } from '../utilities';
|
|
25
25
|
|
|
26
|
+
import { ArgumentError, ReferenceError, UnknownError, ValueError, ExpressionError, NAError, DivideByZeroError } from '../function-error';
|
|
27
|
+
|
|
26
28
|
// use a single, static object for base functions
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -114,6 +116,189 @@ export const FinanceFunctionLibrary: FunctionMap = {
|
|
|
114
116
|
}
|
|
115
117
|
},
|
|
116
118
|
|
|
119
|
+
XNPV: {
|
|
120
|
+
description: 'returns the NPV of a nonperiodic stream of payments at a given rate',
|
|
121
|
+
arguments: [
|
|
122
|
+
{ name: 'Discount rate', },
|
|
123
|
+
{ name: 'Values', },
|
|
124
|
+
{ name: 'Dates', },
|
|
125
|
+
],
|
|
126
|
+
fn: (rate: UnionValue, input_values: CellValue[], input_dates: CellValue[]): UnionValue => {
|
|
127
|
+
|
|
128
|
+
if (typeof rate !== 'number') {
|
|
129
|
+
return ArgumentError();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
input_values = FlattenUnboxed(input_values);
|
|
133
|
+
input_dates = FlattenUnboxed(input_dates);
|
|
134
|
+
|
|
135
|
+
// some validation...
|
|
136
|
+
|
|
137
|
+
if (input_values.length !== input_dates.length) {
|
|
138
|
+
return ArgumentError();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const values: number[] = [];
|
|
142
|
+
|
|
143
|
+
for (const value of input_values) {
|
|
144
|
+
if (typeof value !== 'number') {
|
|
145
|
+
return ArgumentError();
|
|
146
|
+
}
|
|
147
|
+
values.push(value);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const dates: number[] = [];
|
|
151
|
+
|
|
152
|
+
//
|
|
153
|
+
// "Numbers in dates are truncated to integers."
|
|
154
|
+
//
|
|
155
|
+
// https://support.microsoft.com/en-gb/office/xirr-function-de1242ec-6477-445b-b11b-a303ad9adc9d
|
|
156
|
+
//
|
|
157
|
+
// what does that mean? rounded? floored? going to assume the latter...
|
|
158
|
+
|
|
159
|
+
for (const date of input_dates) {
|
|
160
|
+
if (typeof date !== 'number') {
|
|
161
|
+
return ArgumentError();
|
|
162
|
+
}
|
|
163
|
+
dates.push(Math.floor(date));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let npv = 0;
|
|
167
|
+
|
|
168
|
+
for (let j = 0; j < values.length; j++) {
|
|
169
|
+
npv += (values[j] || 0) / Math.pow((1 + rate), (dates[j] - dates[0]) / 365);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
type: ValueType.number,
|
|
174
|
+
value: npv,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
XIRR: {
|
|
181
|
+
description: 'returns the internal rate of return of a nonperiodic stream of payments',
|
|
182
|
+
arguments: [
|
|
183
|
+
{ name: 'Values', },
|
|
184
|
+
{ name: 'Dates', },
|
|
185
|
+
{ name: 'Guess', default: .1 },
|
|
186
|
+
],
|
|
187
|
+
fn: (input_values: CellValue[], input_dates: CellValue[], guess = .1): UnionValue => {
|
|
188
|
+
|
|
189
|
+
input_values = FlattenUnboxed(input_values);
|
|
190
|
+
input_dates = FlattenUnboxed(input_dates);
|
|
191
|
+
|
|
192
|
+
// some validation...
|
|
193
|
+
|
|
194
|
+
if (input_values.length !== input_dates.length) {
|
|
195
|
+
return ArgumentError();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let positive = 0;
|
|
199
|
+
let negative = 0;
|
|
200
|
+
|
|
201
|
+
const values: number[] = [];
|
|
202
|
+
|
|
203
|
+
for (const value of input_values) {
|
|
204
|
+
if (typeof value !== 'number') {
|
|
205
|
+
// console.info('value not number', value);
|
|
206
|
+
return ArgumentError();
|
|
207
|
+
}
|
|
208
|
+
if (value > 0) { positive++; }
|
|
209
|
+
if (value < 0) { negative++; }
|
|
210
|
+
|
|
211
|
+
values.push(value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (positive <= 0 || negative <= 0) {
|
|
215
|
+
// console.info('invalid -/+ count', positive, negative);
|
|
216
|
+
return ArgumentError();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const dates: number[] = [];
|
|
220
|
+
|
|
221
|
+
//
|
|
222
|
+
// "Numbers in dates are truncated to integers."
|
|
223
|
+
//
|
|
224
|
+
// https://support.microsoft.com/en-gb/office/xirr-function-de1242ec-6477-445b-b11b-a303ad9adc9d
|
|
225
|
+
//
|
|
226
|
+
// what does that mean? rounded? floored? going to assume the latter...
|
|
227
|
+
|
|
228
|
+
for (const date of input_dates) {
|
|
229
|
+
if (typeof date !== 'number') {
|
|
230
|
+
return ArgumentError();
|
|
231
|
+
}
|
|
232
|
+
dates.push(Math.floor(date));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const start = dates[0];
|
|
236
|
+
for (const date of dates) {
|
|
237
|
+
if (date < start) {
|
|
238
|
+
return ArgumentError();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// per the above reference link we have max steps = 100 and
|
|
243
|
+
// resolution threshold = 1e-8 ("0.000001 percent")
|
|
244
|
+
|
|
245
|
+
const step = .1; // initial step
|
|
246
|
+
|
|
247
|
+
const bounds = [
|
|
248
|
+
{found: false, value: 0},
|
|
249
|
+
{found: false, value: 0},
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
const count = values.length;
|
|
253
|
+
|
|
254
|
+
for (let i = 0; i < 100; i++) {
|
|
255
|
+
|
|
256
|
+
// calculate npv
|
|
257
|
+
let npv = 0;
|
|
258
|
+
|
|
259
|
+
for (let j = 0; j < count; j++) {
|
|
260
|
+
npv += (values[j] || 0) / Math.pow((1 + guess), (dates[j] - dates[0]) / 365);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (Math.abs(npv) <= 1e-6) { // resolution
|
|
264
|
+
// console.info(`** found in ${i + 1} steps`)
|
|
265
|
+
return {
|
|
266
|
+
type: ValueType.number,
|
|
267
|
+
value: guess,
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// search space is unbounded, unfortunately. we can expand exponentially
|
|
272
|
+
// until we have bounds, at which point it's a standard bounded binary search
|
|
273
|
+
|
|
274
|
+
// ...or we can expand linearly, using a reasonable initial step size?
|
|
275
|
+
|
|
276
|
+
if (npv > 0) {
|
|
277
|
+
bounds[0].value = bounds[0].found ? Math.max(bounds[0].value, guess) : guess;
|
|
278
|
+
bounds[0].found = true;
|
|
279
|
+
if (!bounds[1].found) {
|
|
280
|
+
guess += step;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
bounds[1].value = bounds[1].found ? Math.min(bounds[1].value, guess) : guess;
|
|
286
|
+
bounds[1].found = true;
|
|
287
|
+
if (!bounds[0].found) {
|
|
288
|
+
guess -= step;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
guess = bounds[0].value + (bounds[1].value - bounds[0].value) / 2;
|
|
294
|
+
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return ValueError();
|
|
298
|
+
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
|
|
117
302
|
IRR: {
|
|
118
303
|
description: 'Calculates the internal rate of return of a series of cashflows',
|
|
119
304
|
arguments: [
|
|
@@ -796,13 +796,13 @@ export class Exporter {
|
|
|
796
796
|
case 'png':
|
|
797
797
|
case 'gif':
|
|
798
798
|
|
|
799
|
-
if (annotation.
|
|
799
|
+
if (annotation.layout) {
|
|
800
800
|
images.push({
|
|
801
|
-
anchor: this.
|
|
801
|
+
anchor: this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options});
|
|
802
802
|
}
|
|
803
|
-
else if (annotation.
|
|
803
|
+
else if (annotation.rect) {
|
|
804
804
|
images.push({
|
|
805
|
-
anchor: this.
|
|
805
|
+
anchor: this.AnnotationRectToAnchor(annotation.rect, sheet_source), options});
|
|
806
806
|
}
|
|
807
807
|
else {
|
|
808
808
|
console.warn('annotation missing layout');
|
|
@@ -1006,16 +1006,16 @@ export class Exporter {
|
|
|
1006
1006
|
|
|
1007
1007
|
const rect = (annotation as AnnotationData & { rect?: Partial<Rectangle>}).rect;
|
|
1008
1008
|
|
|
1009
|
-
if (
|
|
1010
|
-
charts.push({
|
|
1011
|
-
anchor: this.AnnotationRectToAnchor(rect, sheet_source), options});
|
|
1012
|
-
// sheet.AddChart(this.AnnotationRectToAnchor(annotation.rect, sheet_source), options);
|
|
1013
|
-
}
|
|
1014
|
-
else if (annotation.layout) {
|
|
1009
|
+
if (annotation.layout) {
|
|
1015
1010
|
charts.push({
|
|
1016
1011
|
anchor: this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options});
|
|
1017
1012
|
// sheet.AddChart(this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options);
|
|
1018
1013
|
}
|
|
1014
|
+
else if (rect) {
|
|
1015
|
+
charts.push({
|
|
1016
|
+
anchor: this.AnnotationRectToAnchor(rect, sheet_source), options});
|
|
1017
|
+
// sheet.AddChart(this.AnnotationRectToAnchor(annotation.rect, sheet_source), options);
|
|
1018
|
+
}
|
|
1019
1019
|
else {
|
|
1020
1020
|
console.warn('annotation missing layout');
|
|
1021
1021
|
}
|