@barchart/portfolio-api-common 1.0.262 → 1.0.266

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,18 +20,34 @@ module.exports = (() => {
20
20
  * @param {Function} descriptionCalculator
21
21
  */
22
22
  class PositionSummaryFrame extends Enum {
23
- constructor(code, description, rangeCalculator, startDateCalculator, descriptionCalculator) {
23
+ constructor(code, description, unique, rangeCalculator, startDateCalculator, descriptionCalculator) {
24
24
  super(code, description);
25
25
 
26
+ assert.argumentIsRequired(unique, 'unique', Boolean);
27
+
26
28
  assert.argumentIsRequired(rangeCalculator, 'rangeCalculator', Function);
27
29
  assert.argumentIsRequired(startDateCalculator, 'startDateCalculator', Function);
28
30
  assert.argumentIsRequired(descriptionCalculator, 'descriptionCalculator', Function);
29
31
 
32
+ this._unique = unique;
33
+
30
34
  this._rangeCalculator = rangeCalculator;
31
35
  this._startDateCalculator = startDateCalculator;
32
36
  this._descriptionCalculator = descriptionCalculator;
33
37
  }
34
38
 
39
+ /**
40
+ * If true, only one summary, of the given type, can exist for a
41
+ * position. If false, multiple summaries, of the given type, can
42
+ * exist for a position.
43
+ *
44
+ * @public
45
+ * @return {Boolean}
46
+ */
47
+ get unique() {
48
+ return this._unique;
49
+ }
50
+
35
51
  /**
36
52
  * Returns a human-readable description of the frame, given
37
53
  * start and end dates.
@@ -129,10 +145,10 @@ module.exports = (() => {
129
145
  }
130
146
  }
131
147
 
132
- const yearly = new PositionSummaryFrame('YEARLY', 'year', getYearlyRanges, getYearlyStartDate, getYearlyRangeDescription);
133
- const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', getQuarterlyRanges, getQuarterlyStartDate, getQuarterlyRangeDescription);
134
- const monthly = new PositionSummaryFrame('MONTH', 'month', getMonthlyRanges, getMonthlyStartDate, getMonthlyRangeDescription);
135
- const ytd = new PositionSummaryFrame('YTD', 'year-to-date', getYearToDateRanges, getYearToDateStartDate, getYearToDateRangeDescription);
148
+ const yearly = new PositionSummaryFrame('YEARLY', 'year', false, getYearlyRanges, getYearlyStartDate, getYearlyRangeDescription);
149
+ const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', false, getQuarterlyRanges, getQuarterlyStartDate, getQuarterlyRangeDescription);
150
+ const monthly = new PositionSummaryFrame('MONTH', 'month', false, getMonthlyRanges, getMonthlyStartDate, getMonthlyRangeDescription);
151
+ const ytd = new PositionSummaryFrame('YTD', 'year-to-date', true, getYearToDateRanges, getYearToDateStartDate, getYearToDateRangeDescription);
136
152
 
137
153
  /**
138
154
  * The start and and date for a {@link PositionSummaryFrame}
@@ -1,4 +1,5 @@
1
- const assert = require('@barchart/common-js/lang/assert');
1
+ const assert = require('@barchart/common-js/lang/assert'),
2
+ array = require('@barchart/common-js/lang/array')
2
3
 
3
4
  const InstrumentType = require('./InstrumentType'),
4
5
  PositionDirection = require('./PositionDirection'),
@@ -17,6 +18,31 @@ module.exports = (() => {
17
18
 
18
19
  }
19
20
 
21
+ /**
22
+ * Given a set of transaction, ensures that sequence numbers and dates
23
+ * are properly ordered.
24
+ *
25
+ * @public
26
+ * @static
27
+ * @param {Array.<Object>} transactions
28
+ * @param {Boolean=} partial - If true, sequence validation starts with the array's first transaction.
29
+ * @return {boolean}
30
+ */
31
+ static validateOrder(transactions, partial) {
32
+ assert.argumentIsArray(transactions, 'transactions');
33
+ assert.argumentIsOptional(partial, 'partial', Boolean);
34
+
35
+ let startSequence;
36
+
37
+ if (partial && transactions.length !== 0) {
38
+ startSequence = array.first(transactions).sequence;
39
+ } else {
40
+ startSequence = 1;
41
+ }
42
+
43
+ return transactions.every((t, i) => t.sequence === (i + startSequence) && (i === 0 || !t.date.getIsBefore(transactions[i - 1].date)));
44
+ }
45
+
20
46
  /**
21
47
  * Given an instrument type, returns all valid transaction types.
22
48
  *
@@ -324,6 +324,8 @@ module.exports = (() => {
324
324
  this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
325
325
  }
326
326
  });
327
+
328
+ recalculatePercentages.call(this);
327
329
  }
328
330
 
329
331
  /**
@@ -337,6 +339,8 @@ module.exports = (() => {
337
339
  assert.argumentIsRequired(position.position, 'position.position', String);
338
340
 
339
341
  removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
342
+
343
+ recalculatePercentages.call(this);
340
344
  }
341
345
 
342
346
  /**
@@ -384,6 +388,8 @@ module.exports = (() => {
384
388
  if (this._symbols.hasOwnProperty(symbol)) {
385
389
  this._symbols[symbol].forEach(item => item.setQuote(quote));
386
390
  }
391
+
392
+ recalculatePercentages.call(this);
387
393
  }
388
394
 
389
395
  /**
@@ -427,7 +433,9 @@ module.exports = (() => {
427
433
  this._forexQuotes[index] = rate;
428
434
  }
429
435
 
430
- Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
436
+ Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false));
437
+
438
+ recalculatePercentages.call(this);
431
439
  }
432
440
 
433
441
  /**
@@ -739,12 +747,6 @@ module.exports = (() => {
739
747
  }
740
748
  }
741
749
  }));
742
-
743
- addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
744
- if (!groupTree.getIsRoot()) {
745
- groupTree.getParent().walk((childGroup) => childGroup.refreshMarketPercent());
746
- }
747
- }));
748
750
  }
749
751
 
750
752
  function createGroups(parentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
@@ -752,6 +754,8 @@ module.exports = (() => {
752
754
  return;
753
755
  }
754
756
 
757
+ const rates = this._forexQuotes;
758
+
755
759
  const levelDefinition = levelDefinitions[0];
756
760
 
757
761
  const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
@@ -759,7 +763,7 @@ module.exports = (() => {
759
763
  const items = populatedObjects[key];
760
764
  const first = items[0];
761
765
 
762
- list.push(new PositionGroup(this, levelDefinition, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
766
+ list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
763
767
 
764
768
  return list;
765
769
  }, [ ]);
@@ -772,7 +776,7 @@ module.exports = (() => {
772
776
  });
773
777
 
774
778
  const empty = missingGroups.map((group) => {
775
- return new PositionGroup(this, levelDefinition, [ ], group.currency, group.key, group.description);
779
+ return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
776
780
  });
777
781
 
778
782
  const compositeGroups = populatedGroups.concat(empty);
@@ -812,6 +816,9 @@ module.exports = (() => {
812
816
 
813
817
  this._nodes[group.id] = childTree;
814
818
 
819
+ group.setParentGroup(this.getParentGroup(group));
820
+ group.setPortfolioGroup(this.getParentGroupForPortfolio(group));
821
+
815
822
  initializeGroupObservers.call(this, childTree, treeDefinition);
816
823
 
817
824
  createGroups.call(this, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
@@ -952,5 +959,11 @@ module.exports = (() => {
952
959
  groupNodeToSever.walk(group => delete this._nodes[group.id], false, true);
953
960
  }
954
961
 
962
+ function recalculatePercentages() {
963
+ Object.keys(this._trees).forEach((key) => {
964
+ this._trees[key].walk(group => group.refreshMarketPercent(), false, false);
965
+ });
966
+ }
967
+
955
968
  return PositionContainer;
956
969
  })();
@@ -33,13 +33,17 @@ module.exports = (() => {
33
33
  * @param {Boolean=} aggregateCash
34
34
  */
35
35
  class PositionGroup {
36
- constructor(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.262",
3
+ "version": "1.0.266",
4
4
  "description": "Common classes used by the Portfolio system",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",