@barchart/portfolio-api-common 1.0.265 → 1.0.269

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
  /**
@@ -222,11 +224,9 @@ module.exports = (() => {
222
224
  assert.argumentIsRequired(portfolio, 'portfolio', Object);
223
225
  assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
224
226
 
225
- this.startTransaction(() => {
226
- getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => item.updatePortfolio(portfolio));
227
+ getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => item.updatePortfolio(portfolio));
227
228
 
228
- updateEmptyPortfolioGroups.call(this, portfolio);
229
- });
229
+ updateEmptyPortfolioGroups.call(this, portfolio);
230
230
  }
231
231
 
232
232
  /**
@@ -241,19 +241,19 @@ module.exports = (() => {
241
241
  assert.argumentIsRequired(portfolio, 'portfolio', Object);
242
242
  assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
243
243
 
244
- this.startTransaction(() => {
245
- getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => removePositionItem.call(this, item));
244
+ getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => removePositionItem.call(this, item));
246
245
 
247
- delete this._portfolios[portfolio.portfolio];
246
+ delete this._portfolios[portfolio.portfolio];
248
247
 
249
- Object.keys(this._trees).forEach((key) => {
250
- this._trees[key].walk((group, groupNode) => {
251
- if (group.definition.type === PositionLevelType.PORTFOLIO && group.key === PositionLevelDefinition.getKeyForPortfolioGroup(portfolio)) {
252
- severGroupNode.call(this, groupNode);
253
- }
254
- }, true, false);
255
- });
248
+ Object.keys(this._trees).forEach((key) => {
249
+ this._trees[key].walk((group, groupNode) => {
250
+ if (group.definition.type === PositionLevelType.PORTFOLIO && group.key === PositionLevelDefinition.getKeyForPortfolioGroup(portfolio)) {
251
+ severGroupNode.call(this, groupNode);
252
+ }
253
+ }, true, false);
256
254
  });
255
+
256
+ recalculatePercentages.call(this);
257
257
  }
258
258
 
259
259
  /**
@@ -274,56 +274,56 @@ module.exports = (() => {
274
274
  return;
275
275
  }
276
276
 
277
- this.startTransaction(() => {
278
- const existingBarchartSymbols = this.getPositionSymbols(false);
277
+ const existingBarchartSymbols = this.getPositionSymbols(false);
279
278
 
280
- removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
279
+ removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
281
280
 
282
- summaries.forEach((summary) => {
283
- addSummaryCurrent(this._summariesCurrent, summary, this._currentSummaryFrame, this._currentSummaryRange);
284
- addSummaryPrevious(this._summariesPrevious, summary, this._previousSummaryFrame, this._previousSummaryRanges);
285
- });
281
+ summaries.forEach((summary) => {
282
+ addSummaryCurrent(this._summariesCurrent, summary, this._currentSummaryFrame, this._currentSummaryRange);
283
+ addSummaryPrevious(this._summariesPrevious, summary, this._previousSummaryFrame, this._previousSummaryRanges);
284
+ });
286
285
 
287
- const item = createPositionItem.call(this, position);
286
+ const item = createPositionItem.call(this, position);
288
287
 
289
- addBarchartSymbol(this._symbols, item);
290
- addDisplaySymbol(this._symbolsDisplay, item);
288
+ addBarchartSymbol(this._symbols, item);
289
+ addDisplaySymbol(this._symbolsDisplay, item);
291
290
 
292
- this._items.push(item);
291
+ this._items.push(item);
293
292
 
294
- const createGroupOrInjectItem = (parentTree, treeDefinition, levelDefinitions) => {
295
- if (levelDefinitions.length === 0) {
296
- return;
297
- }
293
+ const createGroupOrInjectItem = (parentTree, treeDefinition, levelDefinitions) => {
294
+ if (levelDefinitions.length === 0) {
295
+ return;
296
+ }
298
297
 
299
- const levelDefinition = levelDefinitions[0];
300
- const levelKey = levelDefinition.keySelector(item);
298
+ const levelDefinition = levelDefinitions[0];
299
+ const levelKey = levelDefinition.keySelector(item);
301
300
 
302
- let groupTree;
301
+ let groupTree;
303
302
 
304
- if (parentTree.getChildren().length > 0) {
305
- groupTree = parentTree.findChild(childGroup => childGroup.key === levelKey) || null;
306
- } else {
307
- groupTree = null;
308
- }
303
+ if (parentTree.getChildren().length > 0) {
304
+ groupTree = parentTree.findChild(childGroup => childGroup.key === levelKey) || null;
305
+ } else {
306
+ groupTree = null;
307
+ }
309
308
 
310
- if (groupTree !== null) {
311
- groupTree.getValue().addItem(item);
309
+ if (groupTree !== null) {
310
+ groupTree.getValue().addItem(item);
312
311
 
313
- createGroupOrInjectItem(groupTree, treeDefinition, array.dropLeft(levelDefinitions));
314
- } else {
315
- createGroups.call(this, parentTree, [ item ], treeDefinition, levelDefinitions, [ ]);
316
- }
317
- };
312
+ createGroupOrInjectItem(groupTree, treeDefinition, array.dropLeft(levelDefinitions));
313
+ } else {
314
+ createGroups.call(this, parentTree, [ item ], treeDefinition, levelDefinitions, [ ]);
315
+ }
316
+ };
318
317
 
319
- this._definitions.forEach(definition => createGroupOrInjectItem(this._trees[definition.name], definition, definition.definitions));
318
+ this._definitions.forEach(definition => createGroupOrInjectItem(this._trees[definition.name], definition, definition.definitions));
320
319
 
321
- const addedBarchartSymbol = extractSymbolForBarchart(item.position);
320
+ const addedBarchartSymbol = extractSymbolForBarchart(item.position);
322
321
 
323
- if (addedBarchartSymbol !== null && !existingBarchartSymbols.some(existingBarchartSymbol => existingBarchartSymbol === addedBarchartSymbol)) {
324
- this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
325
- }
326
- });
322
+ if (addedBarchartSymbol !== null && !existingBarchartSymbols.some(existingBarchartSymbol => existingBarchartSymbol === addedBarchartSymbol)) {
323
+ this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
324
+ }
325
+
326
+ recalculatePercentages.call(this);
327
327
  }
328
328
 
329
329
  /**
@@ -337,6 +337,8 @@ module.exports = (() => {
337
337
  assert.argumentIsRequired(position.position, 'position.position', String);
338
338
 
339
339
  removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
340
+
341
+ recalculatePercentages.call(this);
340
342
  }
341
343
 
342
344
  /**
@@ -370,19 +372,54 @@ module.exports = (() => {
370
372
  }
371
373
 
372
374
  /**
373
- * Updates the quote for a single symbol; causing updates to any grouping
374
- * level that contains the position(s) for the symbol.
375
+ * Performs a batch update of both position quotes and forex quotes,
376
+ * triggering updates to position(s) and data aggregation(s).
375
377
  *
376
378
  * @public
377
- * @param {String} symbol
378
- * @param {Object} quote
379
+ * @param {Array.<Object>} positionQuotes
380
+ * @param {Array.<Object>} forexQuotes
379
381
  */
