@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.
@@ -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(symbol, quote) {
381
- assert.argumentIsRequired(symbol, 'symbol', String);
385
+ setPositionQuote(quote) {
382
386
  assert.argumentIsRequired(quote, 'quote', Object);
383
387
 
384
- if (this._symbols.hasOwnProperty(symbol)) {
385
- this._symbols[symbol].forEach(item => item.setQuote(quote));
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(symbol, quote) {
418
- assert.argumentIsRequired(symbol, 'symbol', String);
451
+ setForexQuote(quote) {
419
452
  assert.argumentIsRequired(quote, 'quote', Object);
420
453
 
421
- const rate = Rate.fromPair(quote.lastPrice, symbol);
422
- const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
454
+ const symbol = quote.symbol;
423
455
 
424
- if (index < 0) {
425
- this._forexQuotes.push(rate);
426
- } else {
427
- this._forexQuotes[index] = rate;
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
- Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
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(this, levelDefinition, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
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(this, levelDefinition, [ ], group.currency, group.key, group.description);
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(container, definition, items, currency, key, description, aggregateCash) {
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
- setForexRate(rate) {
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
- const rates = this._container.getForexQuotes();
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._container.getForexQuotes(), true);
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._container.getForexQuotes(), sender, false);
521
+ calculatePriceData(this, this._rates, sender, false);
496
522
  });
497
523
 
498
- let fundamentalBinding = item.registerFundamentalDataChangeHandler((data, sender) => {
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, sender) => {
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, sender) => {
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, silent) {
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 !== null && !excluded) {
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(group._container.getParentGroup(group));
869
+ actual.marketPercent = calculatePercent(parentGroup);
847
870
  format.marketPercent = formatPercent(actual.marketPercent, 2);
848
871
 
849
- actual.marketPercentPortfolio = calculatePercent(group._container.getParentGroupForPortfolio(group));
850
- format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
851
-
852
- if (!silent) {
853
- group._marketPercentChangeEvent.fire(group);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.264",
3
+ "version": "1.0.268",
4
4
  "description": "Common classes used by the Portfolio system",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",
@@ -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(symbol, quote) {
1537
- assert.argumentIsRequired(symbol, 'symbol', String);
1541
+ setPositionQuote(quote) {
1538
1542
  assert.argumentIsRequired(quote, 'quote', Object);
1539
1543
 
1540
- if (this._symbols.hasOwnProperty(symbol)) {
1541
- this._symbols[symbol].forEach(item => item.setQuote(quote));
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(symbol, quote) {
1574
- assert.argumentIsRequired(symbol, 'symbol', String);
1607
+ setForexQuote(quote) {
1575
1608
  assert.argumentIsRequired(quote, 'quote', Object);
1576
1609
 
1577
- const rate = Rate.fromPair(quote.lastPrice, symbol);
1578
- const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
1610
+ const symbol = quote.symbol;
1579
1611
 
1580
- if (index < 0) {
1581
- this._forexQuotes.push(rate);
1582
- } else {
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
- Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
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(this, levelDefinition, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
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(this, levelDefinition, [ ], group.currency, group.key, group.description);
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(container, definition, items, currency, key, description, aggregateCash) {
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
- setForexRate(rate) {
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
- const rates = this._container.getForexQuotes();
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._container.getForexQuotes(), true);
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._container.getForexQuotes(), sender, false);
2679
+ calculatePriceData(this, this._rates, sender, false);
2610
2680
  });
2611
2681
 
2612
- let fundamentalBinding = item.registerFundamentalDataChangeHandler((data, sender) => {
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, sender) => {
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, sender) => {
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, silent) {
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 !== null && !excluded) {
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(group._container.getParentGroup(group));
3027
+ actual.marketPercent = calculatePercent(parentGroup);
2961
3028
  format.marketPercent = formatPercent(actual.marketPercent, 2);
2962
3029
 
2963
- actual.marketPercentPortfolio = calculatePercent(group._container.getParentGroupForPortfolio(group));
2964
- format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
2965
-
2966
- if (!silent) {
2967
- group._marketPercentChangeEvent.fire(group);
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