@barchart/portfolio-api-common 1.32.0 → 2.1.0

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.
@@ -3,6 +3,7 @@ const array = require('@barchart/common-js/lang/array'),
3
3
  ComparatorBuilder = require('@barchart/common-js/collections/sorting/ComparatorBuilder'),
4
4
  comparators = require('@barchart/common-js/collections/sorting/comparators'),
5
5
  Currency = require('@barchart/common-js/lang/Currency'),
6
+ CurrencyTranslator = require('@barchart/common-js/lang/CurrencyTranslator'),
6
7
  Day = require('@barchart/common-js/lang/Day'),
7
8
  Decimal = require('@barchart/common-js/lang/Decimal'),
8
9
  DisposableStack = require('@barchart/common-js/collections/specialized/DisposableStack'),
@@ -23,14 +24,19 @@ const PositionGroup = require('./PositionGroup'),
23
24
  module.exports = (() => {
24
25
  'use strict';
25
26
 
26
- const DEFAULT_CURRENCY = Currency.CAD;
27
+ const DEFAULT_CURRENCY = Currency.USD;
27
28
 
28
- const REQUIRED_CURRENCIES = [
29
+ const SUPPORTED_CURRENCIES = [
30
+ Currency.AUD,
29
31
  Currency.CAD,
30
32
  Currency.CHF,
33
+ Currency.GBP,
34
+ Currency.GBX,
31
35
  Currency.EUR,
32
36
  Currency.HKD,
33
37
  Currency.JPY,
38
+ Currency.NOK,
39
+ Currency.SEK,
34
40
  Currency.USD
35
41
  ];
36
42
 
@@ -77,6 +83,7 @@ module.exports = (() => {
77
83
  this._groupBindings = { };
78
84
 
79
85
  this._reporting = reportFrame instanceof PositionSummaryFrame;
86
+ this._useBarchartPriceFormattingRules = false;
80
87
 
81
88
  this._positionSymbolAddedEvent = new Event(this);
82
89
  this._positionSymbolRemovedEvent = new Event(this);
@@ -122,7 +129,7 @@ module.exports = (() => {
122
129
  }, { });
123
130
 
124
131
  this._items = positions.reduce((items, position) => {
125
- const item = createPositionItem.call(this, position, reportFrame ? true : false);
132
+ const item = createPositionItem.call(this, position, !!reportFrame);
126
133
 
127
134
  if (item) {
128
135
  items.push(item);
@@ -143,22 +150,6 @@ module.exports = (() => {
143
150
  return map;
144
151
  }, { });
145
152
 
146
- this._currencies = this._items.reduce((map, item) => {
147
- const currency = extractCurrency(item.position);
148
-
149
- if (currency) {
150
- const code = currency.code;
151
-
152
- if (!map.hasOwnProperty(code)) {
153
- map[code] = [ ];
154
- }
155
-
156
- map[code].push(item);
157
- }
158
-
159
- return map;
160
- }, { });
161
-
162
153
  if (is.array(currencyPairs)) {
163
154
  currencyPairs.forEach((currencyPair) => {
164
155
  currencyPair.sort((a, b) => comparators.compareStrings(a.code, b.code));
@@ -168,21 +159,31 @@ module.exports = (() => {
168
159
  return `^${currencyPair[0].code}${currencyPair[1].code}`;
169
160
  }));
170
161
  } else {
171
- const forexCurrencyCodes = array.unique(Object.keys(this._currencies).concat(REQUIRED_CURRENCIES.map(c => c.code)));
172
-
173
- this._forexSymbols = forexCurrencyCodes.reduce((symbols, code) => {
174
- if (code !== DEFAULT_CURRENCY.code) {
175
- symbols.push(`^${code}${DEFAULT_CURRENCY.code}`);
162
+ this._forexSymbols = SUPPORTED_CURRENCIES.reduce((symbols, currency) => {
163
+ if (currency === DEFAULT_CURRENCY || currency === Currency.GBX) {
164
+ return symbols;
176
165
  }
177
166
 
167
+ symbols.push(`^${DEFAULT_CURRENCY.code}${currency.code}`);
168
+
178
169
  return symbols;
179
170
  }, [ ]);
171
+
172
+ this._forexSymbols.push('^GBXGBP');
180
173
  }
181
174
 
182
- this._forexQuotes = this._forexSymbols.map((symbol) => {
175
+ this._currencyTranslator = new CurrencyTranslator(this._forexSymbols);
176
+
177
+ const forexQuotes = this._forexSymbols.map((symbol) => {
178
+ if (symbol === '^GBXGBP') {
179
+ return Rate.fromPair(0.01, '^GBXGBP');
180
+ }
181
+
183
182
  return Rate.fromPair(Decimal.ONE, symbol);
184
183
  });
185
184
 
185
+ this._currencyTranslator.setRates(forexQuotes);
186
+
186
187
  this._nodes = { };
187
188
 
188
189
  this._trees = this._definitions.reduce((map, treeDefinition) => {
@@ -577,18 +578,6 @@ module.exports = (() => {
577
578
  assert.argumentIsArray(positionQuotes, 'positionQuotes');
578
579
  assert.argumentIsArray(forexQuotes, 'forexQuotes');
579
580
 
580
- if (positionQuotes.length !== 0) {
581
- positionQuotes.forEach((quote) => {
582
- const symbol = quote.symbol;
583
-
584
- if (symbol) {
585
- if (this._symbols.hasOwnProperty(symbol)) {
586
- this._symbols[ symbol ].forEach(item => item.setQuote(quote));
587
- }
588
- }
589
- });
590
- }
591
-
592
581
  if (forexQuotes.length !== 0) {
593
582
  forexQuotes.forEach((quote) => {
594
583
  const symbol = quote.symbol;
@@ -596,27 +585,25 @@ module.exports = (() => {
596
585
  if (symbol) {
597
586
  const rate = Rate.fromPair(quote.lastPrice, symbol);
598
587
 
599
- let index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
588
+ this._currencyTranslator.setRate(rate);
589
+ }
590
+ });
600
591
 
601
- if (index < 0) {
602
- const inverted = rate.invert();
592
+ Object.keys(this._trees).forEach((key) => {
593
+ this._trees[key].walk(group => group.refreshTranslations(), true, false);
594
+ });
595
+ }
603
596
 
604
- index = this._forexQuotes.findIndex(existing => existing.formatPair() === inverted.formatPair());
605
- }
597
+ if (positionQuotes.length !== 0) {
598
+ positionQuotes.forEach((quote) => {
599
+ const symbol = quote.symbol;
606
600
 
607
- if (index < 0) {
608
- this._forexQuotes.push(rate);
609
- } else {
610
- this._forexQuotes[index] = rate;
601
+ if (symbol) {
602
+ if (this._symbols.hasOwnProperty(symbol)) {
603
+ this._symbols[ symbol ].forEach(item => item.setQuote(quote));
611
604
  }
612
-
613
- recalculatePercentages.call(this);
614
605
  }
615
606
  });
616
-
617
- Object.keys(this._trees).forEach((key) => {
618
- this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false);
619
- });
620
607
  }
621
608
 
622
609
  if (positionQuotes.length !== 0 || forexQuotes.length !== 0) {
@@ -645,18 +632,6 @@ module.exports = (() => {
645
632
  return price;
646
633
  }
647
634
 
648
- /**
649
- * Sets a historical forex quote.
650
- *
651
- * @public
652
- * @param {Object} forexQuote
653
- * @param {Day} date
654
- */
655
- setHistoricalForexQuote(forexQuote, date) {
656
- assert.argumentIsRequired(forexQuote, 'forexQuote', Object);
657
- assert.argumentIsRequired(date, 'date', Day, 'Day');
658
- }
659
-
660
635
  /**
661
636
  * Returns all forex symbols that are required to do currency translations.
662
637
  *
@@ -667,16 +642,6 @@ module.exports = (() => {
667
642
  return this._forexSymbols;
668
643
  }
669
644
 
670
- /**
671
- * Returns all current forex quotes.
672
- *
673
- * @public
674
- * @returns {Object[]}
675
- */
676
- getForexQuotes() {
677
- return this._forexQuotes;
678
- }
679
-
680
645
  /**
681
646
  * Updates fundamental data for a single symbol.
682
647
  *
@@ -851,6 +816,24 @@ module.exports = (() => {
851
816
  return this._positionSymbolRemovedEvent.register(handler);
852
817
  }
853
818
 
819
+ /**
820
+ * Changes rules for price formatting.
821
+ *
822
+ * @public
823
+ * @param {boolean} value
824
+ */
825
+ setBarchartPriceFormattingRules(value) {
826
+ assert.argumentIsRequired(value, 'value', Boolean);
827
+
828
+ if (this._useBarchartPriceFormattingRules !== value) {
829
+ this._useBarchartPriceFormattingRules = value;
830
+
831
+ Object.keys(this._trees).forEach((key) => {
832
+ this._trees[key].walk(group => group.setBarchartPriceFormattingRules(this._useBarchartPriceFormattingRules));
833
+ });
834
+ }
835
+ }
836
+
854
837
  toString() {
855
838
  return '[PositionContainer]';
856
839
  }
@@ -892,14 +875,6 @@ module.exports = (() => {
892
875
  }
893
876
  }
894
877
 
895
- function extractCurrency(position) {
896
- if (position.instrument && position.instrument.currency) {
897
- return position.instrument.currency;
898
- } else {
899
- return null;
900
- }
901
- }
902
-
903
878
  function addGroupBinding(group, dispoable) {
904
879
  const id = group.id;
905
880
 
@@ -963,7 +938,7 @@ module.exports = (() => {
963
938
  return;
964
939
  }
965
940
 
966
- const rates = this._forexQuotes;
941
+ const currencyTranslator = this._currencyTranslator;
967
942
 
968
943
  const levelDefinition = levelDefinitions[0];
969
944
 
@@ -972,7 +947,11 @@ module.exports = (() => {
972
947
  const items = populatedObjects[key];
973
948
  const first = items[0];
974
949
 
975
- list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
950
+ const group = new PositionGroup(levelDefinition, items, levelDefinition.currencySelector(first), currencyTranslator, key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash);
951
+
952
+ group.setBarchartPriceFormattingRules(this._useBarchartPriceFormattingRules);
953
+
954
+ list.push(group);
976
955
 
977
956
  return list;
978
957
  }, [ ]);
@@ -985,7 +964,11 @@ module.exports = (() => {
985
964
  });
986
965
 
987
966
  const empty = missingGroups.map((group) => {
988
- return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
967
+ const eg = new PositionGroup(levelDefinition, [ ], group.currency, currencyTranslator, group.key, group.description);
968
+
969
+ eg.setBarchartPriceFormattingRules(this._useBarchartPriceFormattingRules);
970
+
971
+ return eg;
989
972
  });
990
973
 
991
974
  const compositeGroups = populatedGroups.concat(empty);
@@ -1149,12 +1132,6 @@ module.exports = (() => {
1149
1132
  array.remove(this._symbolsDisplay[displaySymbol], i => i === positionItem);
1150
1133
  }
1151
1134
 
1152
- const currency = extractCurrency(positionItem.position);
1153
-
1154
- if (currency && this._currencies.hasOwnProperty(currency.code)) {
1155
- array.remove(this._currencies[currency.code], i => i === positionItem);
1156
- }
1157
-
1158
1135
  Object.keys(this._trees).forEach((key) => {
1159
1136
  this._trees[key].walk((group, groupNode) => {
1160
1137
  if (group.definition.type === PositionLevelType.POSITION && group.key === positionItem.position.position) {
@@ -1,13 +1,13 @@
1
1
  const array = require('@barchart/common-js/lang/array'),
2
2
  assert = require('@barchart/common-js/lang/assert'),
3
3
  Currency = require('@barchart/common-js/lang/Currency'),
4
+ CurrencyTranslator = require('@barchart/common-js/lang/CurrencyTranslator'),
4
5
  Decimal = require('@barchart/common-js/lang/Decimal'),
5
6
  Disposable = require('@barchart/common-js/lang/Disposable'),
6
7
  DisposableStack = require('@barchart/common-js/collections/specialized/DisposableStack'),
7
8
  Event = require('@barchart/common-js/messaging/Event'),
8
9
  formatter = require('@barchart/common-js/lang/formatter'),
9
- is = require('@barchart/common-js/lang/is'),
10
- Rate = require('@barchart/common-js/lang/Rate');
10
+ is = require('@barchart/common-js/lang/is');
11
11
 
12
12
  const fractionFormatter = require('@barchart/marketdata-api-js/lib/utilities/format/fraction');
13
13
 
@@ -28,27 +28,29 @@ module.exports = (() => {
28
28
  * @public
29
29
  * @param {PositionLevelDefinition} definition
30
30
  * @param {PositionItem[]} items
31
- * @param {Rate[]} rates
32
31
  * @param {Currency} currency
32
+ * @param {CurrencyTranslator} currencyTranslator
33
33
  * @param {String} key
34
34
  * @param {String} description
35
35
  * @param {Boolean=} aggregateCash
36
36
  */
37
37
  class PositionGroup {
38
- constructor(definition, items, rates, currency, key, description, aggregateCash) {
38
+ constructor(definition, items, currency, currencyTranslator, key, description, aggregateCash) {
39
39
  this._id = counter++;
40
40
 
41
41
  this._definition = definition;
42
42
 
43
43
  this._items = items;
44
- this._rates = rates;
45
44
 
46
45
  this._parentGroup = null;
47
46
  this._portfolioGroup = null;
48
47
 
49
48
  this._currency = currency || Currency.CAD;
49
+ this._currencyTranslator = currencyTranslator;
50
50
  this._bypassCurrencyTranslation = false;
51
51
 
52
+ this._useBarchartPriceFormattingRules = false;
53
+
52
54
  this._key = key;
53
55
  this._description = description;
54
56
 
@@ -404,20 +406,6 @@ module.exports = (() => {
404
406
  this.refresh();
405
407
  }
406
408
 
407
- /**
408
- * Causes aggregated data to be recalculated using a new exchange rate.
409
- *
410
- * @public
411
- * @param {Rate[]} rates
412
- */
413
- setForexRates(rates) {
414
- this._rates = rates;
415
-
416
- if (!this._bypassCurrencyTranslation) {
417
- this.refresh();
418
- }
419
- }
420
-
421
409
  /**
422
410
  * Set a flag to indicate if parent groups should exclude this group's
423
411
  * items from their calculations.
@@ -477,8 +465,8 @@ module.exports = (() => {
477
465
  * @public
478
466
  */
479
467
  refresh() {
480
- calculateStaticData(this, this._rates, this._definition);
481
- calculatePriceData(this, this._rates, null, true);
468
+ calculateStaticData(this, this._definition);
469
+ calculatePriceData(this,null, true);
482
470
  }
483
471
 
484
472
  /**
@@ -488,7 +476,20 @@ module.exports = (() => {
488
476
  * @public
489
477
  */
490
478
  refreshMarketPercent() {
491
- calculateMarketPercent(this, this._rates, this._parentGroup, this._portfolioGroup);
479
+ calculateMarketPercent(this, this._parentGroup, this._portfolioGroup);
480
+ }
481
+
482
+ /**
483
+ * Causes aggregated data to be recalculated using a new exchange rate.
484
+ *
485
+ * @public
486
+ */
487
+ refreshTranslations() {
488
+ if (this._bypassCurrencyTranslation) {
489
+ return;
490
+ }
491
+
492
+ this.refresh();
492
493
  }
493
494
 
494
495
  /**
@@ -513,6 +514,36 @@ module.exports = (() => {
513
514
  return this._groupExcludedChangeEvent.register(handler);
514
515
  }
515
516
 
517
+ /**
518
+ * Changes rules for price formatting.
519
+ *
520
+ * @public
521
+ * @param {boolean} value
522
+ */
523
+ setBarchartPriceFormattingRules(value) {
524
+ assert.argumentIsRequired(value, 'value', Boolean);
525
+
526
+ if (this._useBarchartPriceFormattingRules !== value) {
527
+ this._useBarchartPriceFormattingRules = value;
528
+
529
+ if (this._single && this._dataActual.currentPrice) {
530
+ const item = this._items[0];
531
+
532
+ const instrument = item.position.instrument;
533
+ const currency = instrument.currency;
534
+
535
+ this._dataFormat.currentPrice = formatFraction(this._dataActual.currentPrice, currency, instrument, this._useBarchartPriceFormattingRules);
536
+
537
+ this._dataFormat.quoteLast = formatFraction(this._dataActual.quoteLast, currency, instrument, this._useBarchartPriceFormattingRules);
538
+ this._dataFormat.quoteOpen = formatFraction(this._dataActual.quoteOpen, currency, instrument, this._useBarchartPriceFormattingRules);
539
+ this._dataFormat.quoteHigh = formatFraction(this._dataActual.quoteHigh, currency, instrument, this._useBarchartPriceFormattingRules);
540
+ this._dataFormat.quoteLow = formatFraction(this._dataActual.quoteLow, currency, instrument, this._useBarchartPriceFormattingRules);
541
+
542
+ this._dataFormat.quoteChange = formatFraction(this._dataActual.quoteChange, currency, instrument, this._useBarchartPriceFormattingRules);
543
+ }
544
+ }
545
+ }
546
+
516
547
  toString() {
517
548
  return '[PositionGroup]';
518
549
  }
@@ -523,27 +554,28 @@ module.exports = (() => {
523
554
  if (this._single) {
524
555
  const instrument = sender.position.instrument;
525
556
  const currency = instrument.currency;
526
- const precision = currency.precision;
527
557
 
528
- this._dataActual.currentPrice = quote.lastPrice;
529
- this._dataFormat.currentPrice = formatFraction(this._dataActual.currentPrice, currency, instrument);
558
+ this._dataActual.currentPrice = is.number(quote.lastPrice) ? quote.lastPrice : null;
559
+ this._dataActual.quoteLast = is.number(quote.previousPrice) ? quote.previousPrice : null;
560
+ this._dataActual.quoteOpen = is.number(quote.openPrice) ? quote.openPrice : null;
561
+ this._dataActual.quoteHigh = is.number(quote.highPrice) ? quote.highPrice : null;
562
+ this._dataActual.quoteLow = is.number(quote.lowPrice) ? quote.lowPrice : null;
530
563
 
531
- this._dataActual.quoteLast = quote.previousPrice;
532
- this._dataActual.quoteOpen = quote.openPrice;
533
- this._dataActual.quoteHigh = quote.highPrice;
534
- this._dataActual.quoteLow = quote.lowPrice;
535
- this._dataActual.quoteChange = quote.priceChange;
536
- this._dataActual.quoteChangePercent = quote.percentChange;
537
- this._dataActual.quoteTime = quote.timeDisplay;
538
- this._dataActual.quoteVolume = quote.volume;
564
+ this._dataFormat.currentPrice = formatFraction(this._dataActual.currentPrice, currency, instrument, this._useBarchartPriceFormattingRules);
565
+ this._dataFormat.quoteLast = formatFraction(this._dataActual.quoteLast, currency, instrument, this._useBarchartPriceFormattingRules);
566
+ this._dataFormat.quoteOpen = formatFraction(this._dataActual.quoteOpen, currency, instrument, this._useBarchartPriceFormattingRules);
567
+ this._dataFormat.quoteHigh = formatFraction(this._dataActual.quoteHigh, currency, instrument, this._useBarchartPriceFormattingRules);
568
+ this._dataFormat.quoteLow = formatFraction(this._dataActual.quoteLow, currency, instrument, this._useBarchartPriceFormattingRules);
539
569
 
540
- this._dataFormat.quoteLast = formatNumber(this._dataActual.quoteLast , precision);
541
- this._dataFormat.quoteOpen = formatNumber(this._dataActual.quoteOpen, precision);
542
- this._dataFormat.quoteHigh = formatNumber(this._dataActual.quoteHigh, precision);
543
- this._dataFormat.quoteLow = formatNumber(this._dataActual.quoteLow, precision);
544
- this._dataFormat.quoteChange = formatFraction(this._dataActual.quoteChange, currency, instrument);
570
+ this._dataActual.quoteChange = is.number(quote.priceChange) ? quote.priceChange : null;
571
+ this._dataActual.quoteChangePercent = is.number(quote.percentChange) ? quote.percentChange : null;
545
572
 
573
+ this._dataFormat.quoteChange = formatFraction(this._dataActual.quoteChange, currency, instrument, this._useBarchartPriceFormattingRules);
546
574
  this._dataFormat.quoteChangePercent = formatPercent(new Decimal(this._dataActual.quoteChangePercent || 0), 2);
575
+
576
+ this._dataActual.quoteTime = quote.timeDisplay;
577
+ this._dataActual.quoteVolume = is.number(quote.volume) ? quote.volume : null;
578
+
547
579
  this._dataFormat.quoteTime = this._dataActual.quoteTime;
548
580
  this._dataFormat.quoteVolume = formatNumber(this._dataActual.quoteVolume, 0);
549
581
 
@@ -558,7 +590,7 @@ module.exports = (() => {
558
590
  this._dataFormat.currentPrice = null;
559
591
  }
560
592
 
561
- calculatePriceData(this, this._rates, sender, false);
593
+ calculatePriceData(this, sender, false);
562
594
  });
563
595
 
564
596
  let fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
@@ -668,9 +700,43 @@ module.exports = (() => {
668
700
  }));
669
701
  }
670
702
 
671
- function formatFraction(value, currency, instrument) {
703
+ function formatNumber(number, precision) {
704
+ if (!is.number(number)) {
705
+ return '—';
706
+ }
707
+
708
+ return formatter.numberToString(number, precision, ',', false);
709
+ }
710
+
711
+ function formatDecimal(decimal, precision) {
712
+ if (decimal === null) {
713
+ return '—';
714
+ }
715
+
716
+ return formatNumber(decimal.toFloat(), precision);
717
+ }
718
+
719
+ function formatPercent(decimal, precision, plus) {
720
+ if (decimal === null) {
721
+ return '—';
722
+ }
723
+
724
+ let prefix;
725
+
726
+ if (is.boolean(plus) && plus && !Decimal.getIsNegative(decimal)) {
727
+ prefix = '+';
728
+ } else {
729
+ prefix = '';
730
+ }
731
+
732
+ return `${prefix}${formatDecimal(decimal.multiply(100), precision)}%`;
733
+ }
734
+
735
+ function formatFraction(value, currency, instrument, useBarchartPriceFormattingRules) {
672
736
  let decimal = value instanceof Decimal;
673
737
 
738
+ let precision = currency.precision;
739
+
674
740
  if (instrument && value !== null) {
675
741
  const type = instrument.type;
676
742
  const code = instrument.code;
@@ -680,101 +746,66 @@ module.exports = (() => {
680
746
 
681
747
  return fractionFormatter(rounded, code.fractionFactor, code.fractionDigits, '-', true);
682
748
  }
749
+
750
+ if (code && useBarchartPriceFormattingRules) {
751
+ precision = code.decimalDigits;
752
+ }
683
753
  }
684
754
 
685
755
  if (decimal) {
686
- return formatDecimal(value, currency.precision);
756
+ return formatDecimal(value, precision);
687
757
  } else {
688
- return formatNumber(value, currency.precision);
758
+ return formatNumber(value, precision);
689
759
  }
690
760
  }
691
761
 
692
- function formatFractionAndTranslate(value, currency, instrument) {
693
- let translatedCurrency;
694
- let translatedValue;
762
+ function formatCurrency(decimal, currency) {
763
+ let translated = decimal;
764
+ let desired = currency;
695
765
 
696
- if (currency === Currency.GBX) {
697
- translatedCurrency = Currency.GBP;
766
+ if (desired === Currency.GBX) {
767
+ desired = Currency.GBP;
698
768
 
699
- if (value instanceof Decimal) {
700
- translatedValue = Rate.convert(value, Currency.GBX, Currency.GBP);
701
- } else if (is.number(value)) {
702
- translatedValue = Rate.convert(new Decimal(value), Currency.GBX, Currency.GBP).toFloat();
703
- } else {
704
- translatedValue = value;
769
+ if (translated !== null) {
770
+ translated = translated.multiply(0.01);
705
771
  }
706
- } else {
707
- translatedCurrency = currency;
708
- translatedValue = value;
709
772
  }
710
773
 
711
- return formatFraction(translatedValue, translatedCurrency, instrument);
774
+ return formatDecimal(translated, desired.precision);
712
775
  }
713
776
 
714
- function formatNumber(number, precision) {
715
- if (is.number(number)) {
716
- return formatter.numberToString(number, precision, ',', false);
717
- } else {
718
- return '—';
719
- }
720
- }
777
+ function formatFractionSpecial(value, currency, instrument) {
778
+ let translated = value;
779
+ let desired = currency;
721
780
 
722
- function formatDecimal(decimal, precision) {
723
- if (decimal !== null) {
724
- return formatNumber(decimal.toFloat(), precision);
725
- } else {
726
- return '—';
727
- }
728
- }
729
-
730
- function formatPercent(decimal, precision, plus) {
731
- if (decimal !== null) {
732
- let prefix;
781
+ if (desired === Currency.GBX) {
782
+ desired = Currency.GBP;
733
783
 
734
- if (is.boolean(plus) && plus && !Decimal.getIsNegative(decimal)) {
735
- prefix = '+';
736
- } else {
737
- prefix = '';
784
+ if (translated !== null) {
785
+ translated = translated.multiply(0.01);
738
786
  }
739
-
740
- return `${prefix}${formatDecimal(decimal.multiply(100), precision)}%`;
741
- } else {
742
- return '—';
743
787
  }
744
- }
745
-
746
- function formatCurrency(decimal, currency) {
747
- let formatted;
748
788
 
749
- if (decimal === null || currency !== Currency.GBX) {
750
- formatted = formatDecimal(decimal, currency.precision);
751
- } else {
752
- formatted = formatDecimal(Rate.convert(decimal, Currency.GBX, Currency.GBP), Currency.GBP.precision);
753
- }
754
-
755
- return formatted;
789
+ return formatFraction(translated, desired, instrument);
756
790
  }
757
791
 
758
- function calculateStaticData(group, rates, definition) {
792
+ function calculateStaticData(group, definition) {
759
793
  const actual = group._dataActual;
760
794
  const format = group._dataFormat;
761
795
 
762
796
  const currency = group.currency;
797
+ const currencyTranslator = group._currencyTranslator;
763
798
 
764
799
  const items = group._consideredItems;
765
800
 
766
801
  group._bypassCurrencyTranslation = items.every(item => item.currency === currency);
767
802
 
768
803
  const translate = (item, value) => {
769
- let translated;
770
-
771
- if (item.currency !== currency && !value.getIsZero()) {
772
- translated = Rate.convert(value, item.currency, currency, ...rates);
773
- } else {
774
- translated = value;
804
+ if (item.currency === currency || value.getIsZero()) {
805
+ return value;
775
806
  }
776
807
 
777
- return translated;
808
+ return currencyTranslator.translate(value, item.currency, currency);
778
809
  };
779
810
 
780
811
  let updates = items.reduce((updates, item) => {
@@ -893,7 +924,7 @@ module.exports = (() => {
893
924
  format.quantityPrevious = formatDecimal(actual.quantityPrevious, 2);
894
925
 
895
926
  actual.basisPrice = item.data.basisPrice;
896
- format.basisPrice = formatFractionAndTranslate(actual.basisPrice, currency, instrument);
927
+ format.basisPrice = formatFractionSpecial(actual.basisPrice, currency, instrument);
897
928
 
898
929
  actual.periodPrice = item.data.periodPrice;
899
930
  actual.periodPricePrevious = item.data.periodPricePrevious;
@@ -902,7 +933,7 @@ module.exports = (() => {
902
933
  format.periodPricePrevious = formatCurrency(actual.periodPricePrevious, currency);
903
934
 
904
935
  actual.unrealizedPrice = item.data.unrealizedPrice;
905
- format.unrealizedPrice = formatFractionAndTranslate(actual.unrealizedPrice, currency, instrument);
936
+ format.unrealizedPrice = formatFractionSpecial(actual.unrealizedPrice, currency, instrument);
906
937
 
907
938
  format.invalid = definition.type === PositionLevelType.POSITION && item.invalid;
908
939
  format.locked = definition.type === PositionLevelType.POSITION && item.data.locked;
@@ -925,7 +956,7 @@ module.exports = (() => {
925
956
  format.portfolioType = portfolioType;
926
957
  }
927
958
 
928
- function calculatePriceData(group, rates, item, forceRefresh) {
959
+ function calculatePriceData(group, item, forceRefresh) {
929
960
  const currency = group.currency;
930
961
 
931
962
  const actual = group._dataActual;
@@ -937,16 +968,14 @@ module.exports = (() => {
937
968
  return;
938
969
  }
939
970
 
940
- const translate = (item, value) => {
941
- let translated;
971
+ const currencyTranslator = group._currencyTranslator;
942
972
 
943
- if (item.currency !== currency && !value.getIsZero()) {
944
- translated = Rate.convert(value, item.currency, currency, ...rates);
945
- } else {
946
- translated = value;
973
+ const translate = (item, value) => {
974
+ if (item.currency === currency || value.getIsZero()) {
975
+ return value;
947
976
  }
948
977
 
949
- return translated;
978
+ return currencyTranslator.translate(value, item.currency, currency);
950
979
  };
951
980
 
952
981
  let updates;
@@ -1062,39 +1091,37 @@ module.exports = (() => {
1062
1091
 
1063
1092
  if (group.single && item) {
1064
1093
  actual.unrealizedPrice = item.data.unrealizedPrice;
1065
- format.unrealizedPrice = formatFractionAndTranslate(actual.unrealizedPrice, currency, item.position.instrument);
1094
+ format.unrealizedPrice = formatFractionSpecial(actual.unrealizedPrice, currency, item.position.instrument);
1066
1095
  }
1067
1096
  }
1068
1097
 
1069
- function calculateMarketPercent(group, rates, parentGroup, portfolioGroup) {
1098
+ function calculateMarketPercent(group, parentGroup, portfolioGroup) {
1070
1099
  const actual = group._dataActual;
1071
1100
  const format = group._dataFormat;
1072
1101
  const excluded = group._excluded;
1073
1102
 
1103
+ const currencyTranslator = group._currencyTranslator;
1104
+
1074
1105
  const calculatePercent = (parent) => {
1075
- let marketPercent;
1106
+ if (!parent || excluded) {
1107
+ return null;
1108
+ }
1076
1109
 
1077
- if (parent && !excluded) {
1078
- const parentData = parent._dataActual;
1110
+ const parentData = parent._dataActual;
1079
1111
 
1080
- if (parentData.marketAbsolute !== null && !parentData.marketAbsolute.getIsApproximate(Decimal.ZERO, 4)) {
1081
- let numerator;
1112
+ if (parentData.marketAbsolute === null || parentData.marketAbsolute.getIsApproximate(Decimal.ZERO, 4)) {
1113
+ return null;
1114
+ }
1082
1115
 
1083
- if (group.currency !== parent.currency) {
1084
- numerator = Rate.convert(actual.marketAbsolute, group.currency, parent.currency, ...rates);
1085
- } else {
1086
- numerator = actual.marketAbsolute;
1087
- }
1116
+ let numerator;
1088
1117
 
1089
- marketPercent = numerator.divide(parentData.marketAbsolute);
1090
- } else {
1091
- marketPercent = null;
1092
- }
1118
+ if (group.currency === parent.currency) {
1119
+ numerator = actual.marketAbsolute;
1093
1120
  } else {
1094
- marketPercent = null;
1121
+ numerator = currencyTranslator.translate(actual.marketAbsolute, group.currency, parent.currency);
1095
1122
  }
1096
1123
 
1097
- return marketPercent;
1124
+ return numerator.divide(parentData.marketAbsolute);
1098
1125
  };
1099
1126
 
1100
1127
  actual.marketPercent = calculatePercent(parentGroup);
@@ -224,7 +224,7 @@ module.exports = (() => {
224
224
  .withField('type', DataType.forEnum(TransactionType, 'TransactionType'))
225
225
  .withField('instrument.name', DataType.STRING, true)
226
226
  .withField('instrument.exchange', DataType.STRING, true)
227
- .withField('instrument.code', DataType.NUMBER, true)
227
+ .withField('instrument.code', DataType.NUMBER, true) // Not intended to be the unit code. Same value as [profile] table [type] column. See `InstrumentType.fromSymbolType` function.
228
228
  .withField('instrument.type', DataType.forEnum(InstrumentType, 'InstrumentType'), true)
229
229
  .withField('instrument.currency', DataType.forEnum(Currency, 'Currency'), true)
230
230
  .withField('instrument.symbol.barchart', DataType.STRING, true)
@@ -274,7 +274,7 @@ module.exports = (() => {
274
274
  .withField('type', DataType.forEnum(TransactionType, 'TransactionType'))
275
275
  .withField('instrument.name', DataType.STRING, true)
276
276
  .withField('instrument.exchange', DataType.STRING, true)
277
- .withField('instrument.code', DataType.NUMBER, true)
277
+ .withField('instrument.code', DataType.NUMBER, true) // Not intended to be the unit code. Same value as [profile] table [type] column. See `InstrumentType.fromSymbolType` function.
278
278
  .withField('instrument.type', DataType.forEnum(InstrumentType, 'InstrumentType'), true)
279
279
  .withField('instrument.currency', DataType.forEnum(Currency, 'Currency'), true)
280
280
  .withField('instrument.symbol.barchart', DataType.STRING, true)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.32.0",
3
+ "version": "2.1.0",
4
4
  "description": "Common JavaScript code used by Barchart's Portfolio Service",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",
@@ -15,9 +15,9 @@
15
15
  "url": "git+ssh://git@github.com/barchart/portfolio-api-common.git"
16
16
  },
17
17
  "dependencies": {
18
- "@barchart/common-js": "^4.43.0",
18
+ "@barchart/common-js": "^4.51.0",
19
19
  "@barchart/marketdata-api-js": "^6.2.1",
20
- "uuid": "^8.3.2"
20
+ "uuid": "9.0.1"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@babel/core": "^7.6.2",