380
- setPositionQuote(symbol, quote) {
381
- assert.argumentIsRequired(symbol, 'symbol', String);
382
- assert.argumentIsRequired(quote, 'quote', Object);
382
+ setQuotes(positionQuotes, forexQuotes) {
383
+ assert.argumentIsArray(positionQuotes, 'positionQuotes');
384
+ assert.argumentIsArray(forexQuotes, 'forexQuotes');
383
385
 
384
- if (this._symbols.hasOwnProperty(symbol)) {
385
- this._symbols[symbol].forEach(item => item.setQuote(quote));
386
+ if (positionQuotes.length !== 0) {
387
+ positionQuotes.forEach((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
+ });
396
+ }
397
+
398
+ if (forexQuotes.length !== 0) {
399
+ forexQuotes.forEach((quote) => {
400
+ const symbol = quote.symbol;
401
+
402
+ if (symbol) {
403
+ const rate = Rate.fromPair(quote.lastPrice, symbol);
404
+ const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
405
+
406
+ if (index < 0) {
407
+ this._forexQuotes.push(rate);
408
+ } else {
409
+ this._forexQuotes[index] = rate;
410
+ }
411
+
412
+ Object.keys(this._trees).forEach((key) => {
413
+ this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false)
414
+ });
415
+
416
+ recalculatePercentages.call(this);
417
+ }
418
+ });
419
+ }
420
+
421
+ if (positionQuotes.length !== 0 || forexQuotes.length !== 0) {
422
+ recalculatePercentages.call(this);
386
423
  }
387
424
  }
388
425
 
@@ -406,30 +443,6 @@ module.exports = (() => {
406
443
  return this._forexQuotes;
407
444
  }
408
445
 
409
- /**
410
- * Updates the forex quote for a single currency pair; causing updates to
411
- * any grouping level that contains that requires translation.
412
- *
413
- * @public
414
- * @param {String} symbol
415
- * @param {Object} quote
416
- */
417
- setForexQuote(symbol, quote) {
418
- assert.argumentIsRequired(symbol, 'symbol', String);
419
- assert.argumentIsRequired(quote, 'quote', Object);
420
-
421
- const rate = Rate.fromPair(quote.lastPrice, symbol);
422
- const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
423
-
424
- if (index < 0) {
425
- this._forexQuotes.push(rate);
426
- } else {
427
- this._forexQuotes[index] = rate;
428
- }
429
-
430
- Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
431
- }
432
-
433
446
  /**
434
447
  * Updates fundamental data for a single symbol.
435
448
  *
@@ -578,39 +591,6 @@ module.exports = (() => {
578
591
  return this.getPositions(portfolio).find(p => p.position === position) || null;
579
592
  }
580
593
 
581
- /**
582
- * Pauses aggregation calculations during the processing of an action.
583
- *
584
- * @public
585
- * @param {Function} executor
586
- * @param {String=|Array.<String>=} names
587
- */
588
- startTransaction(executor, names) {
589
- let namesToUse;
590
-
591
- if (is.array(names)) {
592
- assert.argumentIsArray(names, 'names', String);
593
-
594
- namesToUse = names;
595
- } else {
596
- assert.argumentIsOptional(names, 'names', String);
597
-
598
- if (names) {
599
- namesToUse = [ names ];
600
- } else {
601
- namesToUse = Object.keys(this._trees);
602
- }
603
- }
604
-
605
- assert.argumentIsRequired(executor, 'executor', Function);
606
-
607
- namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(true), false, false));
608
-
609
- executor(this);
610
-
611
- namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(false), false, false));
612
- }
613
-
614
594
  /**
615
595
  * Registers an observer for symbol addition (this occurs when a new position is added
616
596
  * for a symbol that does not already exist in the container). This event only fires
@@ -739,12 +719,6 @@ module.exports = (() => {
739
719
  }
740
720
  }
741
721
  }));
742
-
743
- addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
744
- if (!groupTree.getIsRoot()) {
745
- groupTree.getParent().walk((childGroup) => childGroup.refreshMarketPercent());
746
- }
747
- }));
748
722
  }
749
723
 
750
724
  function createGroups(parentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
@@ -752,6 +726,8 @@ module.exports = (() => {
752
726
  return;
753
727
  }
754
728
 
729
+ const rates = this._forexQuotes;
730
+
755
731
  const levelDefinition = levelDefinitions[0];
756
732
 
757
733
  const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
@@ -759,7 +735,7 @@ module.exports = (() => {
759
735
  const items = populatedObjects[key];
760
736
  const first = items[0];
761
737
 
762
- list.push(new PositionGroup(this, levelDefinition, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
738
+ list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
763
739
 
764
740
  return list;
765
741
  }, [ ]);
@@ -772,7 +748,7 @@ module.exports = (() => {
772
748
  });
773
749
 
774
750
  const empty = missingGroups.map((group) => {
775
- return new PositionGroup(this, levelDefinition, [ ], group.currency, group.key, group.description);
751
+ return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
776
752
  });
777
753
 
778
754
  const compositeGroups = populatedGroups.concat(empty);
@@ -812,6 +788,9 @@ module.exports = (() => {
812
788
 
813
789
  this._nodes[group.id] = childTree;
814
790
 
791
+ group.setParentGroup(this.getParentGroup(group));
792
+ group.setPortfolioGroup(this.getParentGroupForPortfolio(group));
793
+
815
794
  initializeGroupObservers.call(this, childTree, treeDefinition);
816
795
 
817
796
  createGroups.call(this, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
@@ -952,5 +931,11 @@ module.exports = (() => {
952
931
  groupNodeToSever.walk(group => delete this._nodes[group.id], false, true);
953
932
  }
954
933
 
934
+ function recalculatePercentages() {
935
+ Object.keys(this._trees).forEach((key) => {
936
+ this._trees[key].walk(group => group.refreshMarketPercent(), false, false);
937
+ });
938
+ }
939
+
955
940
  return PositionContainer;
956
941
  })();
@@ -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
 
@@ -50,10 +54,8 @@ module.exports = (() => {
50
54
  this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
51
55
 
52
56
  this._excluded = false;
53
- this._suspended = false;
54
57
  this._showClosedPositions = false;
55
58
 
56
- this._marketPercentChangeEvent = new Event(this);
57
59
  this._groupExcludedChangeEvent = new Event(this);
58
60
  this._showClosedPositionsChangeEvent = new Event(this);
59
61
 
@@ -252,10 +254,6 @@ module.exports = (() => {
252
254
  return this._single;
253
255
  }
254
256
 
255
- get suspended() {
256
- return this._suspended;
257
- }
258
-
259
257
  /**
260
258
  * Indicates if the group should be excluded from higher-level aggregations.
261
259
  *
@@ -266,6 +264,40 @@ module.exports = (() => {
266
264
  return this._excluded;
267
265
  }
268
266
 
267
+ /**
268
+ * Sets the immediate parent group (allowing for calculation of relative
269
+ * percentages).
270
+ *
271
+ * @public
272
+ * @param {PortfolioGroup} group
273
+ */
274
+ setParentGroup(group) {
275
+ assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
276
+
277
+ if (this._parentGroup !== null) {
278
+ throw new Error('The parent group has already been set.');
279
+ }
280
+
281
+ this._parentGroup = group;
282
+ }
283
+
284
+ /**
285
+ * Sets the nearest parent group for a portfolio (allowing for calculation
286
+ * of relative percentages).
287
+ *
288
+ * @public
289
+ * @param {PortfolioGroup} group
290
+ */
291
+ setPortfolioGroup(group) {
292
+ assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
293
+
294
+ if (this._portfolioGroup !== null) {
295
+ throw new Error('The portfolio group has already been set.');
296
+ }
297
+
298
+ this._portfolioGroup = group;
299
+ }
300
+
269
301
  /**
270
302
  * Adds a new {@link PositionItem} to the group.
271
303
  *
@@ -311,9 +343,11 @@ module.exports = (() => {
311
343
  * Causes aggregated data to be recalculated using a new exchange rate.
312
344
  *
313
345
  * @public
314
- * @param {Rate} rate
346
+ * @param {Array.<Rate>} rate
315
347
  */
