@barchart/portfolio-api-common 1.27.1 → 1.28.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.
@@ -14,6 +14,7 @@ module.exports = (() => {
14
14
  * @extends {Enum}
15
15
  * @param {String} code
16
16
  * @param {String} description
17
+ * @param {Boolean} unique
17
18
  * @param {Function} rangeCalculator
18
19
  * @param {Function} startDateCalculator
19
20
  * @param {Function} descriptionCalculator
@@ -91,7 +92,7 @@ module.exports = (() => {
91
92
  *
92
93
  * @public
93
94
  * @param {Day} date
94
- * @return {PositionSummaryRange[]}
95
+ * @returns {PositionSummaryRange[]}
95
96
  */
96
97
  getRangesFromDate(date) {
97
98
  assert.argumentIsRequired(date, 'date', Day, 'Day');
@@ -108,6 +109,7 @@ module.exports = (() => {
108
109
  * @public
109
110
  * @param {Day} date
110
111
  * @param {Number} periods
112
+ * @returns {PositionSummaryRange[]}
111
113
  */
112
114
  getPriorRanges(date, periods) {
113
115
  assert.argumentIsRequired(date, 'date', Day, 'Day');
@@ -138,6 +140,7 @@ module.exports = (() => {
138
140
  * A summary for a calendar year.
139
141
  *
140
142
  * @public
143
+ * @static
141
144
  * @returns {PositionSummaryFrame}
142
145
  */
143
146
  static get YEARLY() {
@@ -148,6 +151,7 @@ module.exports = (() => {
148
151
  * A summary for a quarter.
149
152
  *
150
153
  * @public
154
+ * @static
151
155
  * @returns {PositionSummaryFrame}
152
156
  */
153
157
  static get QUARTERLY() {
@@ -158,6 +162,7 @@ module.exports = (() => {
158
162
  * A summary for a calendar month.
159
163
  *
160
164
  * @public
165
+ * @static
161
166
  * @returns {PositionSummaryFrame}
162
167
  */
163
168
  static get MONTHLY() {
@@ -168,6 +173,7 @@ module.exports = (() => {
168
173
  * A summary the current year (to date).
169
174
  *
170
175
  * @public
176
+ * @static
171
177
  * @returns {PositionSummaryFrame}
172
178
  */
173
179
  static get YTD() {
@@ -175,6 +181,9 @@ module.exports = (() => {
175
181
  }
176
182
 
177
183
  /**
184
+ * Returns the {@link PositionSummaryFrame} instance that matches the code
185
+ * provided.
186
+ *
178
187
  * @public
179
188
  * @static
180
189
  * @param {String} code
@@ -214,10 +223,7 @@ module.exports = (() => {
214
223
  */
215
224
 
216
225
  function getRange(start, end) {
217
- return {
218
- start: start,
219
- end: end
220
- };
226
+ return { start, end };
221
227
  }
222
228
 
223
229
  function getYearlyRanges(transactions) {
@@ -87,6 +87,8 @@ module.exports = (() => {
87
87
  }, { });
88
88
 
89
89
  if (reportFrame) {
90
+ this._referenceDate = reportDate;
91
+
90
92
  this._currentSummaryFrame = reportFrame;
91
93
  this._currentSummaryRange = array.last(this._currentSummaryFrame.getPriorRanges(reportDate, 0));
92
94
 
@@ -95,6 +97,8 @@ module.exports = (() => {
95
97
 
96
98
  this._previousSummaryRanges.pop();
97
99
  } else {
100
+ this._referenceDate = Day.getToday();
101
+
98
102
  this._currentSummaryFrame = PositionSummaryFrame.YTD;
99
103
  this._currentSummaryRange = array.first(this._currentSummaryFrame.getRecentRanges(0));
100
104
 
@@ -507,7 +511,7 @@ module.exports = (() => {
507
511
  *
508
512
  * @public
509
513
  * @param {Object} position
510
- * @return {Boolean}
514
+ * @returns {Boolean}
511
515
  */
512
516
  getPositionLock(position) {
513
517
  assert.argumentIsRequired(position, 'position', Object);
@@ -542,7 +546,7 @@ module.exports = (() => {
542
546
  *
543
547
  * @public
544
548
  * @param {Object} position
545
- * @return {Boolean}
549
+ * @returns {Boolean}
546
550
  */
547
551
  getPositionCalculating(position) {
548
552
  assert.argumentIsRequired(position, 'position', Object);
@@ -583,7 +587,14 @@ module.exports = (() => {
583
587
 
584
588
  if (symbol) {
585
589
  const rate = Rate.fromPair(quote.lastPrice, symbol);
586
- const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
590
+
591
+ let index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
592
+
593
+ if (index < 0) {
594
+ const inverted = rate.invert();
595
+
596
+ index = this._forexQuotes.findIndex(existing => existing.formatPair() === inverted.formatPair());
597
+ }
587
598
 
588
599
  if (index < 0) {
589
600
  this._forexQuotes.push(rate);
@@ -591,13 +602,13 @@ module.exports = (() => {
591
602
  this._forexQuotes[index] = rate;
592
603
  }
593
604
 
594
- Object.keys(this._trees).forEach((key) => {
595
- this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false);
596
- });
597
-
598
605
  recalculatePercentages.call(this);
599
606
  }
600
607
  });
608
+
609
+ Object.keys(this._trees).forEach((key) => {
610
+ this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false);
611
+ });
601
612
  }
602
613
 
603
614
  if (positionQuotes.length !== 0 || forexQuotes.length !== 0) {
@@ -608,8 +619,9 @@ module.exports = (() => {
608
619
  /**
609
620
  * Returns current price for symbol provided.
610
621
  *
622
+ * @public
611
623
  * @param {String} symbol
612
- * @return {null|Number}
624
+ * @returns {null|Number}
613
625
  */
614
626
  getCurrentPrice(symbol) {
615
627
  assert.argumentIsRequired(symbol, 'symbol', String);
@@ -1096,7 +1108,7 @@ module.exports = (() => {
1096
1108
  const previousSummaries = this._summariesPrevious[ position.position ] || getSummaryArray(this._previousSummaryRanges);
1097
1109
 
1098
1110
  if (!requireCurrentSummary || currentSummary !== null) {
1099
- returnRef = new PositionItem(portfolio, position, currentSummary, previousSummaries, this._reporting);
1111
+ returnRef = new PositionItem(portfolio, position, currentSummary, previousSummaries, this._reporting, this._referenceDate);
1100
1112
  } else {
1101
1113
  returnRef = null;
1102
1114
  }
@@ -1158,4 +1170,4 @@ module.exports = (() => {
1158
1170
  }
1159
1171
 
1160
1172
  return PositionContainer;
1161
- })();
1173
+ })();
@@ -76,6 +76,7 @@ module.exports = (() => {
76
76
  this._dataFormat.invalid = false;
77
77
  this._dataFormat.locked = false;
78
78
  this._dataFormat.calculating = false;
79
+ this._dataFormat.expired = false;
79
80
  this._dataFormat.newsExists = false;
80
81
  this._dataFormat.quantity = null;
81
82
  this._dataFormat.quantityPrevious = null;
@@ -292,7 +293,7 @@ module.exports = (() => {
292
293
  }
293
294
 
294
295
  /**
295
- * Indicates if the group will only contain one {@link PositionItem} -- that is,
296
+ * Indicates if the group will only contain one {@link PositionItem} that is,
296
297
  * indicates if the group represents a single position.
297
298
  *
298
299
  * @public
@@ -585,7 +586,7 @@ module.exports = (() => {
585
586
  if (summary.count > 0) {
586
587
  averageFormat = formatPercent(new Decimal(summary.total / summary.count), 2, true);
587
588
  } else {
588
- averageFormat = '--';
589
+ averageFormat = '';
589
590
  }
590
591
 
591
592
  summary.averageFormat = averageFormat;
@@ -595,7 +596,7 @@ module.exports = (() => {
595
596
 
596
597
  return sums;
597
598
  }, fundamentalFields.reduce((sums, fieldName) => {
598
- sums[fieldName] = { total: 0, count: 0, averageFormat: '--' };
599
+ sums[fieldName] = { total: 0, count: 0, averageFormat: '' };
599
600
 
600
601
  return sums;
601
602
  }, { }));
@@ -876,6 +877,7 @@ module.exports = (() => {
876
877
  format.invalid = definition.type === PositionLevelType.POSITION && item.invalid;
877
878
  format.locked = definition.type === PositionLevelType.POSITION && item.data.locked;
878
879
  format.calculating = definition.type === PositionLevelType.POSITION && item.data.calculating;
880
+ format.expired = definition.type === PositionLevelType.POSITION && item.data.expired;
879
881
  }
880
882
 
881
883
  let portfolioType = null;
@@ -25,9 +25,10 @@ module.exports = (() => {
25
25
  * @param {Object} currentSummary
26
26
  * @param {Object[]} previousSummaries
27
27
  * @param {Boolean} reporting
28
+ * @param {Day} referenceDate
28
29
  */
29
30
  class PositionItem extends Disposable {
30
- constructor(portfolio, position, currentSummary, previousSummaries, reporting) {
31
+ constructor(portfolio, position, currentSummary, previousSummaries, reporting, referenceDate) {
31
32
  super();
32
33
 
33
34
  this._portfolio = portfolio;
@@ -107,6 +108,7 @@ module.exports = (() => {
107
108
  this._data.fundamental = { };
108
109
  this._data.calculating = getIsCalculating(position);
109
110
  this._data.locked = getIsLocked(position);
111
+ this._data.expired = getIsExpired(position, referenceDate);
110
112
 
111
113
  this._quoteChangedEvent = new Event(this);
112
114
  this._newsExistsChangedEvent = new Event(this);
@@ -505,6 +507,13 @@ module.exports = (() => {
505
507
 
506
508
  const data = item._data;
507
509
 
510
+ // 2023/11/28, BRI. Futures contracts do not have their value set to zero
511
+ // after expiration. At expiration, the contract would have been closed
512
+ // (but the price would not have been zero). On the other hand, option
513
+ // contracts can expire worthless and we attempt to represent that here.
514
+
515
+ const worthless = data.expired && (position.instrument.type === InstrumentType.EQUITY_OPTION || position.instrument.type === InstrumentType.FUTURE_OPTION);
516
+
508
517
  let market;
509
518
 
510
519
  if (position.instrument.type === InstrumentType.OTHER) {
@@ -512,7 +521,15 @@ module.exports = (() => {
512
521
  } else if (position.instrument.type === InstrumentType.CASH) {
513
522
  market = snapshot.open;
514
523
  } else {
515
- market = ValuationCalculator.calculate(position.instrument, price, snapshot.open) || snapshot.value;
524
+ let priceToUse;
525
+
526
+ if (worthless) {
527
+ priceToUse = Decimal.ZERO;
528
+ } else {
529
+ priceToUse = price;
530
+ }
531
+
532
+ market = ValuationCalculator.calculate(position.instrument, priceToUse, snapshot.open) || snapshot.value;
516
533
  }
517
534
 
518
535
  let marketChange;
@@ -565,7 +582,9 @@ module.exports = (() => {
565
582
  if (currentSummary && position.instrument.type !== InstrumentType.CASH) {
566
583
  let priceToUse;
567
584
 
568
- if (price) {
585
+ if (worthless) {
586
+ priceToUse = Decimal.ZERO;
587
+ } else if (price) {
569
588
  priceToUse = price;
570
589
  } else if (data.previousPrice) {
571
590
  priceToUse = new Decimal(data.previousPrice);
@@ -762,11 +781,29 @@ module.exports = (() => {
762
781
  }
763
782
 
764
783
  function getIsCalculating(position) {
765
- assert.argumentIsRequired(position, 'position', Object);
784
+ assert.argumentIsRequired(position, 'position');
766
785
 
767
786
  return is.object(position.system) && is.object(position.system.calculate) && is.number(position.system.calculate.processors) && position.system.calculate.processors > 0;
768
787
  }
769
788
 
789
+ function getIsExpired(position, day) {
790
+ assert.argumentIsRequired(position, 'position');
791
+
792
+ const type = position.instrument.type;
793
+
794
+ let expiration;
795
+
796
+ if (type === InstrumentType.FUTURE) {
797
+ expiration = position.instrument.future.expiration;
798
+ } else if (type === InstrumentType.FUTURE_OPTION || type === InstrumentType.EQUITY_OPTION) {
799
+ expiration = position.instrument.option.expiration;
800
+ } else {
801
+ expiration = null;
802
+ }
803
+
804
+ return expiration !== null && expiration.getIsBefore(day);
805
+ }
806
+
770
807
  function getSnapshot(position, currentSummary, reporting) {
771
808
  let snapshot;
772
809
 
@@ -37,7 +37,7 @@ module.exports = (() => {
37
37
  .then(() => {
38
38
  assert.argumentIsRequired(symbol, 'symbol', String);
39
39
 
40
- return promise.timeout(Gateway.invoke(getInstrumentLookupEndpoint(), {symbol}), MAXIMUM_WAIT_BEFORE_TIMEOUT_IN_MILLISECONDS, 'instrument lookup')
40
+ return promise.timeout(Gateway.invoke(instrumentLookupEndpoint, { symbol }), MAXIMUM_WAIT_BEFORE_TIMEOUT_IN_MILLISECONDS, 'instrument lookup')
41
41
  .catch((e) => {
42
42
  let message;
43
43
 
@@ -63,32 +63,18 @@ module.exports = (() => {
63
63
  }
64
64
  }
65
65
 
66
- function buildInstrumentLookupEndpoint(host) {
67
- return EndpointBuilder.for('query-instrument', 'query instrument')
68
- .withVerb(VerbType.GET)
69
- .withProtocol(ProtocolType.HTTPS)
70
- .withHost(host)
71
- .withPort(443)
72
- .withPathBuilder((pb) => {
73
- pb.withLiteralParameter('instruments', 'instruments')
74
- .withVariableParameter('symbol', 'symbol', 'symbol');
75
- })
76
- .withResponseInterceptor(ResponseInterceptor.DATA)
77
- .withErrorInterceptor(ErrorInterceptor.GENERAL)
78
- .endpoint;
79
- }
80
-
81
- const instrumentLookupEndpoints = new Map();
82
-
83
- function getInstrumentLookupEndpoint() {
84
- const host = 'instruments-prod.aws.barchart.com';
85
-
86
- if (!instrumentLookupEndpoints.has(host)) {
87
- instrumentLookupEndpoints.set(host, buildInstrumentLookupEndpoint(host));
88
- }
89
-
90
- return instrumentLookupEndpoints.get(host);
91
- }
66
+ const instrumentLookupEndpoint = EndpointBuilder.for('query-instrument', 'query instrument')
67
+ .withVerb(VerbType.GET)
68
+ .withProtocol(ProtocolType.HTTPS)
69
+ .withHost('instruments-prod.aws.barchart.com')
70
+ .withPort(443)
71
+ .withPathBuilder((pb) => {
72
+ pb.withLiteralParameter('instruments', 'instruments')
73
+ .withVariableParameter('symbol', 'symbol', 'symbol');
74
+ })
75
+ .withResponseInterceptor(ResponseInterceptor.DATA)
76
+ .withErrorInterceptor(ErrorInterceptor.GENERAL)
77
+ .endpoint;
92
78
 
93
79
  return InstrumentProvider;
94
80
  })();
@@ -13,7 +13,7 @@ module.exports = (() => {
13
13
  *
14
14
  * @public
15
15
  * @param {InstrumentProvider} provider
16
- * @param {Number} cacheDuration - The maximum number of milliseconds to cache an instrument.
16
+ * @param {Number=} cacheDuration - The maximum number of milliseconds to cache an instrument.
17
17
  */
18
18
  class InstrumentProviderCache {
19
19
  constructor(provider, cacheDuration) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.27.1",
3
+ "version": "1.28.0",
4
4
  "description": "Common JavaScript code used by Barchart's Portfolio Service",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",