@barchart/portfolio-api-common 1.0.262 → 1.0.266
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/lib/data/PositionSummaryFrame.js +21 -5
- package/lib/data/TransactionValidator.js +27 -1
- package/lib/processing/PositionContainer.js +22 -9
- package/lib/processing/PositionGroup.js +61 -37
- package/package.json +1 -1
- package/test/SpecRunner.js +520 -86
- package/test/specs/data/PositionSummaryFrameSpec.js +1 -1
- package/test/specs/data/TransactionValidatorSpec.js +51 -0
|
@@ -20,18 +20,34 @@ module.exports = (() => {
|
|
|
20
20
|
* @param {Function} descriptionCalculator
|
|
21
21
|
*/
|
|
22
22
|
class PositionSummaryFrame extends Enum {
|
|
23
|
-
constructor(code, description, rangeCalculator, startDateCalculator, descriptionCalculator) {
|
|
23
|
+
constructor(code, description, unique, rangeCalculator, startDateCalculator, descriptionCalculator) {
|
|
24
24
|
super(code, description);
|
|
25
25
|
|
|
26
|
+
assert.argumentIsRequired(unique, 'unique', Boolean);
|
|
27
|
+
|
|
26
28
|
assert.argumentIsRequired(rangeCalculator, 'rangeCalculator', Function);
|
|
27
29
|
assert.argumentIsRequired(startDateCalculator, 'startDateCalculator', Function);
|
|
28
30
|
assert.argumentIsRequired(descriptionCalculator, 'descriptionCalculator', Function);
|
|
29
31
|
|
|
32
|
+
this._unique = unique;
|
|
33
|
+
|
|
30
34
|
this._rangeCalculator = rangeCalculator;
|
|
31
35
|
this._startDateCalculator = startDateCalculator;
|
|
32
36
|
this._descriptionCalculator = descriptionCalculator;
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* If true, only one summary, of the given type, can exist for a
|
|
41
|
+
* position. If false, multiple summaries, of the given type, can
|
|
42
|
+
* exist for a position.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
* @return {Boolean}
|
|
46
|
+
*/
|
|
47
|
+
get unique() {
|
|
48
|
+
return this._unique;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
/**
|
|
36
52
|
* Returns a human-readable description of the frame, given
|
|
37
53
|
* start and end dates.
|
|
@@ -129,10 +145,10 @@ module.exports = (() => {
|
|
|
129
145
|
}
|
|
130
146
|
}
|
|
131
147
|
|
|
132
|
-
const yearly = new PositionSummaryFrame('YEARLY', 'year', getYearlyRanges, getYearlyStartDate, getYearlyRangeDescription);
|
|
133
|
-
const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', getQuarterlyRanges, getQuarterlyStartDate, getQuarterlyRangeDescription);
|
|
134
|
-
const monthly = new PositionSummaryFrame('MONTH', 'month', getMonthlyRanges, getMonthlyStartDate, getMonthlyRangeDescription);
|
|
135
|
-
const ytd = new PositionSummaryFrame('YTD', 'year-to-date', getYearToDateRanges, getYearToDateStartDate, getYearToDateRangeDescription);
|
|
148
|
+
const yearly = new PositionSummaryFrame('YEARLY', 'year', false, getYearlyRanges, getYearlyStartDate, getYearlyRangeDescription);
|
|
149
|
+
const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', false, getQuarterlyRanges, getQuarterlyStartDate, getQuarterlyRangeDescription);
|
|
150
|
+
const monthly = new PositionSummaryFrame('MONTH', 'month', false, getMonthlyRanges, getMonthlyStartDate, getMonthlyRangeDescription);
|
|
151
|
+
const ytd = new PositionSummaryFrame('YTD', 'year-to-date', true, getYearToDateRanges, getYearToDateStartDate, getYearToDateRangeDescription);
|
|
136
152
|
|
|
137
153
|
/**
|
|
138
154
|
* The start and and date for a {@link PositionSummaryFrame}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const assert = require('@barchart/common-js/lang/assert')
|
|
1
|
+
const assert = require('@barchart/common-js/lang/assert'),
|
|
2
|
+
array = require('@barchart/common-js/lang/array')
|
|
2
3
|
|
|
3
4
|
const InstrumentType = require('./InstrumentType'),
|
|
4
5
|
PositionDirection = require('./PositionDirection'),
|
|
@@ -17,6 +18,31 @@ module.exports = (() => {
|
|
|
17
18
|
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Given a set of transaction, ensures that sequence numbers and dates
|
|
23
|
+
* are properly ordered.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
* @static
|
|
27
|
+
* @param {Array.<Object>} transactions
|
|
28
|
+
* @param {Boolean=} partial - If true, sequence validation starts with the array's first transaction.
|
|
29
|
+
* @return {boolean}
|
|
30
|
+
*/
|
|
31
|
+
static validateOrder(transactions, partial) {
|
|
32
|
+
assert.argumentIsArray(transactions, 'transactions');
|
|
33
|
+
assert.argumentIsOptional(partial, 'partial', Boolean);
|
|
34
|
+
|
|
35
|
+
let startSequence;
|
|
36
|
+
|
|
37
|
+
if (partial && transactions.length !== 0) {
|
|
38
|
+
startSequence = array.first(transactions).sequence;
|
|
39
|
+
} else {
|
|
40
|
+
startSequence = 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return transactions.every((t, i) => t.sequence === (i + startSequence) && (i === 0 || !t.date.getIsBefore(transactions[i - 1].date)));
|
|
44
|
+
}
|
|
45
|
+
|
|
20
46
|
/**
|
|
21
47
|
* Given an instrument type, returns all valid transaction types.
|
|
22
48
|
*
|
|
@@ -324,6 +324,8 @@ module.exports = (() => {
|
|
|
324
324
|
this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
|
|
325
325
|
}
|
|
326
326
|
});
|
|
327
|
+
|
|
328
|
+
recalculatePercentages.call(this);
|
|
327
329
|
}
|
|
328
330
|
|
|
329
331
|
/**
|
|
@@ -337,6 +339,8 @@ module.exports = (() => {
|
|
|
337
339
|
assert.argumentIsRequired(position.position, 'position.position', String);
|
|
338
340
|
|
|
339
341
|
removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
|
|
342
|
+
|
|
343
|
+
recalculatePercentages.call(this);
|
|
340
344
|
}
|
|
341
345
|
|
|
342
346
|
/**
|
|
@@ -384,6 +388,8 @@ module.exports = (() => {
|
|
|
384
388
|
if (this._symbols.hasOwnProperty(symbol)) {
|
|
385
389
|
this._symbols[symbol].forEach(item => item.setQuote(quote));
|
|
386
390
|
}
|
|
391
|
+
|
|
392
|
+
recalculatePercentages.call(this);
|
|
387
393
|
}
|
|
388
394
|
|
|
389
395
|
/**
|
|
@@ -427,7 +433,9 @@ module.exports = (() => {
|
|
|
427
433
|
this._forexQuotes[index] = rate;
|
|
428
434
|
}
|
|
429
435
|
|
|
430
|
-
Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.
|
|
436
|
+
Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false));
|
|
437
|
+
|
|
438
|
+
recalculatePercentages.call(this);
|
|
431
439
|
}
|
|
432
440
|
|
|
433
441
|
/**
|
|
@@ -739,12 +747,6 @@ module.exports = (() => {
|
|
|
739
747
|
}
|
|
740
748
|
}
|
|
741
749
|
}));
|
|
742
|
-
|
|
743
|
-
addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
|
|
744
|
-
if (!groupTree.getIsRoot()) {
|
|
745
|
-
groupTree.getParent().walk((childGroup) => childGroup.refreshMarketPercent());
|
|
746
|
-
}
|
|
747
|
-
}));
|
|
748
750
|
}
|
|
749
751
|
|
|
750
752
|
function createGroups(parentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
|
|
@@ -752,6 +754,8 @@ module.exports = (() => {
|
|
|
752
754
|
return;
|
|
753
755
|
}
|
|
754
756
|
|
|
757
|
+
const rates = this._forexQuotes;
|
|
758
|
+
|
|
755
759
|
const levelDefinition = levelDefinitions[0];
|
|
756
760
|
|
|
757
761
|
const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
|
|
@@ -759,7 +763,7 @@ module.exports = (() => {
|
|
|
759
763
|
const items = populatedObjects[key];
|
|
760
764
|
const first = items[0];
|
|
761
765
|
|
|
762
|
-
list.push(new PositionGroup(
|
|
766
|
+
list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
|
|
763
767
|
|
|
764
768
|
return list;
|
|
765
769
|
}, [ ]);
|
|
@@ -772,7 +776,7 @@ module.exports = (() => {
|
|
|
772
776
|
});
|
|
773
777
|
|
|
774
778
|
const empty = missingGroups.map((group) => {
|
|
775
|
-
return new PositionGroup(
|
|
779
|
+
return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
|
|
776
780
|
});
|
|
777
781
|
|
|
778
782
|
const compositeGroups = populatedGroups.concat(empty);
|
|
@@ -812,6 +816,9 @@ module.exports = (() => {
|
|
|
812
816
|
|
|
813
817
|
this._nodes[group.id] = childTree;
|
|
814
818
|
|
|
819
|
+
group.setParentGroup(this.getParentGroup(group));
|
|
820
|
+
group.setPortfolioGroup(this.getParentGroupForPortfolio(group));
|
|
821
|
+
|
|
815
822
|
initializeGroupObservers.call(this, childTree, treeDefinition);
|
|
816
823
|
|
|
817
824
|
createGroups.call(this, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
|
|
@@ -952,5 +959,11 @@ module.exports = (() => {
|
|
|
952
959
|
groupNodeToSever.walk(group => delete this._nodes[group.id], false, true);
|
|
953
960
|
}
|
|
954
961
|
|
|
962
|
+
function recalculatePercentages() {
|
|
963
|
+
Object.keys(this._trees).forEach((key) => {
|
|
964
|
+
this._trees[key].walk(group => group.refreshMarketPercent(), false, false);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
955
968
|
return PositionContainer;
|
|
956
969
|
})();
|
|
@@ -33,13 +33,17 @@ module.exports = (() => {
|
|
|
33
33
|
* @param {Boolean=} aggregateCash
|
|
34
34
|
*/
|
|
35
35
|
class PositionGroup {
|
|
36
|
-
constructor(
|
|
36
|
+
constructor(definition, items, rates, currency, key, description, aggregateCash) {
|
|
37
37
|
this._id = counter++;
|
|
38
38
|
|
|
39
39
|
this._definition = definition;
|
|
40
|
-
this._container = container;
|
|
41
40
|
|
|
42
41
|
this._items = items;
|
|
42
|
+
this._rates = rates;
|
|
43
|
+
|
|
44
|
+
this._parentGroup = null;
|
|
45
|
+
this._portfolioGroup = null;
|
|
46
|
+
|
|
43
47
|
this._currency = currency || Currency.CAD;
|
|
44
48
|
this._bypassCurrencyTranslation = false;
|
|
45
49
|
|
|
@@ -53,7 +57,6 @@ module.exports = (() => {
|
|
|
53
57
|
this._suspended = false;
|
|
54
58
|
this._showClosedPositions = false;
|
|
55
59
|
|
|
56
|
-
this._marketPercentChangeEvent = new Event(this);
|
|
57
60
|
this._groupExcludedChangeEvent = new Event(this);
|
|
58
61
|
this._showClosedPositionsChangeEvent = new Event(this);
|
|
59
62
|
|
|
@@ -266,6 +269,40 @@ module.exports = (() => {
|
|
|
266
269
|
return this._excluded;
|
|
267
270
|
}
|
|
268
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Sets the immediate parent group (allowing for calculation of relative
|
|
274
|
+
* percentages).
|
|
275
|
+
*
|
|
276
|
+
* @public
|
|
277
|
+
* @param {PortfolioGroup} group
|
|
278
|
+
*/
|
|
279
|
+
setParentGroup(group) {
|
|
280
|
+
assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
|
|
281
|
+
|
|
282
|
+
if (this._parentGroup !== null) {
|
|
283
|
+
throw new Error('The parent group has already been set.');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this._parentGroup = group;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Sets the nearest parent group for a portfolio (allowing for calculation
|
|
291
|
+
* of relative percentages).
|
|
292
|
+
*
|
|
293
|
+
* @public
|
|
294
|
+
* @param {PortfolioGroup} group
|
|
295
|
+
*/
|
|
296
|
+
setPortfolioGroup(group) {
|
|
297
|
+
assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
|
|
298
|
+
|
|
299
|
+
if (this._portfolioGroup !== null) {
|
|
300
|
+
throw new Error('The portfolio group has already been set.');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this._portfolioGroup = group;
|
|
304
|
+
}
|
|
305
|
+
|
|
269
306
|
/**
|
|
270
307
|
* Adds a new {@link PositionItem} to the group.
|
|
271
308
|
*
|
|
@@ -311,9 +348,11 @@ module.exports = (() => {
|
|
|
311
348
|
* Causes aggregated data to be recalculated using a new exchange rate.
|
|
312
349
|
*
|
|
313
350
|
* @public
|
|
314
|
-
* @param {Rate} rate
|
|
351
|
+
* @param {Array.<Rate>} rate
|
|
315
352
|
*/
|
|
316
|
-
|
|
353
|
+
setForexRates(rates) {
|
|
354
|
+
this._rates = rates;
|
|
355
|
+
|
|
317
356
|
if (!this._bypassCurrencyTranslation) {
|
|
318
357
|
this.refresh();
|
|
319
358
|
}
|
|
@@ -401,10 +440,8 @@ module.exports = (() => {
|
|
|
401
440
|
return;
|
|
402
441
|
}
|
|
403
442
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
calculateStaticData(this, rates);
|
|
407
|
-
calculatePriceData(this, rates, null, true);
|
|
443
|
+
calculateStaticData(this, this._rates);
|
|
444
|
+
calculatePriceData(this, this._rates, null, true);
|
|
408
445
|
}
|
|
409
446
|
|
|
410
447
|
/**
|
|
@@ -414,7 +451,7 @@ module.exports = (() => {
|
|
|
414
451
|
* @public
|
|
415
452
|
*/
|
|
416
453
|
refreshMarketPercent() {
|
|
417
|
-
calculateMarketPercent(this, this.
|
|
454
|
+
calculateMarketPercent(this, this._rates, this._parentGroup, this._portfolioGroup);
|
|
418
455
|
}
|
|
419
456
|
|
|
420
457
|
/**
|
|
@@ -427,17 +464,6 @@ module.exports = (() => {
|
|
|
427
464
|
return this._items.length === 0;
|
|
428
465
|
}
|
|
429
466
|
|
|
430
|
-
/**
|
|
431
|
-
* Adds an observer for change in the market percentage of the group.
|
|
432
|
-
*
|
|
433
|
-
* @public
|
|
434
|
-
* @param {Function} handler
|
|
435
|
-
* @return {Disposable}
|
|
436
|
-
*/
|
|
437
|
-
registerMarketPercentChangeHandler(handler) {
|
|
438
|
-
return this._marketPercentChangeEvent.register(handler);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
467
|
/**
|
|
442
468
|
* Adds an observer for changes to the exclusion of the group
|
|
443
469
|
* from higher level aggregations.
|
|
@@ -492,10 +518,10 @@ module.exports = (() => {
|
|
|
492
518
|
this._dataFormat.currentPrice = null;
|
|
493
519
|
}
|
|
494
520
|
|
|
495
|
-
calculatePriceData(this, this.
|
|
521
|
+
calculatePriceData(this, this._rates, sender, false);
|
|
496
522
|
});
|
|
497
523
|
|
|
498
|
-
let fundamentalBinding = item.registerFundamentalDataChangeHandler((data
|
|
524
|
+
let fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
|
|
499
525
|
if (this._single) {
|
|
500
526
|
this._dataFormat.fundamental = data;
|
|
501
527
|
} else {
|
|
@@ -546,13 +572,13 @@ module.exports = (() => {
|
|
|
546
572
|
let newsBinding = Disposable.getEmpty();
|
|
547
573
|
|
|
548
574
|
if (this._single) {
|
|
549
|
-
newsBinding = item.registerNewsExistsChangeHandler((exists
|
|
575
|
+
newsBinding = item.registerNewsExistsChangeHandler((exists) => {
|
|
550
576
|
this._dataActual.newsExists = exists;
|
|
551
577
|
this._dataFormat.newsExists = exists;
|
|
552
578
|
});
|
|
553
579
|
}
|
|
554
580
|
|
|
555
|
-
this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio
|
|
581
|
+
this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio) => {
|
|
556
582
|
const descriptionSelector = this._definition.descriptionSelector;
|
|
557
583
|
|
|
558
584
|
this._description = descriptionSelector(this._items[0]);
|
|
@@ -801,12 +827,11 @@ module.exports = (() => {
|
|
|
801
827
|
|
|
802
828
|
format.total = formatCurrency(actual.total, currency);
|
|
803
829
|
format.totalNegative = actual.total.getIsNegative();
|
|
804
|
-
|
|
805
|
-
calculateMarketPercent(group, rates, false);
|
|
830
|
+
|
|
806
831
|
calculateUnrealizedPercent(group);
|
|
807
832
|
}
|
|
808
833
|
|
|
809
|
-
function calculateMarketPercent(group, rates,
|
|
834
|
+
function calculateMarketPercent(group, rates, parentGroup, portfolioGroup) {
|
|
810
835
|
if (group.suspended) {
|
|
811
836
|
return;
|
|
812
837
|
}
|
|
@@ -815,12 +840,10 @@ module.exports = (() => {
|
|
|
815
840
|
const format = group._dataFormat;
|
|
816
841
|
const excluded = group._excluded;
|
|
817
842
|
|
|
818
|
-
const portfolioParent = group._container.getParentGroupForPortfolio(group);
|
|
819
|
-
|
|
820
843
|
const calculatePercent = (parent) => {
|
|
821
844
|
let marketPercent;
|
|
822
845
|
|
|
823
|
-
if (parent
|
|
846
|
+
if (parent && !excluded) {
|
|
824
847
|
const parentData = parent._dataActual;
|
|
825
848
|
|
|
826
849
|
if (parentData.marketAbsolute !== null && !parentData.marketAbsolute.getIsZero()) {
|
|
@@ -843,14 +866,15 @@ module.exports = (() => {
|
|
|
843
866
|
return marketPercent;
|
|
844
867
|
};
|
|
845
868
|
|
|
846
|
-
actual.marketPercent = calculatePercent(
|
|
869
|
+
actual.marketPercent = calculatePercent(parentGroup);
|
|
847
870
|
format.marketPercent = formatPercent(actual.marketPercent, 2);
|
|
848
871
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
872
|
+
if (parentGroup === portfolioGroup) {
|
|
873
|
+
actual.marketPercentPortfolio = actual.marketPercent;
|
|
874
|
+
format.marketPercentPortfolio = format.marketPercent;
|
|
875
|
+
} else {
|
|
876
|
+
actual.marketPercentPortfolio = calculatePercent(portfolioGroup);
|
|
877
|
+
format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
|
|
854
878
|
}
|
|
855
879
|
}
|
|
856
880
|
|