316
- setForexRate(rate) {
348
+ setForexRates(rates) {
349
+ this._rates = rates;
350
+
317
351
  if (!this._bypassCurrencyTranslation) {
318
352
  this.refresh();
319
353
  }
@@ -342,24 +376,6 @@ module.exports = (() => {
342
376
  }
343
377
  }
344
378
 
345
- /**
346
- * Stops (or starts) group-level aggregation calculations.
347
- *
348
- * @public
349
- * @param {Boolean} value
350
- */
351
- setSuspended(value) {
352
- assert.argumentIsRequired(value, 'value', Boolean);
353
-
354
- if (this._suspended !== value) {
355
- this._suspended = value;
356
-
357
- if (!this._suspended) {
358
- this.refresh();
359
- }
360
- }
361
- }
362
-
363
379
  /**
364
380
  * Updates the portfolio data. For example, a portfolio's name might change. This
365
381
  * function only affects {@link PositionLevelType.PORTFOLIO} groups.
@@ -391,20 +407,13 @@ module.exports = (() => {
391
407
  }
392
408
 
393
409
  /**
394
- * Causes all aggregated data to be recalculated (assuming the group has not
395
- * been suspended).
410
+ * Causes all aggregated data to be recalculated.
396
411
  *
397
412
  * @public
398
413
  */
399
414
  refresh() {
400
- if (this._suspended) {
401
- return;
402
- }
403
-
404
- const rates = this._container.getForexQuotes();
405
-
406
- calculateStaticData(this, rates);
407
- calculatePriceData(this, rates, null, true);
415
+ calculateStaticData(this, this._rates);
416
+ calculatePriceData(this, this._rates, null, true);
408
417
  }
409
418
 
410
419
  /**
@@ -414,7 +423,7 @@ module.exports = (() => {
414
423
  * @public
415
424
  */
416
425
  refreshMarketPercent() {
417
- calculateMarketPercent(this, this._container.getForexQuotes(), true);
426
+ calculateMarketPercent(this, this._rates, this._parentGroup, this._portfolioGroup);
418
427
  }
419
428
 
420
429
  /**
@@ -427,17 +436,6 @@ module.exports = (() => {
427
436
  return this._items.length === 0;
428
437
  }
429
438
 
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
439
  /**
442
440
  * Adds an observer for changes to the exclusion of the group
443
441
  * from higher level aggregations.
@@ -492,10 +490,10 @@ module.exports = (() => {
492
490
  this._dataFormat.currentPrice = null;
493
491
  }
494
492
 
495
- calculatePriceData(this, this._container.getForexQuotes(), sender, false);
493
+ calculatePriceData(this, this._rates, sender, false);
496
494
  });
497
495
 
498
- let fundamentalBinding = item.registerFundamentalDataChangeHandler((data, sender) => {
496
+ let fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
499
497
  if (this._single) {
500
498
  this._dataFormat.fundamental = data;
501
499
  } else {
@@ -546,13 +544,13 @@ module.exports = (() => {
546
544
  let newsBinding = Disposable.getEmpty();
547
545
 
548
546
  if (this._single) {
549
- newsBinding = item.registerNewsExistsChangeHandler((exists, sender) => {
547
+ newsBinding = item.registerNewsExistsChangeHandler((exists) => {
550
548
  this._dataActual.newsExists = exists;
551
549
  this._dataFormat.newsExists = exists;
552
550
  });
553
551
  }
554
552
 
555
- this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio, sender) => {
553
+ this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio) => {
556
554
  const descriptionSelector = this._definition.descriptionSelector;
557
555
 
558
556
  this._description = descriptionSelector(this._items[0]);
@@ -617,10 +615,6 @@ module.exports = (() => {
617
615
  }
618
616
 
619
617
  function calculateStaticData(group, rates) {
620
- if (group.suspended) {
621
- return;
622
- }
623
-
624
618
  const actual = group._dataActual;
625
619
  const format = group._dataFormat;
626
620
 
@@ -717,10 +711,6 @@ module.exports = (() => {
717
711
  }
718
712
 
719
713
  function calculatePriceData(group, rates, item, forceRefresh) {
720
- if (group.suspended) {
721
- return;
722
- }
723
-
724
714
  const currency = group.currency;
725
715
 
726
716
  const actual = group._dataActual;
@@ -801,28 +791,19 @@ module.exports = (() => {
801
791
 
802
792
  format.total = formatCurrency(actual.total, currency);
803
793
  format.totalNegative = actual.total.getIsNegative();
804
-
805
- calculateMarketPercent(group, rates, false);
794
+
806
795
  calculateUnrealizedPercent(group);
807
796
  }
808
797
 
809
- function calculateMarketPercent(group, rates, silent) {
810
- return;
811
-
812
- if (group.suspended) {
813
- return;
814
- }
815
-
798
+ function calculateMarketPercent(group, rates, parentGroup, portfolioGroup) {
816
799
  const actual = group._dataActual;
817
800
  const format = group._dataFormat;
818
801
  const excluded = group._excluded;
819
802
 
820
- const portfolioParent = group._container.getParentGroupForPortfolio(group);
821
-
822
803
  const calculatePercent = (parent) => {
823
804
  let marketPercent;
824
805
 
825
- if (parent !== null && !excluded) {
806
+ if (parent && !excluded) {
826
807
  const parentData = parent._dataActual;
827
808
 
828
809
  if (parentData.marketAbsolute !== null && !parentData.marketAbsolute.getIsZero()) {
@@ -845,14 +826,15 @@ module.exports = (() => {
845
826
  return marketPercent;
846
827
  };
847
828
 
848
- actual.marketPercent = calculatePercent(group._container.getParentGroup(group));
829
+ actual.marketPercent = calculatePercent(parentGroup);
849
830
  format.marketPercent = formatPercent(actual.marketPercent, 2);
850
831
 
851
- actual.marketPercentPortfolio = calculatePercent(group._container.getParentGroupForPortfolio(group));
852
- format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
853
-
854
- if (!silent) {
855
- group._marketPercentChangeEvent.fire(group);
832
+ if (parentGroup === portfolioGroup) {
833
+ actual.marketPercentPortfolio = actual.marketPercent;
834
+ format.marketPercentPortfolio = format.marketPercent;
835
+ } else {
836
+ actual.marketPercentPortfolio = calculatePercent(portfolioGroup);
837
+ format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
856
838
  }
857
839
  }
858
840
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.265",
3
+ "version": "1.0.269",
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
  /**
@@ -1378,11 +1380,9 @@ module.exports = (() => {
1378
1380
  assert.argumentIsRequired(portfolio, 'portfolio', Object);
1379
1381
  assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
1380
1382
 
1381
- this.startTransaction(() => {
1382
- getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => item.updatePortfolio(portfolio));
1383
+ getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => item.updatePortfolio(portfolio));
1383
1384
 
1384
- updateEmptyPortfolioGroups.call(this, portfolio);
1385
- });
1385
+ updateEmptyPortfolioGroups.call(this, portfolio);
1386
1386
  }
1387
1387
 
1388
1388
  /**
@@ -1397,19 +1397,19 @@ module.exports = (() => {
1397
1397
  assert.argumentIsRequired(portfolio, 'portfolio', Object);
1398
1398
  assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
1399
1399
 
1400
- this.startTransaction(() => {
1401
- getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => removePositionItem.call(this, item));
1400
+ getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => removePositionItem.call(this, item));
1402
1401
 
1403
- delete this._portfolios[portfolio.portfolio];
1402
+ delete this._portfolios[portfolio.portfolio];
1404
1403
 
1405
- Object.keys(this._trees).forEach((key) => {
1406
- this._trees[key].walk((group, groupNode) => {
1407
- if (group.definition.type === PositionLevelType.PORTFOLIO && group.key === PositionLevelDefinition.getKeyForPortfolioGroup(portfolio)) {
1408
- severGroupNode.call(this, groupNode);
1409
- }
1410
- }, true, false);
1411
- });
1404
+ Object.keys(this._trees).forEach((key) => {
1405
+ this._trees[key].walk((group, groupNode) => {
1406
+ if (group.definition.type === PositionLevelType.PORTFOLIO && group.key === PositionLevelDefinition.getKeyForPortfolioGroup(portfolio)) {
1407
+ severGroupNode.call(this, groupNode);
1408
+ }
1409
+ }, true, false);
1412
1410
  });
1411
+
1412
+ recalculatePercentages.call(this);
1413
1413
  }
1414
1414
 
1415
1415
  /**
@@ -1430,56 +1430,56 @@ module.exports = (() => {
1430
1430
  return;
1431
1431
  }
1432
1432
 
1433
- this.startTransaction(() => {
1434
- const existingBarchartSymbols = this.getPositionSymbols(false);
1433
+ const existingBarchartSymbols = this.getPositionSymbols(false);
1435
1434
 
1436
- removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
1435
+ removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
1437
1436
 
1438
- summaries.forEach((summary) => {
1439
- addSummaryCurrent(this._summariesCurrent, summary, this._currentSummaryFrame, this._currentSummaryRange);
1440
- addSummaryPrevious(this._summariesPrevious, summary, this._previousSummaryFrame, this._previousSummaryRanges);
1441
- });
1437
+ summaries.forEach((summary) => {
1438
+ addSummaryCurrent(this._summariesCurrent, summary, this._currentSummaryFrame, this._currentSummaryRange);
1439
+ addSummaryPrevious(this._summariesPrevious, summary, this._previousSummaryFrame, this._previousSummaryRanges);
1440
+ });
1442
1441
 
1443
- const item = createPositionItem.call(this, position);
1442
+ const item = createPositionItem.call(this, position);
1444
1443
 
1445
- addBarchartSymbol(this._symbols, item);
1446
- addDisplaySymbol(this._symbolsDisplay, item);
1444
+ addBarchartSymbol(this._symbols, item);
1445
+ addDisplaySymbol(this._symbolsDisplay, item);
1447
1446
 
1448
- this._items.push(item);
1447
+ this._items.push(item);
1449
1448
 
1450
- const createGroupOrInjectItem = (parentTree, treeDefinition, levelDefinitions) => {
1451
- if (levelDefinitions.length === 0) {
1452
- return;
1453
- }
1449
+ const createGroupOrInjectItem = (parentTree, treeDefinition, levelDefinitions) => {
1450
+ if (levelDefinitions.length === 0) {
1451
+ return;
1452
+ }
1454
1453
 
1455
- const levelDefinition = levelDefinitions[0];
1456
- const levelKey = levelDefinition.keySelector(item);
1454
+ const levelDefinition = levelDefinitions[0];
1455
+ const levelKey = levelDefinition.keySelector(item);
1457
1456
 
1458
- let groupTree;
1457
+ let groupTree;
1459
1458
 
1460
- if (parentTree.getChildren().length > 0) {
1461
- groupTree = parentTree.findChild(childGroup => childGroup.key === levelKey) || null;
1462
- } else {
1463
- groupTree = null;
1464
- }
1459
+ if (parentTree.getChildren().length > 0) {
1460
+ groupTree = parentTree.findChild(childGroup => childGroup.key === levelKey) || null;
1461
+ } else {
1462
+ groupTree = null;
1463
+ }
1465
1464
 
1466
- if (groupTree !== null) {
1467
- groupTree.getValue().addItem(item);
1465
+ if (groupTree !== null) {
1466
+ groupTree.getValue().addItem(item);
1468
1467
 
1469
- createGroupOrInjectItem(groupTree, treeDefinition, array.dropLeft(levelDefinitions));
1470
- } else {
1471
- createGroups.call(this, parentTree, [ item ], treeDefinition, levelDefinitions, [ ]);
1472
- }
1473
- };
1468
+ createGroupOrInjectItem(groupTree, treeDefinition, array.dropLeft(levelDefinitions));
1469
+ } else {
1470
+ createGroups.call(this, parentTree, [ item ], treeDefinition, levelDefinitions, [ ]);
1471
+ }
1472
+ };
1474
1473
 
1475
- this._definitions.forEach(definition => createGroupOrInjectItem(this._trees[definition.name], definition, definition.definitions));
1474
+ this._definitions.forEach(definition => createGroupOrInjectItem(this._trees[definition.name], definition, definition.definitions));
1476
1475
 
1477
- const addedBarchartSymbol = extractSymbolForBarchart(item.position);
1476
+ const addedBarchartSymbol = extractSymbolForBarchart(item.position);
1478
1477
 
1479
- if (addedBarchartSymbol !== null && !existingBarchartSymbols.some(existingBarchartSymbol => existingBarchartSymbol === addedBarchartSymbol)) {
1480
- this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
1481
- }
1482
- });
1478
+ if (addedBarchartSymbol !== null && !existingBarchartSymbols.some(existingBarchartSymbol => existingBarchartSymbol === addedBarchartSymbol)) {
1479
+ this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
1480
+ }
1481
+
1482
+ recalculatePercentages.call(this);
1483
1483
  }
1484
1484
 
1485
1485
  /**
@@ -1493,6 +1493,8 @@ module.exports = (() => {
1493
1493
  assert.argumentIsRequired(position.position, 'position.position', String);
1494
1494
 
1495
1495
  removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
1496
+
1497
+ recalculatePercentages.call(this);
1496
1498
  }
1497
1499
 
1498
1500
  /**
@@ -1526,19 +1528,54 @@ module.exports = (() => {
1526
1528
  }
1527
1529
 
1528
1530
  /**
1529
- * Updates the quote for a single symbol; causing updates to any grouping
1530
- * level that contains the position(s) for the symbol.
1531
+ * Performs a batch update of both position quotes and forex quotes,
1532
+ * triggering updates to position(s) and data aggregation(s).
1531
1533
  *
1532
1534
  * @public
1533
- * @param {String} symbol
1534
- * @param {Object} quote
1535
+ * @param {Array.<Object>} positionQuotes
1536
+ * @param {Array.<Object>} forexQuotes
1535
1537
  */
1536
- setPositionQuote(symbol, quote) {
1537
- assert.argumentIsRequired(symbol, 'symbol', String);
1538
- assert.argumentIsRequired(quote, 'quote', Object);
1538
+ setQuotes(positionQuotes, forexQuotes) {
1539
+ assert.argumentIsArray(positionQuotes, 'positionQuotes');
1540
+ assert.argumentIsArray(forexQuotes, 'forexQuotes');
1541
+
1542
+ if (positionQuotes.length !== 0) {
1543
+ positionQuotes.forEach((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
+ });
1552
+ }
1553
+
1554
+ if (forexQuotes.length !== 0) {
1555
+ forexQuotes.forEach((quote) => {
1556
+ const symbol = quote.symbol;
1557
+
1558
+ if (symbol) {
1559
+ const rate = Rate.fromPair(quote.lastPrice, symbol);
1560
+ const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
1561
+
1562
+ if (index < 0) {
1563
+ this._forexQuotes.push(rate);
1564
+ } else {
1565
+ this._forexQuotes[index] = rate;
1566
+ }
1567
+
1568
+ Object.keys(this._trees).forEach((key) => {
1569
+ this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false)
1570
+ });
1571
+
1572
+ recalculatePercentages.call(this);
1573
+ }
1574
+ });
1575
+ }
1539
1576
 
1540
- if (this._symbols.hasOwnProperty(symbol)) {
1541
- this._symbols[symbol].forEach(item => item.setQuote(quote));
1577
+ if (positionQuotes.length !== 0 || forexQuotes.length !== 0) {
1578
+ recalculatePercentages.call(this);
1542
1579
  }
1543
1580
  }
1544
1581
 
@@ -1562,30 +1599,6 @@ module.exports = (() => {
1562
1599
  return this._forexQuotes;
1563
1600
  }
1564
1601
 
1565
- /**
1566
- * Updates the forex quote for a single currency pair; causing updates to
1567
- * any grouping level that contains that requires translation.
1568
- *
1569
- * @public
1570
- * @param {String} symbol
1571
- * @param {Object} quote
1572
- */
1573
- setForexQuote(symbol, quote) {
1574
- assert.argumentIsRequired(symbol, 'symbol', String);
1575
- assert.argumentIsRequired(quote, 'quote', Object);
1576
-
1577
- const rate = Rate.fromPair(quote.lastPrice, symbol);
1578
- const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
1579
-
1580
- if (index < 0) {
1581
- this._forexQuotes.push(rate);
1582
- } else {
1583
- this._forexQuotes[index] = rate;
1584
- }
1585
-
1586
- Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
1587
- }
1588
-
1589
1602
  /**
1590
1603
  * Updates fundamental data for a single symbol.
1591
1604
  *
@@ -1734,39 +1747,6 @@ module.exports = (() => {
1734
1747
  return this.getPositions(portfolio).find(p => p.position === position) || null;
1735
1748
  }
1736
1749
 
1737
- /**
1738
- * Pauses aggregation calculations during the processing of an action.
1739
- *
1740
- * @public
1741
- * @param {Function} executor
1742
- * @param {String=|Array.<String>=} names
1743
- */
1744
- startTransaction(executor, names) {
1745
- let namesToUse;
1746
-
1747
- if (is.array(names)) {
1748
- assert.argumentIsArray(names, 'names', String);
1749
-
1750
- namesToUse = names;
1751
- } else {
1752
- assert.argumentIsOptional(names, 'names', String);
1753
-
1754
- if (names) {
1755
- namesToUse = [ names ];
1756
- } else {
1757
- namesToUse = Object.keys(this._trees);
1758
- }
1759
- }
1760
-
1761
- assert.argumentIsRequired(executor, 'executor', Function);
1762
-
1763
- namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(true), false, false));
1764
-
1765
- executor(this);
1766
-
1767
- namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(false), false, false));
1768
- }
1769
-
1770
1750
  /**
1771
1751
  * Registers an observer for symbol addition (this occurs when a new position is added
1772
1752
  * for a symbol that does not already exist in the container). This event only fires
@@ -1895,12 +1875,6 @@ module.exports = (() => {
1895
1875
  }
1896
1876
  }
1897
1877
  }));
1898
-
1899
- addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
1900
- if (!groupTree.getIsRoot()) {
1901
- groupTree.getParent().walk((childGroup) => childGroup.refreshMarketPercent());
1902
- }
1903
- }));
1904
1878
  }
1905
1879
 
1906
1880
  function createGroups(parentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
@@ -1908,6 +1882,8 @@ module.exports = (() => {
1908
1882
  return;
1909
1883
  }
1910
1884
 
1885
+ const rates = this._forexQuotes;
1886
+
1911
1887
  const levelDefinition = levelDefinitions[0];
1912
1888
 
1913
1889
  const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
@@ -1915,7 +1891,7 @@ module.exports = (() => {
1915
1891
  const items = populatedObjects[key];
1916
1892
  const first = items[0];
1917
1893
 
1918
- list.push(new PositionGroup(this, levelDefinition, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
1894
+ list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
1919
1895
 
1920
1896
  return list;
1921
1897
  }, [ ]);
@@ -1928,7 +1904,7 @@ module.exports = (() => {
1928
1904
  });
1929
1905
 
1930
1906
  const empty = missingGroups.map((group) => {
1931
- return new PositionGroup(this, levelDefinition, [ ], group.currency, group.key, group.description);
1907
+ return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
1932
1908
  });
1933
1909
 
1934
1910
  const compositeGroups = populatedGroups.concat(empty);
@@ -1968,6 +1944,9 @@ module.exports = (() => {
1968
1944
 
1969
1945
  this._nodes[group.id] = childTree;
1970
1946
 
1947
+ group.setParentGroup(this.getParentGroup(group));
1948
+ group.setPortfolioGroup(this.getParentGroupForPortfolio(group));
1949
+
1971
1950
  initializeGroupObservers.call(this, childTree, treeDefinition);
1972
1951
 
1973
1952
  createGroups.call(this, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
@@ -2108,6 +2087,12 @@ module.exports = (() => {
2108
2087
  groupNodeToSever.walk(group => delete this._nodes[group.id], false, true);
2109
2088
  }
2110
2089
 
2090
+ function recalculatePercentages() {
2091
+ Object.keys(this._trees).forEach((key) => {
2092
+ this._trees[key].walk(group => group.refreshMarketPercent(), false, false);
2093
+ });
2094
+ }
2095
+
2111
2096
  return PositionContainer;
2112
2097
  })();
2113
2098
 
@@ -2147,13 +2132,17 @@ module.exports = (() => {
2147
2132
  * @param {Boolean=} aggregateCash
2148
2133
  */
