@barchart/portfolio-api-common 1.0.264 → 1.0.268
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/processing/PositionContainer.js +68 -24
- package/lib/processing/PositionGroup.js +61 -37
- package/package.json +1 -1
- package/test/SpecRunner.js +129 -61
|
@@ -146,6 +146,8 @@ module.exports = (() => {
|
|
|
146
146
|
}, { });
|
|
147
147
|
|
|
148
148
|
Object.keys(this._portfolios).forEach(key => updateEmptyPortfolioGroups.call(this, this._portfolios[key]));
|
|
149
|
+
|
|
150
|
+
recalculatePercentages.call(this);
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
/**
|
|
@@ -324,6 +326,8 @@ module.exports = (() => {
|
|
|
324
326
|
this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
|
|
325
327
|
}
|
|
326
328
|
});
|
|
329
|
+
|
|
330
|
+
recalculatePercentages.call(this);
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
/**
|
|
@@ -337,6 +341,8 @@ module.exports = (() => {
|
|
|
337
341
|
assert.argumentIsRequired(position.position, 'position.position', String);
|
|
338
342
|
|
|
339
343
|
removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
|
|
344
|
+
|
|
345
|
+
recalculatePercentages.call(this);
|
|
340
346
|
}
|
|
341
347
|
|
|
342
348
|
/**
|
|
@@ -374,18 +380,47 @@ module.exports = (() => {
|
|
|
374
380
|
* level that contains the position(s) for the symbol.
|
|
375
381
|
*
|
|
376
382
|
* @public
|
|
377
|
-
* @param {String} symbol
|
|
378
383
|
* @param {Object} quote
|
|
379
384
|
*/
|
|
380
|
-
setPositionQuote(
|
|
381
|
-
assert.argumentIsRequired(symbol, 'symbol', String);
|
|
385
|
+
setPositionQuote(quote) {
|
|
382
386
|
assert.argumentIsRequired(quote, 'quote', Object);
|
|
383
387
|
|
|
384
|
-
|
|
385
|
-
|
|
388
|
+
const symbol = quote.symbol;
|
|
389
|
+
|
|
390
|
+
if (symbol) {
|
|
391
|
+
if (this._symbols.hasOwnProperty(symbol)) {
|
|
392
|
+
this._symbols[symbol].forEach(item => item.setQuote(quote));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
recalculatePercentages.call(this);
|
|
386
396
|
}
|
|
387
397
|
}
|
|
388
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Performs a batch update of quotes; causing updates to any grouping
|
|
401
|
+
* level that contains the position(s) for the symbol(s).
|
|
402
|
+
*
|
|
403
|
+
* @public
|
|
404
|
+
* @param {Object} quote
|
|
405
|
+
*/
|
|
406
|
+
setPositionQuotes(quotes) {
|
|
407
|
+
assert.argumentIsArray(quotes, 'quotes');
|
|
408
|
+
|
|
409
|
+
this.startTransaction(() => {
|
|
410
|
+
quotes.forEach((quote) => {
|
|
411
|
+
const symbol = quote.symbol;
|
|
412
|
+
|
|
413
|
+
if (symbol) {
|
|
414
|
+
if (this._symbols.hasOwnProperty(symbol)) {
|
|
415
|
+
this._symbols[symbol].forEach(item => item.setQuote(quote));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
recalculatePercentages.call(this);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
389
424
|
/**
|
|
390
425
|
* Returns all forex symbols that are required to do currency translations.
|
|
391
426
|
*
|
|
@@ -411,23 +446,27 @@ module.exports = (() => {
|
|
|
411
446
|
* any grouping level that contains that requires translation.
|
|
412
447
|
*
|
|
413
448
|
* @public
|
|
414
|
-
* @param {String} symbol
|
|
415
449
|
* @param {Object} quote
|
|
416
450
|
*/
|
|
417
|
-
setForexQuote(
|
|
418
|
-
assert.argumentIsRequired(symbol, 'symbol', String);
|
|
451
|
+
setForexQuote(quote) {
|
|
419
452
|
assert.argumentIsRequired(quote, 'quote', Object);
|
|
420
453
|
|
|
421
|
-
const
|
|
422
|
-
const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
454
|
+
const symbol = quote.symbol;
|
|
423
455
|
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
456
|
+
if (symbol) {
|
|
457
|
+
const rate = Rate.fromPair(quote.lastPrice, symbol);
|
|
458
|
+
const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
459
|
+
|
|
460
|
+
if (index < 0) {
|
|
461
|
+
this._forexQuotes.push(rate);
|
|
462
|
+
} else {
|
|
463
|
+
this._forexQuotes[ index ] = rate;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
Object.keys(this._trees).forEach(key => this._trees[ key ].walk(group => group.setForexRates(this._forexQuotes), true, false));
|
|
429
467
|
|
|
430
|
-
|
|
468
|
+
recalculatePercentages.call(this);
|
|
469
|
+
}
|
|
431
470
|
}
|
|
432
471
|
|
|
433
472
|
/**
|
|
@@ -739,12 +778,6 @@ module.exports = (() => {
|
|
|
739
778
|
}
|
|
740
779
|
}
|
|
741
780
|
}));
|
|
742
|
-
|
|
743
|
-
addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
|
|
744
|
-
if (!groupTree.getIsRoot()) {
|
|
745
|
-
groupTree.getParent().walk((childGroup) => childGroup.refreshMarketPercent());
|
|
746
|
-
}
|
|
747
|
-
}));
|
|
748
781
|
}
|
|
749
782
|
|
|
750
783
|
function createGroups(parentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
|
|
@@ -752,6 +785,8 @@ module.exports = (() => {
|
|
|
752
785
|
return;
|
|
753
786
|
}
|
|
754
787
|
|
|
788
|
+
const rates = this._forexQuotes;
|
|
789
|
+
|
|
755
790
|
const levelDefinition = levelDefinitions[0];
|
|
756
791
|
|
|
757
792
|
const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
|
|
@@ -759,7 +794,7 @@ module.exports = (() => {
|
|
|
759
794
|
const items = populatedObjects[key];
|
|
760
795
|
const first = items[0];
|
|
761
796
|
|
|
762
|
-
list.push(new PositionGroup(
|
|
797
|
+
list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
|
|
763
798
|
|
|
764
799
|
return list;
|
|
765
800
|
}, [ ]);
|
|
@@ -772,7 +807,7 @@ module.exports = (() => {
|
|
|
772
807
|
});
|
|
773
808
|
|
|
774
809
|
const empty = missingGroups.map((group) => {
|
|
775
|
-
return new PositionGroup(
|
|
810
|
+
return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
|
|
776
811
|
});
|
|
777
812
|
|
|
778
813
|
const compositeGroups = populatedGroups.concat(empty);
|
|
@@ -812,6 +847,9 @@ module.exports = (() => {
|
|
|
812
847
|
|
|
813
848
|
this._nodes[group.id] = childTree;
|
|
814
849
|
|
|
850
|
+
group.setParentGroup(this.getParentGroup(group));
|
|
851
|
+
group.setPortfolioGroup(this.getParentGroupForPortfolio(group));
|
|
852
|
+
|
|
815
853
|
initializeGroupObservers.call(this, childTree, treeDefinition);
|
|
816
854
|
|
|
817
855
|
createGroups.call(this, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
|
|
@@ -952,5 +990,11 @@ module.exports = (() => {
|
|
|
952
990
|
groupNodeToSever.walk(group => delete this._nodes[group.id], false, true);
|
|
953
991
|
}
|
|
954
992
|
|
|
993
|
+
function recalculatePercentages() {
|
|
994
|
+
Object.keys(this._trees).forEach((key) => {
|
|
995
|
+
this._trees[key].walk(group => group.refreshMarketPercent(), false, false);
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
955
999
|
return PositionContainer;
|
|
956
1000
|
})();
|
|
@@ -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
|
|
package/package.json
CHANGED
package/test/SpecRunner.js
CHANGED
|
@@ -1302,6 +1302,8 @@ module.exports = (() => {
|
|
|
1302
1302
|
}, { });
|
|
1303
1303
|
|
|
1304
1304
|
Object.keys(this._portfolios).forEach(key => updateEmptyPortfolioGroups.call(this, this._portfolios[key]));
|
|
1305
|
+
|
|
1306
|
+
recalculatePercentages.call(this);
|
|
1305
1307
|
}
|
|
1306
1308
|
|
|
1307
1309
|
/**
|
|
@@ -1480,6 +1482,8 @@ module.exports = (() => {
|
|
|
1480
1482
|
this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
|
|
1481
1483
|
}
|
|
1482
1484
|
});
|
|
1485
|
+
|
|
1486
|
+
recalculatePercentages.call(this);
|
|
1483
1487
|
}
|
|
1484
1488
|
|
|
1485
1489
|
/**
|
|
@@ -1493,6 +1497,8 @@ module.exports = (() => {
|
|
|
1493
1497
|
assert.argumentIsRequired(position.position, 'position.position', String);
|
|
1494
1498
|
|
|
1495
1499
|
removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
|
|
1500
|
+
|
|
1501
|
+
recalculatePercentages.call(this);
|
|
1496
1502
|
}
|
|
1497
1503
|
|
|
1498
1504
|
/**
|
|
@@ -1530,18 +1536,47 @@ module.exports = (() => {
|
|
|
1530
1536
|
* level that contains the position(s) for the symbol.
|
|
1531
1537
|
*
|
|
1532
1538
|
* @public
|
|
1533
|
-
* @param {String} symbol
|
|
1534
1539
|
* @param {Object} quote
|
|
1535
1540
|
*/
|
|
1536
|
-
setPositionQuote(
|
|
1537
|
-
assert.argumentIsRequired(symbol, 'symbol', String);
|
|
1541
|
+
setPositionQuote(quote) {
|
|
1538
1542
|
assert.argumentIsRequired(quote, 'quote', Object);
|
|
1539
1543
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1544
|
+
const symbol = quote.symbol;
|
|
1545
|
+
|
|
1546
|
+
if (symbol) {
|
|
1547
|
+
if (this._symbols.hasOwnProperty(symbol)) {
|
|
1548
|
+
this._symbols[symbol].forEach(item => item.setQuote(quote));
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
recalculatePercentages.call(this);
|
|
1542
1552
|
}
|
|
1543
1553
|
}
|
|
1544
1554
|
|
|
1555
|
+
/**
|
|
1556
|
+
* Performs a batch update of quotes; causing updates to any grouping
|
|
1557
|
+
* level that contains the position(s) for the symbol(s).
|
|
1558
|
+
*
|
|
1559
|
+
* @public
|
|
1560
|
+
* @param {Object} quote
|
|
1561
|
+
*/
|
|
1562
|
+
setPositionQuotes(quotes) {
|
|
1563
|
+
assert.argumentIsArray(quotes, 'quotes');
|
|
1564
|
+
|
|
1565
|
+
this.startTransaction(() => {
|
|
1566
|
+
quotes.forEach((quote) => {
|
|
1567
|
+
const symbol = quote.symbol;
|
|
1568
|
+
|
|
1569
|
+
if (symbol) {
|
|
1570
|
+
if (this._symbols.hasOwnProperty(symbol)) {
|
|
1571
|
+
this._symbols[symbol].forEach(item => item.setQuote(quote));
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
recalculatePercentages.call(this);
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1545
1580
|
/**
|
|
1546
1581
|
* Returns all forex symbols that are required to do currency translations.
|
|
1547
1582
|
*
|
|
@@ -1567,23 +1602,27 @@ module.exports = (() => {
|
|
|
1567
1602
|
* any grouping level that contains that requires translation.
|
|
1568
1603
|
*
|
|
1569
1604
|
* @public
|
|
1570
|
-
* @param {String} symbol
|
|
1571
1605
|
* @param {Object} quote
|
|
1572
1606
|
*/
|
|
1573
|
-
setForexQuote(
|
|
1574
|
-
assert.argumentIsRequired(symbol, 'symbol', String);
|
|
1607
|
+
setForexQuote(quote) {
|
|
1575
1608
|
assert.argumentIsRequired(quote, 'quote', Object);
|
|
1576
1609
|
|
|
1577
|
-
const
|
|
1578
|
-
const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
1610
|
+
const symbol = quote.symbol;
|
|
1579
1611
|
|
|
1580
|
-
if (
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
this._forexQuotes[index] = rate;
|
|
1584
|
-
}
|
|
1612
|
+
if (symbol) {
|
|
1613
|
+
const rate = Rate.fromPair(quote.lastPrice, symbol);
|
|
1614
|
+
const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
1585
1615
|
|
|
1586
|
-
|
|
1616
|
+
if (index < 0) {
|
|
1617
|
+
this._forexQuotes.push(rate);
|
|
1618
|
+
} else {
|
|
1619
|
+
this._forexQuotes[ index ] = rate;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
Object.keys(this._trees).forEach(key => this._trees[ key ].walk(group => group.setForexRates(this._forexQuotes), true, false));
|
|
1623
|
+
|
|
1624
|
+
recalculatePercentages.call(this);
|
|
1625
|
+
}
|
|
1587
1626
|
}
|
|
1588
1627
|
|
|
1589
1628
|
/**
|
|
@@ -1895,12 +1934,6 @@ module.exports = (() => {
|
|
|
1895
1934
|
}
|
|
1896
1935
|
}
|
|
1897
1936
|
}));
|
|
1898
|
-
|
|
1899
|
-
addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
|
|
1900
|
-
if (!groupTree.getIsRoot()) {
|
|
1901
|
-
groupTree.getParent().walk((childGroup) => childGroup.refreshMarketPercent());
|
|
1902
|
-
}
|
|
1903
|
-
}));
|
|
1904
1937
|
}
|
|
1905
1938
|
|
|
1906
1939
|
function createGroups(parentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
|
|
@@ -1908,6 +1941,8 @@ module.exports = (() => {
|
|
|
1908
1941
|
return;
|
|
1909
1942
|
}
|
|
1910
1943
|
|
|
1944
|
+
const rates = this._forexQuotes;
|
|
1945
|
+
|
|
1911
1946
|
const levelDefinition = levelDefinitions[0];
|
|
1912
1947
|
|
|
1913
1948
|
const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
|
|
@@ -1915,7 +1950,7 @@ module.exports = (() => {
|
|
|
1915
1950
|
const items = populatedObjects[key];
|
|
1916
1951
|
const first = items[0];
|
|
1917
1952
|
|
|
1918
|
-
list.push(new PositionGroup(
|
|
1953
|
+
list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
|
|
1919
1954
|
|
|
1920
1955
|
return list;
|
|
1921
1956
|
}, [ ]);
|
|
@@ -1928,7 +1963,7 @@ module.exports = (() => {
|
|
|
1928
1963
|
});
|
|
1929
1964
|
|
|
1930
1965
|
const empty = missingGroups.map((group) => {
|
|
1931
|
-
return new PositionGroup(
|
|
1966
|
+
return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
|
|
1932
1967
|
});
|
|
1933
1968
|
|
|
1934
1969
|
const compositeGroups = populatedGroups.concat(empty);
|
|
@@ -1968,6 +2003,9 @@ module.exports = (() => {
|
|
|
1968
2003
|
|
|
1969
2004
|
this._nodes[group.id] = childTree;
|
|
1970
2005
|
|
|
2006
|
+
group.setParentGroup(this.getParentGroup(group));
|
|
2007
|
+
group.setPortfolioGroup(this.getParentGroupForPortfolio(group));
|
|
2008
|
+
|
|
1971
2009
|
initializeGroupObservers.call(this, childTree, treeDefinition);
|
|
1972
2010
|
|
|
1973
2011
|
createGroups.call(this, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
|
|
@@ -2108,6 +2146,12 @@ module.exports = (() => {
|
|
|
2108
2146
|
groupNodeToSever.walk(group => delete this._nodes[group.id], false, true);
|
|
2109
2147
|
}
|
|
2110
2148
|
|
|
2149
|
+
function recalculatePercentages() {
|
|
2150
|
+
Object.keys(this._trees).forEach((key) => {
|
|
2151
|
+
this._trees[key].walk(group => group.refreshMarketPercent(), false, false);
|
|
2152
|
+
});
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2111
2155
|
return PositionContainer;
|
|
2112
2156
|
})();
|
|
2113
2157
|
|
|
@@ -2147,13 +2191,17 @@ module.exports = (() => {
|
|
|
2147
2191
|
* @param {Boolean=} aggregateCash
|
|
2148
2192
|
*/
|
|
2149
2193
|
class PositionGroup {
|
|
2150
|
-
constructor(
|
|
2194
|
+
constructor(definition, items, rates, currency, key, description, aggregateCash) {
|
|
2151
2195
|
this._id = counter++;
|
|
2152
2196
|
|
|
2153
2197
|
this._definition = definition;
|
|
2154
|
-
this._container = container;
|
|
2155
2198
|
|
|
2156
2199
|
this._items = items;
|
|
2200
|
+
this._rates = rates;
|
|
2201
|
+
|
|
2202
|
+
this._parentGroup = null;
|
|
2203
|
+
this._portfolioGroup = null;
|
|
2204
|
+
|
|
2157
2205
|
this._currency = currency || Currency.CAD;
|
|
2158
2206
|
this._bypassCurrencyTranslation = false;
|
|
2159
2207
|
|
|
@@ -2167,7 +2215,6 @@ module.exports = (() => {
|
|
|
2167
2215
|
this._suspended = false;
|
|
2168
2216
|
this._showClosedPositions = false;
|
|
2169
2217
|
|
|
2170
|
-
this._marketPercentChangeEvent = new Event(this);
|
|
2171
2218
|
this._groupExcludedChangeEvent = new Event(this);
|
|
2172
2219
|
this._showClosedPositionsChangeEvent = new Event(this);
|
|
2173
2220
|
|
|
@@ -2380,6 +2427,40 @@ module.exports = (() => {
|
|
|
2380
2427
|
return this._excluded;
|
|
2381
2428
|
}
|
|
2382
2429
|
|
|
2430
|
+
/**
|
|
2431
|
+
* Sets the immediate parent group (allowing for calculation of relative
|
|
2432
|
+
* percentages).
|
|
2433
|
+
*
|
|
2434
|
+
* @public
|
|
2435
|
+
* @param {PortfolioGroup} group
|
|
2436
|
+
*/
|
|
2437
|
+
setParentGroup(group) {
|
|
2438
|
+
assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
|
|
2439
|
+
|
|
2440
|
+
if (this._parentGroup !== null) {
|
|
2441
|
+
throw new Error('The parent group has already been set.');
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
this._parentGroup = group;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/**
|
|
2448
|
+
* Sets the nearest parent group for a portfolio (allowing for calculation
|
|
2449
|
+
* of relative percentages).
|
|
2450
|
+
*
|
|
2451
|
+
* @public
|
|
2452
|
+
* @param {PortfolioGroup} group
|
|
2453
|
+
*/
|
|
2454
|
+
setPortfolioGroup(group) {
|
|
2455
|
+
assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
|
|
2456
|
+
|
|
2457
|
+
if (this._portfolioGroup !== null) {
|
|
2458
|
+
throw new Error('The portfolio group has already been set.');
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
this._portfolioGroup = group;
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2383
2464
|
/**
|
|
2384
2465
|
* Adds a new {@link PositionItem} to the group.
|
|
2385
2466
|
*
|
|
@@ -2425,9 +2506,11 @@ module.exports = (() => {
|
|
|
2425
2506
|
* Causes aggregated data to be recalculated using a new exchange rate.
|
|
2426
2507
|
*
|
|
2427
2508
|
* @public
|
|
2428
|
-
* @param {Rate} rate
|
|
2509
|
+
* @param {Array.<Rate>} rate
|
|
2429
2510
|
*/
|
|
2430
|
-
|
|
2511
|
+
setForexRates(rates) {
|
|
2512
|
+
this._rates = rates;
|
|
2513
|
+
|
|
2431
2514
|
if (!this._bypassCurrencyTranslation) {
|
|
2432
2515
|
this.refresh();
|
|
2433
2516
|
}
|
|
@@ -2515,10 +2598,8 @@ module.exports = (() => {
|
|
|
2515
2598
|
return;
|
|
2516
2599
|
}
|
|
2517
2600
|
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
calculateStaticData(this, rates);
|
|
2521
|
-
calculatePriceData(this, rates, null, true);
|
|
2601
|
+
calculateStaticData(this, this._rates);
|
|
2602
|
+
calculatePriceData(this, this._rates, null, true);
|
|
2522
2603
|
}
|
|
2523
2604
|
|
|
2524
2605
|
/**
|
|
@@ -2528,7 +2609,7 @@ module.exports = (() => {
|
|
|
2528
2609
|
* @public
|
|
2529
2610
|
*/
|
|
2530
2611
|
refreshMarketPercent() {
|
|
2531
|
-
calculateMarketPercent(this, this.
|
|
2612
|
+
calculateMarketPercent(this, this._rates, this._parentGroup, this._portfolioGroup);
|
|
2532
2613
|
}
|
|
2533
2614
|
|
|
2534
2615
|
/**
|
|
@@ -2541,17 +2622,6 @@ module.exports = (() => {
|
|
|
2541
2622
|
return this._items.length === 0;
|
|
2542
2623
|
}
|
|
2543
2624
|
|
|
2544
|
-
/**
|
|
2545
|
-
* Adds an observer for change in the market percentage of the group.
|
|
2546
|
-
*
|
|
2547
|
-
* @public
|
|
2548
|
-
* @param {Function} handler
|
|
2549
|
-
* @return {Disposable}
|
|
2550
|
-
*/
|
|
2551
|
-
registerMarketPercentChangeHandler(handler) {
|
|
2552
|
-
return this._marketPercentChangeEvent.register(handler);
|
|
2553
|
-
}
|
|
2554
|
-
|
|
2555
2625
|
/**
|
|
2556
2626
|
* Adds an observer for changes to the exclusion of the group
|
|
2557
2627
|
* from higher level aggregations.
|
|
@@ -2606,10 +2676,10 @@ module.exports = (() => {
|
|
|
2606
2676
|
this._dataFormat.currentPrice = null;
|
|
2607
2677
|
}
|
|
2608
2678
|
|
|
2609
|
-
calculatePriceData(this, this.
|
|
2679
|
+
calculatePriceData(this, this._rates, sender, false);
|
|
2610
2680
|
});
|
|
2611
2681
|
|
|
2612
|
-
let fundamentalBinding = item.registerFundamentalDataChangeHandler((data
|
|
2682
|
+
let fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
|
|
2613
2683
|
if (this._single) {
|
|
2614
2684
|
this._dataFormat.fundamental = data;
|
|
2615
2685
|
} else {
|
|
@@ -2660,13 +2730,13 @@ module.exports = (() => {
|
|
|
2660
2730
|
let newsBinding = Disposable.getEmpty();
|
|
2661
2731
|
|
|
2662
2732
|
if (this._single) {
|
|
2663
|
-
newsBinding = item.registerNewsExistsChangeHandler((exists
|
|
2733
|
+
newsBinding = item.registerNewsExistsChangeHandler((exists) => {
|
|
2664
2734
|
this._dataActual.newsExists = exists;
|
|
2665
2735
|
this._dataFormat.newsExists = exists;
|
|
2666
2736
|
});
|
|
2667
2737
|
}
|
|
2668
2738
|
|
|
2669
|
-
this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio
|
|
2739
|
+
this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio) => {
|
|
2670
2740
|
const descriptionSelector = this._definition.descriptionSelector;
|
|
2671
2741
|
|
|
2672
2742
|
this._description = descriptionSelector(this._items[0]);
|
|
@@ -2915,12 +2985,11 @@ module.exports = (() => {
|
|
|
2915
2985
|
|
|
2916
2986
|
format.total = formatCurrency(actual.total, currency);
|
|
2917
2987
|
format.totalNegative = actual.total.getIsNegative();
|
|
2918
|
-
|
|
2919
|
-
calculateMarketPercent(group, rates, false);
|
|
2988
|
+
|
|
2920
2989
|
calculateUnrealizedPercent(group);
|
|
2921
2990
|
}
|
|
2922
2991
|
|
|
2923
|
-
function calculateMarketPercent(group, rates,
|
|
2992
|
+
function calculateMarketPercent(group, rates, parentGroup, portfolioGroup) {
|
|
2924
2993
|
if (group.suspended) {
|
|
2925
2994
|
return;
|
|
2926
2995
|
}
|
|
@@ -2929,12 +2998,10 @@ module.exports = (() => {
|
|
|
2929
2998
|
const format = group._dataFormat;
|
|
2930
2999
|
const excluded = group._excluded;
|
|
2931
3000
|
|
|
2932
|
-
const portfolioParent = group._container.getParentGroupForPortfolio(group);
|
|
2933
|
-
|
|
2934
3001
|
const calculatePercent = (parent) => {
|
|
2935
3002
|
let marketPercent;
|
|
2936
3003
|
|
|
2937
|
-
if (parent
|
|
3004
|
+
if (parent && !excluded) {
|
|
2938
3005
|
const parentData = parent._dataActual;
|
|
2939
3006
|
|
|
2940
3007
|
if (parentData.marketAbsolute !== null && !parentData.marketAbsolute.getIsZero()) {
|
|
@@ -2957,14 +3024,15 @@ module.exports = (() => {
|
|
|
2957
3024
|
return marketPercent;
|
|
2958
3025
|
};
|
|
2959
3026
|
|
|
2960
|
-
actual.marketPercent = calculatePercent(
|
|
3027
|
+
actual.marketPercent = calculatePercent(parentGroup);
|
|
2961
3028
|
format.marketPercent = formatPercent(actual.marketPercent, 2);
|
|
2962
3029
|
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
3030
|
+
if (parentGroup === portfolioGroup) {
|
|
3031
|
+
actual.marketPercentPortfolio = actual.marketPercent;
|
|
3032
|
+
format.marketPercentPortfolio = format.marketPercent;
|
|
3033
|
+
} else {
|
|
3034
|
+
actual.marketPercentPortfolio = calculatePercent(portfolioGroup);
|
|
3035
|
+
format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
|
|
2968
3036
|
}
|
|
2969
3037
|
}
|
|
2970
3038
|
|