@barchart/portfolio-api-common 2.2.1 → 4.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.
@@ -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
 
@@ -131,6 +131,7 @@ module.exports = (() => {
131
131
  this._dataActual.basis = null;
132
132
  this._dataActual.basis2 = null;
133
133
  this._dataActual.realized = null;
134
+ this._dataActual.realizedToday = null;
134
135
  this._dataActual.income = null;
135
136
  this._dataActual.market = null;
136
137
  this._dataActual.market2 = null;
@@ -138,6 +139,7 @@ module.exports = (() => {
138
139
  this._dataActual.marketPercentPortfolio = null;
139
140
  this._dataActual.unrealized = null;
140
141
  this._dataActual.unrealizedToday = null;
142
+ this._dataActual.gainToday = null;
141
143
  this._dataActual.total = null;
142
144
  this._dataActual.summaryTotalCurrent = null;
143
145
  this._dataActual.summaryTotalPrevious = null;
@@ -157,6 +159,7 @@ module.exports = (() => {
157
159
  this._dataFormat.basis2 = null;
158
160
  this._dataFormat.realized = null;
159
161
  this._dataFormat.realizedPercent = null;
162
+ this._dataFormat.realizedToday = null;
160
163
  this._dataFormat.income = null;
161
164
  this._dataFormat.market = null;
162
165
  this._dataFormat.market2 = null;
@@ -168,6 +171,8 @@ module.exports = (() => {
168
171
  this._dataFormat.unrealizedNegative = false;
169
172
  this._dataFormat.unrealizedToday = null;
170
173
  this._dataFormat.unrealizedTodayNegative = false;
174
+ this._dataFormat.gainToday = null;
175
+ this._dataFormat.gainTodayNegative = false;
171
176
  this._dataFormat.total = null;
172
177
  this._dataFormat.totalNegative = false;
173
178
  this._dataFormat.summaryTotalCurrent = null;
@@ -821,6 +826,7 @@ module.exports = (() => {
821
826
 
822
827
  updates.realized = updates.realized.add(translate(item, item.data.realized));
823
828
  updates.unrealized = updates.unrealized.add(translate(item, item.data.unrealized));
829
+ updates.realizedToday = updates.realizedToday.add(translate(item, item.data.realizedToday));
824
830
  updates.income = updates.income.add(translate(item, item.data.income));
825
831
  updates.summaryTotalCurrent = updates.summaryTotalCurrent.add(translate(item, item.data.periodGain));
826
832
  updates.summaryTotalPrevious = updates.summaryTotalPrevious.add(translate(item, item.data.periodGainPrevious));
@@ -847,6 +853,7 @@ module.exports = (() => {
847
853
  basis2: Decimal.ZERO,
848
854
  realized: Decimal.ZERO,
849
855
  unrealized: Decimal.ZERO,
856
+ realizedToday: Decimal.ZERO,
850
857
  income: Decimal.ZERO,
851
858
  summaryTotalCurrent: Decimal.ZERO,
852
859
  summaryTotalPrevious: Decimal.ZERO,
@@ -867,6 +874,7 @@ module.exports = (() => {
867
874
  actual.basis2 = updates.basis2;
868
875
  actual.realized = updates.realized;
869
876
  actual.unrealized = updates.unrealized;
877
+ actual.realizedToday = updates.realizedToday;
870
878
  actual.income = updates.income;
871
879
  actual.summaryTotalCurrent = updates.summaryTotalCurrent;
872
880
  actual.summaryTotalPrevious = updates.summaryTotalPrevious;
@@ -886,6 +894,7 @@ module.exports = (() => {
886
894
  format.basis2 = formatCurrency(actual.basis2, currency);
887
895
  format.realized = formatCurrency(actual.realized, currency);
888
896
  format.unrealized = formatCurrency(actual.unrealized, currency);
897
+ format.realizedToday = formatCurrency(actual.realizedToday, currency);
889
898
  format.income = formatCurrency(actual.income, currency);
890
899
  format.summaryTotalCurrent = formatCurrency(updates.summaryTotalCurrent, currency);
891
900
  format.summaryTotalCurrentNegative = updates.summaryTotalCurrent.getIsNegative();
@@ -995,6 +1004,7 @@ module.exports = (() => {
995
1004
  updates.marketAbsolute = updates.marketAbsolute.add(translate(item, item.data.marketAbsolute));
996
1005
  updates.unrealized = updates.unrealized.add(translate(item, item.data.unrealized));
997
1006
  updates.unrealizedToday = updates.unrealizedToday.add(translate(item, item.data.unrealizedToday));
1007
+ updates.gainToday = updates.gainToday.add(translate(item, item.data.unrealizedToday.add(item.data.realizedToday)));
998
1008
  updates.summaryTotalCurrent = updates.summaryTotalCurrent.add(translate(item, item.data.periodGain));
999
1009
  updates.periodUnrealized = updates.periodUnrealized.add(translate(item, item.data.periodUnrealized));
1000
1010
 
@@ -1006,6 +1016,7 @@ module.exports = (() => {
1006
1016
  marketDirection: unchanged,
1007
1017
  unrealized: Decimal.ZERO,
1008
1018
  unrealizedToday: Decimal.ZERO,
1019
+ gainToday: Decimal.ZERO,
1009
1020
  summaryTotalCurrent: Decimal.ZERO,
1010
1021
  periodUnrealized: Decimal.ZERO
1011
1022
  });
@@ -1024,6 +1035,7 @@ module.exports = (() => {
1024
1035
  updates.marketDirection = { up: item.data.marketChange.getIsPositive(), down: item.data.marketChange.getIsNegative() };
1025
1036
  updates.unrealized = actual.unrealized.add(translate(item, item.data.unrealizedChange));
1026
1037
  updates.unrealizedToday = actual.unrealizedToday.add(translate(item, item.data.unrealizedTodayChange));
1038
+ updates.gainToday = actual.gainToday.add(translate(item, item.data.unrealizedTodayChange));
1027
1039
  updates.summaryTotalCurrent = actual.summaryTotalCurrent.add(translate(item, item.data.periodGainChange));
1028
1040
  updates.periodUnrealized = actual.periodUnrealized.add(translate(item, item.data.periodUnrealizedChange));
1029
1041
  }
@@ -1033,6 +1045,7 @@ module.exports = (() => {
1033
1045
  actual.marketAbsolute = updates.marketAbsolute;
1034
1046
  actual.unrealized = updates.unrealized;
1035
1047
  actual.unrealizedToday = updates.unrealizedToday;
1048
+ actual.gainToday = updates.gainToday;
1036
1049
  actual.summaryTotalCurrent = updates.summaryTotalCurrent;
1037
1050
  actual.periodUnrealized = updates.periodUnrealized;
1038
1051
 
@@ -1071,6 +1084,9 @@ module.exports = (() => {
1071
1084
  format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
1072
1085
  format.unrealizedTodayNegative = actual.unrealizedToday.getIsNegative();
1073
1086
 
1087
+ format.gainToday = formatCurrency(actual.gainToday, currency);
1088
+ format.gainTodayNegative = actual.gainToday.getIsNegative();
1089
+
1074
1090
  format.summaryTotalCurrent = formatCurrency(actual.summaryTotalCurrent, currency);
1075
1091
  format.summaryTotalCurrentNegative = actual.summaryTotalCurrent.getIsNegative();
1076
1092
 
@@ -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
 
@@ -118,8 +122,8 @@ module.exports = (() => {
118
122
  this._portfolioChangedEvent = new Event(this);
119
123
  this._positionItemDisposeEvent = new Event(this);
120
124
 
121
- calculateStaticData(this);
122
- calculatePriceData(this, null);
125
+ calculateStaticData(this, this._referenceDate);
126
+ calculatePriceData(this, null, null);
123
127
  }
124
128
 
125
129
  /**
@@ -255,7 +259,7 @@ module.exports = (() => {
255
259
  this._data.previousPrice = quote.previousPrice;
256
260
  }
257
261
 
258
- calculatePriceData(this, quote.lastPrice);
262
+ calculatePriceData(this, quote.lastPrice, quote.lastDay instanceof Day && quote.lastDay.getIsEqual(this._referenceDate));
259
263
 
260
264
  this._currentPricePrevious = this._currentPrice;
261
265
  this._currentPrice = quote.lastPrice;
@@ -436,7 +440,7 @@ module.exports = (() => {
436
440
  }
437
441
  }
438
442
 
439
- function calculateStaticData(item) {
443
+ function calculateStaticData(item, referenceDate) {
440
444
  const position = item.position;
441
445
 
442
446
  const currentSummary = item.currentSummary;
@@ -467,6 +471,12 @@ module.exports = (() => {
467
471
  data.realized = snapshot.gain;
468
472
  data.unrealized = Decimal.ZERO;
469
473
 
474
+ if (position.latest && position.latest.date && position.latest.date.getIsEqual(referenceDate) && position.latest.gain) {
475
+ data.realizedToday = position.latest.gain;
476
+ } else {
477
+ data.realizedToday = Decimal.ZERO;
478
+ }
479
+
470
480
  data.income = snapshot.income;
471
481
 
472
482
  data.marketPrevious = previousSummary1 === null ? Decimal.ZERO : previousSummary1.end.value;
@@ -503,7 +513,7 @@ module.exports = (() => {
503
513
  data.totalDivisor = calculateTotalDivisor(position.instrument.type, data.initiate, position);
504
514
  }
505
515
 
506
- function calculatePriceData(item, price) {
516
+ function calculatePriceData(item, price, today) {
507
517
  const position = item.position;
508
518
  const snapshot = getSnapshot(position, item.currentSummary, item._reporting);
509
519
 
@@ -512,7 +522,7 @@ module.exports = (() => {
512
522
  // 2023/11/28, BRI. Futures contracts do not have their value set to zero
513
523
  // after expiration. At expiration, the contract would have been closed
514
524
  // (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.
525
+ // contracts can expire worthless, and we attempt to represent that here.
516
526
 
517
527
  const worthless = data.expired && (position.instrument.type === InstrumentType.EQUITY_OPTION || position.instrument.type === InstrumentType.FUTURE_OPTION);
518
528
 
@@ -560,7 +570,13 @@ module.exports = (() => {
560
570
  let unrealizedToday;
561
571
  let unrealizedTodayChange;
562
572
 
563
- if (data.previousPrice && price) {
573
+ // 2025/07/20, BRI. The unrealized gain should only be calculated if the position
574
+ // has quoted today. That means the position item is date-aware at this point. In
575
+ // words, the phrase "today" is literal. It does not mean the gain on the last known
576
+ // price change (e.g. friday, last week, sometime in the past when the instrument
577
+ // was delisted, etc).
578
+
579
+ if (today && data.previousPrice && price) {
564
580
  const unrealizedTodayBase = ValuationCalculator.calculate(position.instrument, data.previousPrice, snapshot.open);
565
581
 
566
582
  unrealizedToday = market.subtract(unrealizedTodayBase);
@@ -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.0.0",
4
4
  "description": "Common JavaScript code used by Barchart's Portfolio Service",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",