@barchart/portfolio-api-common 2.2.1 → 4.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.
@@ -150,6 +150,12 @@ module.exports = (() => {
150
150
  f.raw.open = getRawForDecimal(f.open);
151
151
  };
152
152
 
153
+ const gainFormatter = (t, f) => {
154
+ f.gain = t.gain;
155
+
156
+ f.raw.gain = getRawForDecimal(f.gain);
157
+ };
158
+
153
159
  const averageCostFormatter = (t, f) => {
154
160
  const basis = t.snapshot.basis;
155
161
  const open = t.snapshot.open;
@@ -495,8 +501,8 @@ module.exports = (() => {
495
501
  const formatters = new Map();
496
502
 
497
503
  formatters.set(TransactionType.BUY, [ basicFormatter, buySellFormatter, averageCostFormatter ]);
498
- formatters.set(TransactionType.SELL, [ basicFormatter, buySellFormatter, averageCostFormatter ]);
499
- formatters.set(TransactionType.BUY_SHORT, [ basicFormatter, buySellFormatter, averageCostFormatter ]);
504
+ formatters.set(TransactionType.SELL, [ basicFormatter, buySellFormatter, averageCostFormatter, gainFormatter ]);
505
+ formatters.set(TransactionType.BUY_SHORT, [ basicFormatter, buySellFormatter, averageCostFormatter, gainFormatter ]);
500
506
  formatters.set(TransactionType.SELL_SHORT, [ basicFormatter, buySellFormatter, averageCostFormatter ]);
501
507
  formatters.set(TransactionType.DIVIDEND, [ basicFormatter, dividendFormatter, averageCostFormatter ]);
502
508
  formatters.set(TransactionType.DIVIDEND_STOCK, [ basicFormatter, dividendStockFormatter, averageCostFormatter ]);
@@ -40,6 +40,10 @@ module.exports = (() => {
40
40
  Currency.USD
41
41
  ];
42
42
 
43
+ const STATIC_RATES = [
44
+ Rate.fromPair(0.01, '^GBXGBP')
45
+ ];
46
+
43
47
  /**
44
48
  * A container for positions which groups the positions into one or more
45
49
  * trees for aggregation and display purposes. For example, positions could be
@@ -168,21 +172,15 @@ module.exports = (() => {
168
172
 
169
173
  return symbols;
170
174
  }, [ ]);
171
-
172
- this._forexSymbols.push('^GBXGBP');
173
175
  }
174
176
 
175
- this._currencyTranslator = new CurrencyTranslator(this._forexSymbols);
177
+ this._currencyTranslator = new CurrencyTranslator(this._forexSymbols.concat(STATIC_RATES.map(r => r.getSymbol())));
176
178
 
177
179
  const forexQuotes = this._forexSymbols.map((symbol) => {
178
- if (symbol === '^GBXGBP') {
179
- return Rate.fromPair(0.01, '^GBXGBP');
180
- }
181
-
182
180
  return Rate.fromPair(Decimal.ONE, symbol);
183
181
  });
184
182
 
185
- this._currencyTranslator.setRates(forexQuotes);
183
+ this._currencyTranslator.setRates(forexQuotes.concat(STATIC_RATES));
186
184
 
187
185
  this._nodes = { };
188
186
 
@@ -625,6 +623,20 @@ module.exports = (() => {
625
623
  }
626
624
  }
627
625
 
626
+ /**
627
+ * Sets the reference date (today).
628
+ *
629
+ * @public
630
+ * @param {Day} referenceDate
631
+ */
632
+ setReferenceDate(referenceDate) {
633
+ this._referenceDate = referenceDate;
634
+
635
+ this._items.forEach((item) => {
636
+ item.setReferenceDate(this._referenceDate);
637
+ });
638
+ }
639
+
628
640
  /**
629
641
  * Returns current price for symbol provided.
630
642
  *
@@ -4,7 +4,6 @@ const array = require('@barchart/common-js/lang/array'),
4
4
  CurrencyTranslator = require('@barchart/common-js/lang/CurrencyTranslator'),
5
5
  Decimal = require('@barchart/common-js/lang/Decimal'),
6
6
  Disposable = require('@barchart/common-js/lang/Disposable'),
7
- DisposableStack = require('@barchart/common-js/collections/specialized/DisposableStack'),
8
7
  Event = require('@barchart/common-js/messaging/Event'),
9
8
  formatter = require('@barchart/common-js/lang/formatter'),
10
9
  is = require('@barchart/common-js/lang/is');
@@ -63,8 +62,6 @@ module.exports = (() => {
63
62
  this._groupExcludedChangeEvent = new Event(this);
64
63
  this._showClosedPositionsChangeEvent = new Event(this);
65
64
 
66
- this._disposeStack = new DisposableStack();
67
-
68
65
  this._excludedItems = [ ];
69
66
  this._excludedItemMap = { };
70
67
  this._consideredItems = this._items.slice(0);
@@ -131,6 +128,7 @@ module.exports = (() => {
131
128
  this._dataActual.basis = null;
132
129
  this._dataActual.basis2 = null;
133
130
  this._dataActual.realized = null;
131
+ this._dataActual.realizedToday = null;
134
132
  this._dataActual.income = null;
135
133
  this._dataActual.market = null;
136
134
  this._dataActual.market2 = null;
@@ -138,6 +136,7 @@ module.exports = (() => {
138
136
  this._dataActual.marketPercentPortfolio = null;
139
137
  this._dataActual.unrealized = null;
140
138
  this._dataActual.unrealizedToday = null;
139
+ this._dataActual.gainToday = null;
141
140
  this._dataActual.total = null;
142
141
  this._dataActual.summaryTotalCurrent = null;
143
142
  this._dataActual.summaryTotalPrevious = null;
@@ -157,6 +156,7 @@ module.exports = (() => {
157
156
  this._dataFormat.basis2 = null;
158
157
  this._dataFormat.realized = null;
159
158
  this._dataFormat.realizedPercent = null;
159
+ this._dataFormat.realizedToday = null;
160
160
  this._dataFormat.income = null;
161
161
  this._dataFormat.market = null;
162
162
  this._dataFormat.market2 = null;
@@ -168,6 +168,8 @@ module.exports = (() => {
168
168
  this._dataFormat.unrealizedNegative = false;
169
169
  this._dataFormat.unrealizedToday = null;
170
170
  this._dataFormat.unrealizedTodayNegative = false;
171
+ this._dataFormat.gainToday = null;
172
+ this._dataFormat.gainTodayNegative = false;
171
173
  this._dataFormat.total = null;
172
174
  this._dataFormat.totalNegative = false;
173
175
  this._dataFormat.summaryTotalCurrent = null;
@@ -593,52 +595,54 @@ module.exports = (() => {
593
595
  calculatePriceData(this, sender, false);
594
596
  });
595
597
 
596
- let fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
598
+ const fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
597
599
  if (this._single) {
598
600
  this._dataFormat.fundamental = data;
599
- } else {
600
- const fundamentalFields = [ 'percentChange1m', 'percentChange1y', 'percentChange3m', 'percentChangeYtd' ];
601
601
 
602
- const fundamentalData = this.items.reduce((sums, item, i) => {
603
- if (item.data && item.data.fundamental && item.data.fundamental.raw) {
604
- const fundamental = item.data.fundamental.raw;
602
+ return;
603
+ }
604
+
605
+ const fundamentalFields = [ 'percentChange1m', 'percentChange1y', 'percentChange3m', 'percentChangeYtd' ];
605
606
 
606
- fundamentalFields.forEach((fieldName) => {
607
- const summary = sums[fieldName];
608
- const value = fundamental[fieldName];
607
+ const fundamentalData = this.items.reduce((sums, item, i) => {
608
+ if (item.data && item.data.fundamental && item.data.fundamental.raw) {
609
+ const fundamental = item.data.fundamental.raw;
609
610
 
610
- if (is.number(value)) {
611
- summary.total = sums[fieldName].total + value;
612
- summary.count = sums[fieldName].count + 1;
613
- }
611
+ fundamentalFields.forEach((fieldName) => {
612
+ const summary = sums[fieldName];
613
+ const value = fundamental[fieldName];
614
614
 
615
- if ((i + 1) == this.items.length) {
616
- let averageFormat;
615
+ if (is.number(value)) {
616
+ summary.total = sums[fieldName].total + value;
617
+ summary.count = sums[fieldName].count + 1;
618
+ }
617
619
 
618
- if (summary.count > 0) {
619
- averageFormat = formatPercent(new Decimal(summary.total / summary.count), 2, true);
620
- } else {
621
- averageFormat = '—';
622
- }
620
+ if ((i + 1) == this.items.length) {
621
+ let averageFormat;
623
622
 
624
- summary.averageFormat = averageFormat;
623
+ if (summary.count > 0) {
624
+ averageFormat = formatPercent(new Decimal(summary.total / summary.count), 2, true);
625
+ } else {
626
+ averageFormat = '—';
625
627
  }
626
- });
627
- }
628
628
 
629
- return sums;
630
- }, fundamentalFields.reduce((sums, fieldName) => {
631
- sums[fieldName] = { total: 0, count: 0, averageFormat: '—' };
629
+ summary.averageFormat = averageFormat;
630
+ }
631
+ });
632
+ }
632
633
 
633
- return sums;
634
- }, { }));
634
+ return sums;
635
+ }, fundamentalFields.reduce((sums, fieldName) => {
636
+ sums[fieldName] = { total: 0, count: 0, averageFormat: '—' };
635
637
 
636
- this._dataFormat.fundamental = fundamentalFields.reduce((sums, fieldName) => {
637
- sums[fieldName] = fundamentalData[fieldName].averageFormat;
638
+ return sums;
639
+ }, { }));
638
640
 
639
- return sums;
640
- }, { });
641
- }
641
+ this._dataFormat.fundamental = fundamentalFields.reduce((sums, fieldName) => {
642
+ sums[fieldName] = fundamentalData[fieldName].averageFormat;
643
+
644
+ return sums;
645
+ }, { });
642
646
  });
643
647
 
644
648
  let newsBinding = Disposable.getEmpty();
@@ -660,7 +664,7 @@ module.exports = (() => {
660
664
  });
661
665
  }
662
666
 
663
- this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio) => {
667
+ const portfolioChangeBinding = item.registerPortfolioChangeHandler((portfolio) => {
664
668
  const descriptionSelector = this._definition.descriptionSelector;
665
669
 
666
670
  this._description = descriptionSelector(this._items[0]);
@@ -675,21 +679,27 @@ module.exports = (() => {
675
679
  const currencySelector = this._definition.currencySelector;
676
680
 
677
681
  this.changeCurrency(currencySelector({ portfolio }));
678
- }));
682
+ });
679
683
 
680
- this._disposeStack.push(fundamentalBinding);
681
- this._disposeStack.push(quoteBinding);
682
- this._disposeStack.push(lockedBinding);
683
- this._disposeStack.push(calculatingBinding);
684
- this._disposeStack.push(newsBinding);
684
+ const referenceDateBinding = item.registerReferenceDateChangeHandler(() => {
685
+ this.refresh();
686
+ });
685
687
 
686
- this._disposeStack.push(item.registerPositionItemDisposeHandler(() => {
687
- fundamentalBinding.dispose();
688
+ let disposalBinding = null;
689
+
690
+ disposalBinding = item.registerPositionItemDisposeHandler(() => {
688
691
  quoteBinding.dispose();
692
+ fundamentalBinding.dispose();
693
+
689
694
  newsBinding.dispose();
690
695
  lockedBinding.dispose();
691
696
  calculatingBinding.dispose();
692
697
 
698
+ portfolioChangeBinding.dispose();
699
+ referenceDateBinding.dispose();
700
+
701
+ disposalBinding.dispose();
702
+
693
703
  array.remove(this._items, i => i === item);
694
704
  array.remove(this._excludedItems, i => i === item);
695
705
  array.remove(this._consideredItems, i => i === item);
@@ -697,7 +707,7 @@ module.exports = (() => {
697
707
  delete this._excludedItemMap[item.position.position];
698
708
 
699
709
  this.refresh();
700
- }));
710
+ });
701
711
  }
702
712
 
703
713
  function formatNumber(number, precision) {
@@ -821,6 +831,7 @@ module.exports = (() => {
821
831
 
822
832
  updates.realized = updates.realized.add(translate(item, item.data.realized));
823
833
  updates.unrealized = updates.unrealized.add(translate(item, item.data.unrealized));
834
+ updates.realizedToday = updates.realizedToday.add(translate(item, item.data.realizedToday));
824
835
  updates.income = updates.income.add(translate(item, item.data.income));
825
836
  updates.summaryTotalCurrent = updates.summaryTotalCurrent.add(translate(item, item.data.periodGain));
826
837
  updates.summaryTotalPrevious = updates.summaryTotalPrevious.add(translate(item, item.data.periodGainPrevious));
@@ -847,6 +858,7 @@ module.exports = (() => {
847
858
  basis2: Decimal.ZERO,
848
859
  realized: Decimal.ZERO,
849
860
  unrealized: Decimal.ZERO,
861
+ realizedToday: Decimal.ZERO,
850
862
  income: Decimal.ZERO,
851
863
  summaryTotalCurrent: Decimal.ZERO,
852
864
  summaryTotalPrevious: Decimal.ZERO,
@@ -867,6 +879,7 @@ module.exports = (() => {
867
879
  actual.basis2 = updates.basis2;
868
880
  actual.realized = updates.realized;
869
881
  actual.unrealized = updates.unrealized;
882
+ actual.realizedToday = updates.realizedToday;
870
883
  actual.income = updates.income;
871
884
  actual.summaryTotalCurrent = updates.summaryTotalCurrent;
872
885
  actual.summaryTotalPrevious = updates.summaryTotalPrevious;
@@ -886,6 +899,7 @@ module.exports = (() => {
886
899
  format.basis2 = formatCurrency(actual.basis2, currency);
887
900
  format.realized = formatCurrency(actual.realized, currency);
888
901
  format.unrealized = formatCurrency(actual.unrealized, currency);
902
+ format.realizedToday = formatCurrency(actual.realizedToday, currency);
889
903
  format.income = formatCurrency(actual.income, currency);
890
904
  format.summaryTotalCurrent = formatCurrency(updates.summaryTotalCurrent, currency);
891
905
  format.summaryTotalCurrentNegative = updates.summaryTotalCurrent.getIsNegative();
@@ -995,6 +1009,7 @@ module.exports = (() => {
995
1009
  updates.marketAbsolute = updates.marketAbsolute.add(translate(item, item.data.marketAbsolute));
996
1010
  updates.unrealized = updates.unrealized.add(translate(item, item.data.unrealized));
997
1011
  updates.unrealizedToday = updates.unrealizedToday.add(translate(item, item.data.unrealizedToday));
1012
+ updates.gainToday = updates.gainToday.add(translate(item, item.data.unrealizedToday.add(item.data.realizedToday)));
998
1013
  updates.summaryTotalCurrent = updates.summaryTotalCurrent.add(translate(item, item.data.periodGain));
999
1014
  updates.periodUnrealized = updates.periodUnrealized.add(translate(item, item.data.periodUnrealized));
1000
1015
 
@@ -1006,6 +1021,7 @@ module.exports = (() => {
1006
1021
  marketDirection: unchanged,
1007
1022
  unrealized: Decimal.ZERO,
1008
1023
  unrealizedToday: Decimal.ZERO,
1024
+ gainToday: Decimal.ZERO,
1009
1025
  summaryTotalCurrent: Decimal.ZERO,
1010
1026
  periodUnrealized: Decimal.ZERO
1011
1027
  });
@@ -1024,6 +1040,7 @@ module.exports = (() => {
1024
1040
  updates.marketDirection = { up: item.data.marketChange.getIsPositive(), down: item.data.marketChange.getIsNegative() };
1025
1041
  updates.unrealized = actual.unrealized.add(translate(item, item.data.unrealizedChange));
1026
1042
  updates.unrealizedToday = actual.unrealizedToday.add(translate(item, item.data.unrealizedTodayChange));
1043
+ updates.gainToday = actual.gainToday.add(translate(item, item.data.unrealizedTodayChange));
1027
1044
  updates.summaryTotalCurrent = actual.summaryTotalCurrent.add(translate(item, item.data.periodGainChange));
1028
1045
  updates.periodUnrealized = actual.periodUnrealized.add(translate(item, item.data.periodUnrealizedChange));
1029
1046
  }
@@ -1033,6 +1050,7 @@ module.exports = (() => {
1033
1050
  actual.marketAbsolute = updates.marketAbsolute;
1034
1051
  actual.unrealized = updates.unrealized;
1035
1052
  actual.unrealizedToday = updates.unrealizedToday;
1053
+ actual.gainToday = updates.gainToday;
1036
1054
  actual.summaryTotalCurrent = updates.summaryTotalCurrent;
1037
1055
  actual.periodUnrealized = updates.periodUnrealized;
1038
1056
 
@@ -1071,6 +1089,9 @@ module.exports = (() => {
1071
1089
  format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
1072
1090
  format.unrealizedTodayNegative = actual.unrealizedToday.getIsNegative();
1073
1091
 
1092
+ format.gainToday = formatCurrency(actual.gainToday, currency);
1093
+ format.gainTodayNegative = actual.gainToday.getIsNegative();
1094
+
1074
1095
  format.summaryTotalCurrent = formatCurrency(actual.summaryTotalCurrent, currency);
1075
1096
  format.summaryTotalCurrentNegative = actual.summaryTotalCurrent.getIsNegative();
1076
1097
 
@@ -1,5 +1,6 @@
1
1
  const assert = require('@barchart/common-js/lang/assert'),
2
2
  Currency = require('@barchart/common-js/lang/Currency'),
3
+ Day = require('@barchart/common-js/lang/Day'),
3
4
  Decimal = require('@barchart/common-js/lang/Decimal'),
4
5
  Disposable = require('@barchart/common-js/lang/Disposable'),
5
6
  Event = require('@barchart/common-js/messaging/Event'),
@@ -43,6 +44,7 @@ module.exports = (() => {
43
44
  this._previousSummaries = previousSummaries || [ ];
44
45
 
45
46
  this._reporting = reporting;
47
+ this._referenceDate = referenceDate;
46
48
 
47
49
  this._currentQuote = null;
48
50
  this._previousQuote = null;
@@ -61,6 +63,8 @@ module.exports = (() => {
61
63
  this._data.marketAbsolute = null;
62
64
  this._data.marketAbsoluteChange = null;
63
65
 
66
+ this._data.realizedToday = null;
67
+
64
68
  this._data.unrealizedToday = null;
65
69
  this._data.unrealizedTodayChange = null;
66
70
 
@@ -116,10 +120,11 @@ module.exports = (() => {
116
120
  this._lockChangedEvent = new Event(this);
117
121
  this._calculatingChangedEvent = new Event(this);
118
122
  this._portfolioChangedEvent = new Event(this);
123
+ this._referenceDateChangedEvent = new Event(this);
119
124
  this._positionItemDisposeEvent = new Event(this);
120
125
 
121
- calculateStaticData(this);
122
- calculatePriceData(this, null);
126
+ calculateStaticData(this, this._referenceDate);
127
+ calculatePriceData(this, null, null);
123
128
  }
124
129
 
125
130
  /**
@@ -250,14 +255,13 @@ module.exports = (() => {
250
255
  return;
251
256
  }
252
257
 
253
- if (this._currentPricePrevious !== quote.lastPrice || force) {
258
+ if (this._currentPrice !== quote.lastPrice || force) {
254
259
  if (quote.previousPrice) {
255
260
  this._data.previousPrice = quote.previousPrice;
256
261
  }
257
262
 
258
- calculatePriceData(this, quote.lastPrice);
263
+ calculatePriceData(this, quote.lastPrice, getQuoteIsToday(quote, this._referenceDate));
259
264
 
260
- this._currentPricePrevious = this._currentPrice;
261
265
  this._currentPrice = quote.lastPrice;
262
266
 
263
267
  this._previousQuote = this._currentQuote;
@@ -342,6 +346,31 @@ module.exports = (() => {
342
346
  }
343
347
  }
344
348
 
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
+
345
374
  /**
346
375
  * Registers an observer for quote changes, which is fired after internal recalculations
347
376
  * of position data are complete.
@@ -399,7 +428,7 @@ module.exports = (() => {
399
428
  }
400
429
 
401
430
  /**
402
- * Registers an observer changes to portfolio metadata.
431
+ * Registers an observer for changes to portfolio metadata.
403
432
  *
404
433
  * @public
405
434
  * @param {Function} handler
@@ -409,6 +438,17 @@ module.exports = (() => {
409
438
  return this._portfolioChangedEvent.register(handler);
410
439
  }
411
440
 
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
+
412
452
  /**
413
453
  * Registers an observer for object disposal.
414
454
  *
@@ -427,7 +467,9 @@ module.exports = (() => {
427
467
  this._newsExistsChangedEvent.clear();
428
468
  this._fundamentalDataChangedEvent.clear();
429
469
  this._lockChangedEvent.clear();
470
+ this._calculatingChangedEvent.clear();
430
471
  this._portfolioChangedEvent.clear();
472
+ this._referenceDateChangedEvent.clear();
431
473
  this._positionItemDisposeEvent.clear();
432
474
  }
433
475
 
@@ -436,7 +478,7 @@ module.exports = (() => {
436
478
  }
437
479
  }
438
480
 
439
- function calculateStaticData(item) {
481
+ function calculateStaticData(item, referenceDate) {
440
482
  const position = item.position;
441
483
 
442
484
  const currentSummary = item.currentSummary;
@@ -467,6 +509,12 @@ module.exports = (() => {
467
509
  data.realized = snapshot.gain;
468
510
  data.unrealized = Decimal.ZERO;
469
511
 
512
+ if (position.latest && position.latest.date && position.latest.date.getIsEqual(referenceDate) && position.latest.gain) {
513
+ data.realizedToday = position.latest.gain;
514
+ } else {
515
+ data.realizedToday = Decimal.ZERO;
516
+ }
517
+
470
518
  data.income = snapshot.income;
471
519
 
472
520
  data.marketPrevious = previousSummary1 === null ? Decimal.ZERO : previousSummary1.end.value;
@@ -503,7 +551,7 @@ module.exports = (() => {
503
551
  data.totalDivisor = calculateTotalDivisor(position.instrument.type, data.initiate, position);
504
552
  }
505
553
 
506
- function calculatePriceData(item, price) {
554
+ function calculatePriceData(item, price, today) {
507
555
  const position = item.position;
508
556
  const snapshot = getSnapshot(position, item.currentSummary, item._reporting);
509
557
 
@@ -512,7 +560,7 @@ module.exports = (() => {
512
560
  // 2023/11/28, BRI. Futures contracts do not have their value set to zero
513
561
  // after expiration. At expiration, the contract would have been closed
514
562
  // (but the price would not have been zero). On the other hand, option
515
- // contracts can expire worthless and we attempt to represent that here.
563
+ // contracts can expire worthless, and we attempt to represent that here.
516
564
 
517
565
  const worthless = data.expired && (position.instrument.type === InstrumentType.EQUITY_OPTION || position.instrument.type === InstrumentType.FUTURE_OPTION);
518
566
 
@@ -560,7 +608,13 @@ module.exports = (() => {
560
608
  let unrealizedToday;
561
609
  let unrealizedTodayChange;
562
610
 
563
- if (data.previousPrice && price) {
611
+ // 2025/07/20, BRI. The unrealized gain should only be calculated if the position
612
+ // has quoted today. That means the position item is date-aware at this point. In
613
+ // words, the phrase "today" is literal. It does not mean the gain on the last known
614
+ // price change (e.g. friday, last week, sometime in the past when the instrument
615
+ // was delisted, etc).
616
+
617
+ if (today && data.previousPrice && price) {
564
618
  const unrealizedTodayBase = ValuationCalculator.calculate(position.instrument, data.previousPrice, snapshot.open);
565
619
 
566
620
  unrealizedToday = market.subtract(unrealizedTodayBase);
@@ -830,5 +884,9 @@ module.exports = (() => {
830
884
  return snapshot;
831
885
  }
832
886
 
887
+ function getQuoteIsToday(quote, referenceDate) {
888
+ return quote && quote.lastDay instanceof Day && referenceDate instanceof Day && quote.lastDay.getIsEqual(referenceDate);
889
+ }
890
+
833
891
  return PositionItem;
834
892
  })();
@@ -121,6 +121,8 @@ module.exports = (() => {
121
121
  .withField('snapshot.income', DataType.DECIMAL)
122
122
  .withField('snapshot.value', DataType.DECIMAL)
123
123
  .withField('snapshot.initial', DataType.forEnum(PositionDirection, 'PositionDirection'), true)
124
+ .withField('latest.date', DataType.DAY)
125
+ .withField('latest.gain', DataType.DECIMAL)
124
126
  .withField('legacy.system', DataType.STRING, true)
125
127
  .withField('legacy.user', DataType.STRING, true)
126
128
  .withField('legacy.portfolio', DataType.STRING, true)
@@ -168,6 +170,8 @@ module.exports = (() => {
168
170
  .withField('snapshot.income', DataType.DECIMAL)
169
171
  .withField('snapshot.value', DataType.DECIMAL)
170
172
  .withField('snapshot.initial', DataType.forEnum(PositionDirection, 'PositionDirection'), true)
173
+ .withField('latest.date', DataType.DAY)
174
+ .withField('latest.gain', DataType.DECIMAL)
171
175
  .withField('system.calculate.processors', DataType.NUMBER, true)
172
176
  .withField('system.locked', DataType.BOOLEAN, true)
173
177
  .withField('previous', DataType.NUMBER, true)
@@ -137,6 +137,7 @@ module.exports = (() => {
137
137
  .withField('amount', DataType.DECIMAL)
138
138
  .withField('quantity', DataType.DECIMAL)
139
139
  .withField('fee', DataType.DECIMAL, true)
140
+ .withField('gain', DataType.DECIMAL)
140
141
  .withField('reference.position', DataType.STRING, true)
141
142
  .withField('reference.transaction', DataType.STRING, true)
142
143
  .withField('snapshot.open', DataType.DECIMAL)
@@ -187,6 +188,7 @@ module.exports = (() => {
187
188
  .withField('amount', DataType.DECIMAL)
188
189
  .withField('quantity', DataType.DECIMAL)
189
190
  .withField('fee', DataType.DECIMAL, true)
191
+ .withField('gain', DataType.DECIMAL)
190
192
  .withField('reference.position', DataType.STRING, true)
191
193
  .withField('reference.transaction', DataType.NUMBER, true)
192
194
  .withField('snapshot.open', DataType.DECIMAL)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "2.2.1",
3
+ "version": "4.1.0",
4
4
  "description": "Common JavaScript code used by Barchart's Portfolio Service",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",