2149
2134
  class PositionGroup {
2150
- constructor(container, definition, items, currency, key, description, aggregateCash) {
2135
+ constructor(definition, items, rates, currency, key, description, aggregateCash) {
2151
2136
  this._id = counter++;
2152
2137
 
2153
2138
  this._definition = definition;
2154
- this._container = container;
2155
2139
 
2156
2140
  this._items = items;
2141
+ this._rates = rates;
2142
+
2143
+ this._parentGroup = null;
2144
+ this._portfolioGroup = null;
2145
+
2157
2146
  this._currency = currency || Currency.CAD;
2158
2147
  this._bypassCurrencyTranslation = false;
2159
2148
 
@@ -2164,10 +2153,8 @@ module.exports = (() => {
2164
2153
  this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
2165
2154
 
2166
2155
  this._excluded = false;
2167
- this._suspended = false;
2168
2156
  this._showClosedPositions = false;
2169
2157
 
2170
- this._marketPercentChangeEvent = new Event(this);
2171
2158
  this._groupExcludedChangeEvent = new Event(this);
2172
2159
  this._showClosedPositionsChangeEvent = new Event(this);
2173
2160
 
@@ -2366,10 +2353,6 @@ module.exports = (() => {
2366
2353
  return this._single;
2367
2354
  }
2368
2355
 
2369
- get suspended() {
2370
- return this._suspended;
2371
- }
2372
-
2373
2356
  /**
2374
2357
  * Indicates if the group should be excluded from higher-level aggregations.
2375
2358
  *
@@ -2380,6 +2363,40 @@ module.exports = (() => {
2380
2363
  return this._excluded;
2381
2364
  }
2382
2365
 
2366
+ /**
2367
+ * Sets the immediate parent group (allowing for calculation of relative
2368
+ * percentages).
2369
+ *
2370
+ * @public
2371
+ * @param {PortfolioGroup} group
2372
+ */
2373
+ setParentGroup(group) {
2374
+ assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
2375
+
2376
+ if (this._parentGroup !== null) {
2377
+ throw new Error('The parent group has already been set.');
2378
+ }
2379
+
2380
+ this._parentGroup = group;
2381
+ }
2382
+
2383
+ /**
2384
+ * Sets the nearest parent group for a portfolio (allowing for calculation
2385
+ * of relative percentages).
2386
+ *
2387
+ * @public
2388
+ * @param {PortfolioGroup} group
2389
+ */
2390
+ setPortfolioGroup(group) {
2391
+ assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
2392
+
2393
+ if (this._portfolioGroup !== null) {
2394
+ throw new Error('The portfolio group has already been set.');
2395
+ }
2396
+
2397
+ this._portfolioGroup = group;
2398
+ }
2399
+
2383
2400
  /**
2384
2401
  * Adds a new {@link PositionItem} to the group.
2385
2402
  *
@@ -2425,9 +2442,11 @@ module.exports = (() => {
2425
2442
  * Causes aggregated data to be recalculated using a new exchange rate.
2426
2443
  *
2427
2444
  * @public
2428
- * @param {Rate} rate
2445
+ * @param {Array.<Rate>} rate
2429
2446
  */
2430
- setForexRate(rate) {
2447
+ setForexRates(rates) {
2448
+ this._rates = rates;
2449
+
2431
2450
  if (!this._bypassCurrencyTranslation) {
2432
2451
  this.refresh();
2433
2452
  }
@@ -2456,24 +2475,6 @@ module.exports = (() => {
2456
2475
  }
2457
2476
  }
2458
2477
 
2459
- /**
2460
- * Stops (or starts) group-level aggregation calculations.
2461
- *
2462
- * @public
2463
- * @param {Boolean} value
2464
- */
2465
- setSuspended(value) {
2466
- assert.argumentIsRequired(value, 'value', Boolean);
2467
-
2468
- if (this._suspended !== value) {
2469
- this._suspended = value;
2470
-
2471
- if (!this._suspended) {
2472
- this.refresh();
2473
- }
2474
- }
2475
- }
2476
-
2477
2478
  /**
2478
2479
  * Updates the portfolio data. For example, a portfolio's name might change. This
2479
2480
  * function only affects {@link PositionLevelType.PORTFOLIO} groups.
@@ -2505,20 +2506,13 @@ module.exports = (() => {
2505
2506
  }
2506
2507
 
2507
2508
  /**
2508
- * Causes all aggregated data to be recalculated (assuming the group has not
2509
- * been suspended).
2509
+ * Causes all aggregated data to be recalculated.
2510
2510
  *
2511
2511
  * @public
2512
2512
  */
2513
2513
  refresh() {
2514
- if (this._suspended) {
2515
- return;
2516
- }
2517
-
2518
- const rates = this._container.getForexQuotes();
2519
-
2520
- calculateStaticData(this, rates);
2521
- calculatePriceData(this, rates, null, true);
2514
+ calculateStaticData(this, this._rates);
2515
+ calculatePriceData(this, this._rates, null, true);
2522
2516
  }
2523
2517
 
2524
2518
  /**
@@ -2528,7 +2522,7 @@ module.exports = (() => {
2528
2522
  * @public
2529
2523
  */
2530
2524
  refreshMarketPercent() {
2531
- calculateMarketPercent(this, this._container.getForexQuotes(), true);
2525
+ calculateMarketPercent(this, this._rates, this._parentGroup, this._portfolioGroup);
2532
2526
  }
2533
2527
 
2534
2528
  /**
@@ -2541,17 +2535,6 @@ module.exports = (() => {
2541
2535
  return this._items.length === 0;
2542
2536
  }
2543
2537
 
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
2538
  /**
2556
2539
  * Adds an observer for changes to the exclusion of the group
2557
2540
  * from higher level aggregations.
@@ -2606,10 +2589,10 @@ module.exports = (() => {
2606
2589
  this._dataFormat.currentPrice = null;
2607
2590
  }
2608
2591
 
2609
- calculatePriceData(this, this._container.getForexQuotes(), sender, false);
2592
+ calculatePriceData(this, this._rates, sender, false);
2610
2593
  });
2611
2594
 
2612
- let fundamentalBinding = item.registerFundamentalDataChangeHandler((data, sender) => {
2595
+ let fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
2613
2596
  if (this._single) {
2614
2597
  this._dataFormat.fundamental = data;
2615
2598
  } else {
@@ -2660,13 +2643,13 @@ module.exports = (() => {
2660
2643
  let newsBinding = Disposable.getEmpty();
2661
2644
 
2662
2645
  if (this._single) {
2663
- newsBinding = item.registerNewsExistsChangeHandler((exists, sender) => {
2646
+ newsBinding = item.registerNewsExistsChangeHandler((exists) => {
2664
2647
  this._dataActual.newsExists = exists;
2665
2648
  this._dataFormat.newsExists = exists;
2666
2649
  });
2667
2650
  }
2668
2651
 
2669
- this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio, sender) => {
2652
+ this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio) => {
2670
2653
  const descriptionSelector = this._definition.descriptionSelector;
2671
2654
 
2672
2655
  this._description = descriptionSelector(this._items[0]);
@@ -2731,10 +2714,6 @@ module.exports = (() => {
2731
2714
  }
2732
2715
 
2733
2716
  function calculateStaticData(group, rates) {
2734
- if (group.suspended) {
2735
- return;
2736
- }
2737
-
2738
2717
  const actual = group._dataActual;
2739
2718
  const format = group._dataFormat;
2740
2719
 
@@ -2831,10 +2810,6 @@ module.exports = (() => {
2831
2810
  }
2832
2811
 
2833
2812
  function calculatePriceData(group, rates, item, forceRefresh) {
2834
- if (group.suspended) {
2835
- return;
2836
- }
2837
-
2838
2813
  const currency = group.currency;
2839
2814
 
2840
2815
  const actual = group._dataActual;
@@ -2915,28 +2890,19 @@ module.exports = (() => {
2915
2890
 
2916
2891
  format.total = formatCurrency(actual.total, currency);
2917
2892
  format.totalNegative = actual.total.getIsNegative();
2918
-
2919
- calculateMarketPercent(group, rates, false);
2893
+
2920
2894
  calculateUnrealizedPercent(group);
2921
2895
  }
2922
2896
 
2923
- function calculateMarketPercent(group, rates, silent) {
2924
- return;
2925
-
2926
- if (group.suspended) {
2927
- return;
2928
- }
2929
-
2897
+ function calculateMarketPercent(group, rates, parentGroup, portfolioGroup) {
2930
2898
  const actual = group._dataActual;
2931
2899
  const format = group._dataFormat;
2932
2900
  const excluded = group._excluded;
2933
2901
 
2934
- const portfolioParent = group._container.getParentGroupForPortfolio(group);
2935
-
2936
2902
  const calculatePercent = (parent) => {
2937
2903
  let marketPercent;
2938
2904
 
2939
- if (parent !== null && !excluded) {
2905
+ if (parent && !excluded) {
2940
2906
  const parentData = parent._dataActual;
2941
2907
 
2942
2908
  if (parentData.marketAbsolute !== null && !parentData.marketAbsolute.getIsZero()) {
@@ -2959,14 +2925,15 @@ module.exports = (() => {
2959
2925
  return marketPercent;
2960
2926
  };
2961
2927
 
2962
- actual.marketPercent = calculatePercent(group._container.getParentGroup(group));
2928
+ actual.marketPercent = calculatePercent(parentGroup);
2963
2929
  format.marketPercent = formatPercent(actual.marketPercent, 2);
2964
2930
 
2965
- actual.marketPercentPortfolio = calculatePercent(group._container.getParentGroupForPortfolio(group));
2966
- format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
2967
-
2968
- if (!silent) {
2969
- group._marketPercentChangeEvent.fire(group);
2931
+ if (parentGroup === portfolioGroup) {
2932
+ actual.marketPercentPortfolio = actual.marketPercent;
2933
+ format.marketPercentPortfolio = format.marketPercent;
2934
+ } else {
2935
+ actual.marketPercentPortfolio = calculatePercent(portfolioGroup);
2936
+ format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
2970
2937
  }
2971
2938
  }
2972
2939