@barchart/portfolio-api-common 5.0.0 → 6.0.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.
@@ -92,6 +92,8 @@ module.exports = (() => {
92
92
  this._positionSymbolAddedEvent = new Event(this);
93
93
  this._positionSymbolRemovedEvent = new Event(this);
94
94
 
95
+ this._exchanges = { };
96
+
95
97
  this._portfolios = portfolios.reduce((map, portfolio) => {
96
98
  map[portfolio.portfolio] = portfolio;
97
99
 
@@ -99,7 +101,7 @@ module.exports = (() => {
99
101
  }, { });
100
102
 
101
103
  if (reportFrame) {
102
- this._referenceDate = reportDate;
104
+ this._reportDate = reportDate;
103
105
 
104
106
  this._currentSummaryFrame = reportFrame;
105
107
  this._currentSummaryRange = array.last(this._currentSummaryFrame.getPriorRanges(reportDate, 0));
@@ -109,7 +111,7 @@ module.exports = (() => {
109
111
 
110
112
  this._previousSummaryRanges.pop();
111
113
  } else {
112
- this._referenceDate = Day.getToday();
114
+ this._reportDate = null;
113
115
 
114
116
  this._currentSummaryFrame = PositionSummaryFrame.YTD;
115
117
  this._currentSummaryRange = array.first(this._currentSummaryFrame.getRecentRanges(0));
@@ -374,12 +376,24 @@ module.exports = (() => {
374
376
 
375
377
  const existingBarchartSymbols = this.getPositionSymbols(false);
376
378
 
377
- let similarPositionItem;
379
+ let exchange;
380
+
381
+ if (extractExchangeCode(position)) {
382
+ const code = extractExchangeCode(position);
383
+
384
+ exchange = this._exchanges[code] || null;
385
+ }
386
+
387
+ let currentQuote = null;
388
+ let previousQuote = null;
378
389
 
379
390
  if (extractSymbolForBarchart(position)) {
380
- similarPositionItem = this._items.find(item => extractSymbolForBarchart(item.position) === extractSymbolForBarchart(position)) || null;
381
- } else {
382
- similarPositionItem = null;
391
+ const similarPositionItem = this._items.find(item => extractSymbolForBarchart(item.position) === extractSymbolForBarchart(position)) || null;
392
+
393
+ if (similarPositionItem !== null) {
394
+ currentQuote = similarPositionItem.quote || null;
395
+ previousQuote = similarPositionItem.previousQuote || null;
396
+ }
383
397
  }
384
398
 
385
399
  removePositionItem.call(this, this._items.find(item => item.position.position === position.position));
@@ -429,14 +443,16 @@ module.exports = (() => {
429
443
  this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
430
444
  }
431
445
 
432
- if (similarPositionItem !== null) {
433
- if (similarPositionItem.previousQuote) {
434
- item.setQuote(similarPositionItem.previousQuote);
435
- }
446
+ if (exchange) {
447
+ item.setExchangeStatus(exchange);
448
+ }
436
449
 
437
- if (similarPositionItem.quote) {
438
- item.setQuote(similarPositionItem.quote);
439
- }
450
+ if (previousQuote !== null) {
451
+ item.setQuote(previousQuote);
452
+ }
453
+
454
+ if (currentQuote !== null) {
455
+ item.setQuote(currentQuote);
440
456
  }
441
457
 
442
458
  recalculatePercentages.call(this);
@@ -581,8 +597,8 @@ module.exports = (() => {
581
597
  * triggering updates to position(s) and data aggregation(s).
582
598
  *
583
599
  * @public
584
- * @param {Object[]} positionQuotes
585
- * @param {Object[]} forexQuotes
600
+ * @param {Quote[]} positionQuotes
601
+ * @param {Quote[]} forexQuotes
586
602
  * @param {Boolean=} force
587
603
  */
588
604
  setQuotes(positionQuotes, forexQuotes, force) {
@@ -612,7 +628,7 @@ module.exports = (() => {
612
628
 
613
629
  if (symbol) {
614
630
  if (this._symbols.hasOwnProperty(symbol)) {
615
- this._symbols[ symbol ].forEach(item => item.setQuote(quote, force || false));
631
+ this._symbols[symbol].forEach(item => item.setQuote(quote, force || false));
616
632
  }
617
633
  }
618
634
  });
@@ -624,18 +640,26 @@ module.exports = (() => {
624
640
  }
625
641
 
626
642
  /**
627
- * Sets the reference date (today).
643
+ * Performs an update of an exchange's status, triggering updates to position(s) and
644
+ * data aggregation(s).
628
645
  *
629
646
  * @public
630
- * @param {Day} referenceDate
647
+ * @param {ExchangeStatus} exchange
631
648
  */
632
- setReferenceDate(referenceDate) {
633
- assert.argumentIsRequired(referenceDate, 'referenceDate', Day, 'Day');
649
+ setExchangeStatus(exchange) {
650
+ assert.argumentIsRequired(exchange, 'exchange', Object);
651
+ assert.argumentIsRequired(exchange.code, 'exchange.code', String);
652
+ assert.argumentIsRequired(exchange.currentDay, 'exchange.currentDay', Day, 'Day');
653
+ assert.argumentIsRequired(exchange.currentOpened, 'exchange.currentOpened', Boolean);
654
+
655
+ const code = exchange.code;
634
656
 
635
- this._referenceDate = referenceDate;
657
+ this._exchanges[code] = exchange;
636
658
 
637
659
  this._items.forEach((item) => {
638
- item.setReferenceDate(this._referenceDate);
660
+ if (extractExchangeCode(item.position) === code) {
661
+ item.setExchangeStatus(exchange);
662
+ }
639
663
  });
640
664
  }
641
665
 
@@ -903,6 +927,14 @@ module.exports = (() => {
903
927
  }
904
928
  }
905
929
 
930
+ function extractExchangeCode(position) {
931
+ if (position.instrument && position.instrument.exchange) {
932
+ return position.instrument.exchange;
933
+ } else {
934
+ return null;
935
+ }
936
+ }
937
+
906
938
  function addGroupBinding(group, dispoable) {
907
939
  const id = group.id;
908
940
 
@@ -1127,7 +1159,7 @@ module.exports = (() => {
1127
1159
  const previousSummaries = this._summariesPrevious[ position.position ] || getSummaryArray(this._previousSummaryRanges);
1128
1160
 
1129
1161
  if (!requireCurrentSummary || currentSummary !== null) {
1130
- returnRef = new PositionItem(portfolio, position, currentSummary, previousSummaries, this._reporting, this._referenceDate);
1162
+ returnRef = new PositionItem(portfolio, position, currentSummary, previousSummaries, this._reporting, this._reportDate);
1131
1163
  } else {
1132
1164
  returnRef = null;
1133
1165
  }
@@ -1182,5 +1214,36 @@ module.exports = (() => {
1182
1214
  });
1183
1215
  }
1184
1216
 
1217
+ /**
1218
+ * @namespace Schema
1219
+ */
1220
+
1221
+ /**
1222
+ * @typedef Quote
1223
+ * @memberOf Schema
1224
+ * @type Object
1225
+ * @property {string} symbol
1226
+ * @property {number} lastPrice
1227
+ * @property {string} lastPriceDirection
1228
+ * @property {number} previousPrice
1229
+ * @property {number} priceChange
1230
+ * @property {number} percentChange
1231
+ * @property {number} openPrice
1232
+ * @property {number} highPrice
1233
+ * @property {number} lowPrice
1234
+ * @property {number} volume
1235
+ * @property {string} timeDisplay
1236
+ * @property {Day|null} lastDay
1237
+ */
1238
+
1239
+ /**
1240
+ * @typedef ExchangeStatus
1241
+ * @memberOf Schema
1242
+ * @type Object
1243
+ * @property {string} code
1244
+ * @property {Day} currentDay
1245
+ * @property {boolean} currentOpened
1246
+ */
1247
+
1185
1248
  return PositionContainer;
1186
1249
  })();
@@ -681,10 +681,6 @@ module.exports = (() => {
681
681
  this.changeCurrency(currencySelector({ portfolio }));
682
682
  });
683
683
 
684
- const referenceDateBinding = item.registerReferenceDateChangeHandler(() => {
685
- this.refresh();
686
- });
687
-
688
684
  let disposalBinding = null;
689
685
 
690
686
  disposalBinding = item.registerPositionItemDisposeHandler(() => {
@@ -696,7 +692,6 @@ module.exports = (() => {
696
692
  calculatingBinding.dispose();
697
693
 
698
694
  portfolioChangeBinding.dispose();
699
- referenceDateBinding.dispose();
700
695
 
701
696
  disposalBinding.dispose();
702
697
 
@@ -1009,6 +1004,7 @@ module.exports = (() => {
1009
1004
  updates.marketAbsolute = updates.marketAbsolute.add(translate(item, item.data.marketAbsolute));
1010
1005
  updates.unrealized = updates.unrealized.add(translate(item, item.data.unrealized));
1011
1006
  updates.unrealizedToday = updates.unrealizedToday.add(translate(item, item.data.unrealizedToday));
1007
+ updates.realizedToday = updates.realizedToday.add(translate(item, item.data.realizedToday));
1012
1008
  updates.gainToday = updates.gainToday.add(translate(item, item.data.unrealizedToday.add(item.data.realizedToday)));
1013
1009
  updates.summaryTotalCurrent = updates.summaryTotalCurrent.add(translate(item, item.data.periodGain));
1014
1010
  updates.periodUnrealized = updates.periodUnrealized.add(translate(item, item.data.periodUnrealized));
@@ -1021,6 +1017,7 @@ module.exports = (() => {
1021
1017
  marketDirection: unchanged,
1022
1018
  unrealized: Decimal.ZERO,
1023
1019
  unrealizedToday: Decimal.ZERO,
1020
+ realizedToday: Decimal.ZERO,
1024
1021
  gainToday: Decimal.ZERO,
1025
1022
  summaryTotalCurrent: Decimal.ZERO,
1026
1023
  periodUnrealized: Decimal.ZERO
@@ -1040,7 +1037,8 @@ module.exports = (() => {
1040
1037
  updates.marketDirection = { up: item.data.marketChange.getIsPositive(), down: item.data.marketChange.getIsNegative() };
1041
1038
  updates.unrealized = actual.unrealized.add(translate(item, item.data.unrealizedChange));
1042
1039
  updates.unrealizedToday = actual.unrealizedToday.add(translate(item, item.data.unrealizedTodayChange));
1043
- updates.gainToday = actual.gainToday.add(translate(item, item.data.unrealizedTodayChange));
1040
+ updates.realizedToday = actual.realizedToday.add(translate(item, item.data.realizedTodayChange));
1041
+ updates.gainToday = actual.gainToday.add(translate(item, item.data.unrealizedTodayChange).add(item.data.realizedTodayChange));
1044
1042
  updates.summaryTotalCurrent = actual.summaryTotalCurrent.add(translate(item, item.data.periodGainChange));
1045
1043
  updates.periodUnrealized = actual.periodUnrealized.add(translate(item, item.data.periodUnrealizedChange));
1046
1044
  }
@@ -1050,6 +1048,7 @@ module.exports = (() => {
1050
1048
  actual.marketAbsolute = updates.marketAbsolute;
1051
1049
  actual.unrealized = updates.unrealized;
1052
1050
  actual.unrealizedToday = updates.unrealizedToday;
1051
+ actual.realizedToday = updates.realizedToday;
1053
1052
  actual.gainToday = updates.gainToday;
1054
1053
  actual.summaryTotalCurrent = updates.summaryTotalCurrent;
1055
1054
  actual.periodUnrealized = updates.periodUnrealized;
@@ -1089,6 +1088,8 @@ module.exports = (() => {
1089
1088
  format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
1090
1089
  format.unrealizedTodayNegative = actual.unrealizedToday.getIsNegative();
1091
1090
 
1091
+ format.realizedToday = formatCurrency(actual.realizedToday, currency);
1092
+
1092
1093
  format.gainToday = formatCurrency(actual.gainToday, currency);
1093
1094
  format.gainTodayNegative = actual.gainToday.getIsNegative();
1094
1095
 
@@ -26,10 +26,10 @@ module.exports = (() => {
26
26
  * @param {Object} currentSummary
27
27
  * @param {Object[]} previousSummaries
28
28
  * @param {Boolean} reporting
29
- * @param {Day} referenceDate
29
+ * @param {Day|null} reportDate
30
30
  */
31
31
  class PositionItem extends Disposable {
32
- constructor(portfolio, position, currentSummary, previousSummaries, reporting, referenceDate) {
32
+ constructor(portfolio, position, currentSummary, previousSummaries, reporting, reportDate) {
33
33
  super();
34
34
 
35
35
  this._portfolio = portfolio;
@@ -44,12 +44,16 @@ module.exports = (() => {
44
44
  this._previousSummaries = previousSummaries || [ ];
45
45
 
46
46
  this._reporting = reporting;
47
- this._referenceDate = referenceDate;
47
+ this._reportDate = reportDate || null;
48
+
49
+ this._exchangeStatus = null;
48
50
 
49
51
  this._currentQuote = null;
50
52
  this._previousQuote = null;
51
53
  this._currentPrice = null;
52
54
 
55
+ const today = calculateToday(this._reportDate, this._exchangeStatus);
56
+
53
57
  this._data = { };
54
58
 
55
59
  this._data.basis = null;
@@ -64,6 +68,7 @@ module.exports = (() => {
64
68
  this._data.marketAbsoluteChange = null;
65
69
 
66
70
  this._data.realizedToday = null;
71
+ this._data.realizedTodayChange = null;
67
72
 
68
73
  this._data.unrealizedToday = null;
69
74
  this._data.unrealizedTodayChange = null;
@@ -112,7 +117,7 @@ module.exports = (() => {
112
117
  this._data.fundamental = { };
113
118
  this._data.calculating = getIsCalculating(position);
114
119
  this._data.locked = getIsLocked(position);
115
- this._data.expired = getIsExpired(position, referenceDate);
120
+ this._data.expired = getIsExpired(position, today);
116
121
 
117
122
  this._quoteChangedEvent = new Event(this);
118
123
  this._newsExistsChangedEvent = new Event(this);
@@ -120,11 +125,10 @@ module.exports = (() => {
120
125
  this._lockChangedEvent = new Event(this);
121
126
  this._calculatingChangedEvent = new Event(this);
122
127
  this._portfolioChangedEvent = new Event(this);
123
- this._referenceDateChangedEvent = new Event(this);
124
128
  this._positionItemDisposeEvent = new Event(this);
125
129
 
126
- calculateStaticData(this, this._referenceDate);
127
- calculatePriceData(this, null, null);
130
+ calculateStaticData(this, today);
131
+ calculatePriceData(this, null, today, false);
128
132
  }
129
133
 
130
134
  /**
@@ -244,7 +248,7 @@ module.exports = (() => {
244
248
  * be recalculated.
245
249
  *
246
250
  * @public
247
- * @param {Object} quote
251
+ * @param {Quote} quote
248
252
  * @param {Boolean=} force
249
253
  */
250
254
  setQuote(quote, force) {
@@ -260,7 +264,9 @@ module.exports = (() => {
260
264
  this._data.previousPrice = quote.previousPrice;
261
265
  }
262
266
 
263
- calculatePriceData(this, quote.lastPrice, getQuoteIsToday(quote, this._referenceDate));
267
+ const today = calculateToday(this._reportDate, this._exchangeStatus);
268
+
269
+ calculatePriceData(this, quote.lastPrice, today, getQuoteIsToday(quote, today));
264
270
 
265
271
  this._currentPrice = quote.lastPrice;
266
272
 
@@ -271,6 +277,28 @@ module.exports = (() => {
271
277
  }
272
278
  }
273
279
 
280
+ /**
281
+ * Sets the current exchange status -- causing position-level data (e.g. today's gain) to
282
+ * be recalculated.
283
+ *
284
+ * @public
285
+ * @param {ExchangeStatus} exchange
286
+ */
287
+ setExchangeStatus(exchange) {
288
+ assert.argumentIsRequired(exchange, 'exchange', Object);
289
+ assert.argumentIsRequired(exchange.code, 'exchange.code', String);
290
+ assert.argumentIsRequired(exchange.currentDay, 'exchange.currentDay', Day, 'Day');
291
+ assert.argumentIsRequired(exchange.currentOpened, 'exchange.currentOpened', Boolean);
292
+
293
+ if (this._exchangeStatus === null || !(exchange.currentDay.getIsEqual(this._exchangeStatus.currentDay) && exchange.currentOpened === this._exchangeStatus.currentOpened)) {
294
+ this._exchangeStatus = exchange;
295
+
296
+ if (this._currentQuote) {
297
+ this.setQuote(this._currentQuote, true);
298
+ }
299
+ }
300
+ }
301
+
274
302
  /**
275
303
  * Sets a flag which indicates if news article(s) exist for the encapsulated position's
276
304
  * symbol.
@@ -346,31 +374,6 @@ module.exports = (() => {
346
374
  }
347
375
  }
348
376
 
349
- /**
350
- * Sets the reference date (today).
351
- *
352
- * @public
353
- * @param {Day} referenceDate
354
- */
355
- setReferenceDate(referenceDate) {
356
- assert.argumentIsRequired(referenceDate, 'referenceDate', Day, 'Day');
357
-
358
- if (this.getIsDisposed()) {
359
- return;
360
- }
361
-
362
- if (this._referenceDate.getIsEqual(referenceDate)) {
363
- return;
364
- }
365
-
366
- this._referenceDate = referenceDate;
367
-
368
- calculateStaticData(this, this._referenceDate);
369
- calculatePriceData(this, this._currentPrice, getQuoteIsToday(this._currentQuote, this._referenceDate));
370
-
371
- this._referenceDateChangedEvent.fire(this._referenceDate);
372
- }
373
-
374
377
  /**
375
378
  * Registers an observer for quote changes, which is fired after internal recalculations
376
379
  * of position data are complete.
@@ -438,17 +441,6 @@ module.exports = (() => {
438
441
  return this._portfolioChangedEvent.register(handler);
439
442
  }
440
443
 
441
- /**
442
- * Registers an observer for changes to the reference date (today).
443
- *
444
- * @public
445
- * @param {Function} handler
446
- * @returns {Disposable}
447
- */
448
- registerReferenceDateChangeHandler(handler) {
449
- return this._referenceDateChangedEvent.register(handler);
450
- }
451
-
452
444
  /**
453
445
  * Registers an observer for object disposal.
454
446
  *
@@ -469,7 +461,6 @@ module.exports = (() => {
469
461
  this._lockChangedEvent.clear();
470
462
  this._calculatingChangedEvent.clear();
471
463
  this._portfolioChangedEvent.clear();
472
- this._referenceDateChangedEvent.clear();
473
464
  this._positionItemDisposeEvent.clear();
474
465
  }
475
466
 
@@ -478,7 +469,12 @@ module.exports = (() => {
478
469
  }
479
470
  }
480
471
 
481
- function calculateStaticData(item, referenceDate) {
472
+ /**
473
+ * @private
474
+ * @param {PositionItem} item
475
+ * @param {Day|null} day
476
+ */
477
+ function calculateStaticData(item, day) {
482
478
  const position = item.position;
483
479
 
484
480
  const currentSummary = item.currentSummary;
@@ -509,7 +505,7 @@ module.exports = (() => {
509
505
  data.realized = snapshot.gain;
510
506
  data.unrealized = Decimal.ZERO;
511
507
 
512
- if (position.latest && position.latest.date && position.latest.date.getIsEqual(referenceDate) && position.latest.gain) {
508
+ if (position.latest && position.latest.date && position.latest.date.getIsEqual(day) && position.latest.gain) {
513
509
  data.realizedToday = position.latest.gain;
514
510
  } else {
515
511
  data.realizedToday = Decimal.ZERO;
@@ -551,7 +547,14 @@ module.exports = (() => {
551
547
  data.totalDivisor = calculateTotalDivisor(position.instrument.type, data.initiate, position);
552
548
  }
553
549
 
554
- function calculatePriceData(item, price, today) {
550
+ /**
551
+ * @private
552
+ * @param {PositionItem} item
553
+ * @param {Decimal|number} price
554
+ * @param {Day} day
555
+ * @param {Boolean} today
556
+ */
557
+ function calculatePriceData(item, price, day, today) {
555
558
  const position = item.position;
556
559
  const snapshot = getSnapshot(position, item.currentSummary, item._reporting);
557
560
 
@@ -632,6 +635,24 @@ module.exports = (() => {
632
635
  data.unrealizedToday = unrealizedToday;
633
636
  data.unrealizedTodayChange = unrealizedTodayChange;
634
637
 
638
+ let realizedToday;
639
+ let realizedTodayChange;
640
+
641
+ if (position.latest && position.latest.date && position.latest.date.getIsEqual(day) && position.latest.gain) {
642
+ realizedToday = position.latest.gain;
643
+ } else {
644
+ realizedToday = Decimal.ZERO;
645
+ }
646
+
647
+ if (data.realizedToday) {
648
+ realizedTodayChange = realizedToday.subtract(data.realizedToday);
649
+ } else {
650
+ realizedTodayChange = realizedToday;
651
+ }
652
+
653
+ data.realizedToday = realizedToday;
654
+ data.realizedTodayChange = realizedTodayChange;
655
+
635
656
  const currentSummary = item.currentSummary;
636
657
  const previousSummary = getPreviousSummary(item.previousSummaries, 1);
637
658
 
@@ -884,8 +905,20 @@ module.exports = (() => {
884
905
  return snapshot;
885
906
  }
886
907
 
887
- function getQuoteIsToday(quote, referenceDate) {
888
- return quote && quote.lastDay instanceof Day && referenceDate instanceof Day && quote.lastDay.getIsEqual(referenceDate);
908
+ function calculateToday(reportDate, exchangeStatus) {
909
+ if (reportDate !== null) {
910
+ return reportDate;
911
+ }
912
+
913
+ if (exchangeStatus !== null) {
914
+ return exchangeStatus.currentDay;
915
+ }
916
+
917
+ return Day.getToday();
918
+ }
919
+
920
+ function getQuoteIsToday(quote, today) {
921
+ return quote && quote.lastDay instanceof Day && today instanceof Day && quote.lastDay.getIsEqual(today);
889
922
  }
890
923
 
891
924
  return PositionItem;
@@ -229,6 +229,7 @@ module.exports = (() => {
229
229
  .withField('instrument.code', DataType.NUMBER, true) // Not intended to be the unit code. Same value as [profile] table [type] column. See `InstrumentType.fromSymbolType` function.
230
230
  .withField('instrument.type', DataType.forEnum(InstrumentType, 'InstrumentType'), true)
231
231
  .withField('instrument.currency', DataType.forEnum(Currency, 'Currency'), true)
232
+ .withField('instrument.exchange', DataType.STRING, true)
232
233
  .withField('instrument.symbol.barchart', DataType.STRING, true)
233
234
  .withField('instrument.symbol.display', DataType.STRING, true)
234
235
  .withField('date', DataType.DAY)
@@ -279,6 +280,7 @@ module.exports = (() => {
279
280
  .withField('instrument.code', DataType.NUMBER, true) // Not intended to be the unit code. Same value as [profile] table [type] column. See `InstrumentType.fromSymbolType` function.
280
281
  .withField('instrument.type', DataType.forEnum(InstrumentType, 'InstrumentType'), true)
281
282
  .withField('instrument.currency', DataType.forEnum(Currency, 'Currency'), true)
283
+ .withField('instrument.exchange', DataType.STRING, true)
282
284
  .withField('instrument.symbol.barchart', DataType.STRING, true)
283
285
  .withField('instrument.symbol.display', DataType.STRING, true)
284
286
  .withField('date', DataType.DAY)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Common JavaScript code used by Barchart's Portfolio Service",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",