@barchart/portfolio-api-common 1.0.127 → 1.0.131

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.
@@ -22,11 +22,11 @@ module.exports = (() => {
22
22
 
23
23
  /**
24
24
  * A container for positions which groups the positions into one or more
25
- * trees for aggregation and display purposes. For example, perhaps a positions
26
- * grouped first by asset class then by position is desired.
25
+ * trees for aggregation and display purposes. For example, positions could be
26
+ * grouped first by asset class then by position.
27
27
  *
28
28
  * Furthermore, the container performs aggregation (driven primarily by price
29
- * changes) for each level of grouping in the internal tree(s).
29
+ * changes) for each level of grouping.
30
30
  *
31
31
  * @public
32
32
  * @param {Array.<PositionTreeDefinition>} definitions
@@ -219,10 +219,14 @@ module.exports = (() => {
219
219
  }, { });
220
220
  }
221
221
 
222
- get defaultCurrency() {
223
- return this._defaultCurrency;
224
- }
225
-
222
+ /**
223
+ * Returns a distinct list of all symbols used by the positions
224
+ * within the container.
225
+ *
226
+ * @public
227
+ * @param {Boolean} display - If true, all "display" symbols are returned; otherwise Barchart symbols are returned.
228
+ * @returns {Array.<String>}
229
+ */
226
230
  getPositionSymbols(display) {
227
231
  const symbols = this._items.reduce((symbols, item) => {
228
232
  const position = item.position;
@@ -245,6 +249,14 @@ module.exports = (() => {
245
249
  return array.unique(symbols);
246
250
  }
247
251
 
252
+ /**
253
+ * Updates the quote for a single symbol; causing updates to any grouping
254
+ * level that contains the position(s) for the symbol.
255
+ *
256
+ * @public
257
+ * @param {String} symbol
258
+ * @param {Object} quote
259
+ */
248
260
  setPositionQuote(symbol, quote) {
249
261
  assert.argumentIsRequired(symbol, 'symbol', String);
250
262
  assert.argumentIsRequired(quote, 'quote', Object);
@@ -254,14 +266,34 @@ module.exports = (() => {
254
266
  }
255
267
  }
256
268
 
269
+ /**
270
+ * Returns all forex symbols that are required to do currency translations.
271
+ *
272
+ * @public
273
+ * @returns {Array.<String>}
274
+ */
257
275
  getForexSymbols() {
258
276
  return this._forexSymbols;
259
277
  }
260
278
 
279
+ /**
280
+ * Returns all current forex quotes.
281
+ *
282
+ * @public
283
+ * @returns {Array.<Object>}
284
+ */
261
285
  getForexQuotes() {
262
286
  return this._forexQuotes;
263
287
  }
264
288
 
289
+ /**
290
+ * Updates the forex quote for a single currency pair; causing updates to
291
+ * any grouping level that contains that requires translation.
292
+ *
293
+ * @public
294
+ * @param {String} symbol
295
+ * @param {Object} quote
296
+ */
265
297
  setForexQuote(symbol, quote) {
266
298
  assert.argumentIsRequired(symbol, 'symbol', String);
267
299
  assert.argumentIsRequired(quote, 'quote', Object);
@@ -279,10 +311,40 @@ module.exports = (() => {
279
311
  Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
280
312
  }
281
313
 
282
- setPositionFundamentals(data) {
314
+ /**
315
+ * Updates fundamental data for a single symbol.
316
+ *
317
+ * @public
318
+ * @param {String} symbol
319
+ * @param {Object} data
320
+ */
321
+ setPositionFundamentalData(symbol, data) {
283
322
  return;
284
323
  }
285
324
 
325
+ /**
326
+ * Indicates if a news article exists for a symbol.
327
+ *
328
+ * @public
329
+ * @param {String} symbol
330
+ * @param {Boolean} exists
331
+ */
332
+ setNewsArticleExists(symbol, exists) {
333
+ assert.argumentIsRequired(symbol, 'symbol', String);
334
+ assert.argumentIsRequired(exists, 'exists', Boolean);
335
+
336
+ if (this._symbols.hasOwnProperty(symbol)) {
337
+ this._symbols[symbol].forEach(item => item.setNewsArticleExists(exists));
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Returns a single level of grouping from one of the internal trees.
343
+ *
344
+ * @param {String} name
345
+ * @param {Array.<String> keys
346
+ * @returns {PositionGroup}
347
+ */
286
348
  getGroup(name, keys) {
287
349
  assert.argumentIsRequired(name, 'name', String);
288
350
  assert.argumentIsArray(keys, 'keys', Number);
@@ -290,6 +352,14 @@ module.exports = (() => {
290
352
  return findNode(this._trees[name], keys).getValue();
291
353
  }
292
354
 
355
+ /**
356
+ * Returns all child groups from a level of grouping within one of
357
+ * the internal trees.
358
+ *
359
+ * @param {String} name
360
+ * @param {Array.<String> keys
361
+ * @returns {Array.<PositionGroup>}
362
+ */
293
363
  getGroups(name, keys) {
294
364
  assert.argumentIsRequired(name, 'name', String);
295
365
  assert.argumentIsArray(keys, 'keys', Number);
@@ -301,8 +371,6 @@ module.exports = (() => {
301
371
  assert.argumentIsRequired(name, 'name', String);
302
372
  assert.argumentIsRequired(executor, 'executor', Function);
303
373
 
304
- assert.argumentIsRequired(executor, 'executor', Function);
305
-
306
374
  this._trees[name].walk(group => group.setSuspended(true), false, false);
307
375
 
308
376
  executor(this);
@@ -10,7 +10,17 @@ module.exports = (() => {
10
10
  'use strict';
11
11
 
12
12
  /**
13
+ * A grouping of {@link PositionItem} instances. The group aggregates from across
14
+ * all the positions and performs currency translation, as necessary.
15
+ *
13
16
  * @public
17
+ * @param {PositionContainer} container
18
+ * @param {PositionGroup|null} parent
19
+ * @param {Array.<PositionItem>} items
20
+ * @param {Currency} currency
21
+ * @param {String} key
22
+ * @param {String} description
23
+ * @param {Boolean=} single
14
24
  */
15
25
  class PositionGroup {
16
26
  constructor(container, parent, items, currency, key, description, single) {
@@ -36,8 +46,15 @@ module.exports = (() => {
36
46
 
37
47
  this._dataFormat.key = this._key;
38
48
  this._dataFormat.description = this._description;
39
-
49
+ this._dataFormat.newsExists = false;
40
50
  this._dataFormat.quantity = null;
51
+ this._dataFormat.basisPrice = null;
52
+
53
+ this._dataActual.key = this._key;
54
+ this._dataActual.description = this._description;
55
+ this._dataActual.newsExists = false;
56
+ this._dataActual.quantity = null;
57
+ this._dataActual.basisPrice = null;
41
58
 
42
59
  if (this._single) {
43
60
  const item = items[0];
@@ -116,35 +133,85 @@ module.exports = (() => {
116
133
 
117
134
  calculatePriceData(this, this._container.getForexQuotes(), sender, false);
118
135
  });
136
+
137
+ if (this._single) {
138
+ item.registerNewsExistsChangeHandler((exists, sender) => {
139
+ this._dataFormat.newsExists = exists;
140
+ this._dataFormat.newsExists = exists;
141
+ });
142
+ }
119
143
  });
120
144
 
121
145
  this.refresh();
122
146
  }
123
147
 
148
+ /**
149
+ * The key of the group.
150
+ *
151
+ * @public
152
+ * @returns {String}
153
+ */
124
154
  get key() {
125
155
  return this._key;
126
156
  }
127
157
 
158
+ /**
159
+ * The description of the group.
160
+ *
161
+ * @public
162
+ * @returns {String}
163
+ */
128
164
  get description() {
129
165
  return this._description;
130
166
  }
131
167
 
168
+ /**
169
+ * The {@link Currency} which all aggregated data is presented in.
170
+ *
171
+ * @public
172
+ * @returns {Currency}
173
+ */
132
174
  get currency() {
133
175
  return this._currency;
134
176
  }
135
177
 
178
+ /**
179
+ * The {@link PositionItem} instances which for which aggregated data is compiled.
180
+ *
181
+ * @public
182
+ * @returns {Currency}
183
+ */
136
184
  get items() {
137
185
  return this._items;
138
186
  }
139
187
 
188
+ /**
189
+ * The string-based, human-readable aggregated data for the group.
190
+ *
191
+ * @public
192
+ * @returns {Object}
193
+ */
140
194
  get data() {
141
195
  return this._dataFormat;
142
196
  }
143
197
 
198
+ /**
199
+ * The raw aggregated data for the group (typically {@link Decimal} instances).
200
+ *
201
+ * @public
202
+ * @returns {Object}
203
+ */
144
204
  get actual() {
145
205
  return this._dataActual;
146
206
  }
147
207
 
208
+ /**
209
+ * Indicates if the group will only contain one {@link PositionItem} -- that is,
210
+ * indicates if the group represents a single position.
211
+ *
212
+ * @public
213
+ * @returns {Boolean}
214
+ */
148
215
  get single() {
149
216
  return this._single;
150
217
  }
@@ -153,10 +220,22 @@ module.exports = (() => {
153
220
  return this._suspended;
154
221
  }
155
222
 
223
+ /**
224
+ * Indicates if the group should be excluded from higher-level aggregations.
225
+ *
226
+ * @public
227
+ * @returns {Boolean}
228
+ */
156
229
  get excluded() {
157
230
  return this._excluded;
158
231
  }
159
232
 
233
+ /**
234
+ * Causes aggregated data to be recalculated using a new exchange rate.
235
+ *
236
+ * @public
237
+ * @param {Rate} rate
238
+ */
160
239
  setForexRate(rate) {
161
240
  if (!this._bypassCurrencyTranslation) {
162
241
  this.refresh();
@@ -185,6 +264,11 @@ module.exports = (() => {
185
264
  }
186
265
  }
187
266
 
267
+ /**
268
+ * Causes all aggregated data to be recalculated.
269
+ *
270
+ * @public
271
+ */
188
272
  refresh() {
189
273
  const rates = this._container.getForexQuotes();
190
274
 
@@ -192,6 +276,12 @@ module.exports = (() => {
192
276
  calculatePriceData(this, rates, null, true);
193
277
  }
194
278
 
279
+ /**
280
+ * Causes the percent of the position, with respect to the parent container's
281
+ * total, to be recalculated.
282
+ *
283
+ * @public
284
+ */
195
285
  refreshMarketPercent() {
196
286
  calculateMarketPercent(this, this._container.getForexQuotes(), true);
197
287
  }
@@ -245,7 +335,7 @@ module.exports = (() => {
245
335
 
246
336
  const items = group._items;
247
337
 
248
- group._bypassCurrencyTranslation = items.some(item => item.currency !== currency);
338
+ group._bypassCurrencyTranslation = items.every(item => item.currency === currency);
249
339
 
250
340
  const translate = (item, value) => {
251
341
  let translated;
@@ -297,8 +387,11 @@ module.exports = (() => {
297
387
  if (group.single) {
298
388
  const item = group._items[0];
299
389
 
300
- format.quantity = formatDecimal(item.position.snapshot.open, 2);
301
- format.basisPrice = formatCurrency(item.data.basisPrice, currency);
390
+ actual.quantity = item.position.snapshot.open;
391
+ actual.basisPrice = item.data.basisPrice;
392
+
393
+ format.quantity = formatDecimal(actual.quantity, 2);
394
+ format.basisPrice = formatCurrency(actual.basisPrice, currency);
302
395
  }
303
396
  }
304
397
 
@@ -11,7 +11,15 @@ module.exports = (() => {
11
11
  'use strict';
12
12
 
13
13
  /**
14
+ * A container for a single position, which handles quote changes and
15
+ * notifies observers -- which are typically parent-level {@link PositionGroup}
16
+ * instances.
17
+ *
14
18
  * @public
19
+ * @param {Object} portfolio
20
+ * @param {Object} position
21
+ * @param {Object} currentSummary
22
+ * @param {Array.<Object>} previousSummaries
15
23
  */
16
24
  class PositionItem {
17
25
  constructor(portfolio, position, currentSummary, previousSummaries) {
@@ -27,10 +35,12 @@ module.exports = (() => {
27
35
  this._data.basis = null;
28
36
 
29
37
  this._currentQuote = null;
30
- this._previousQuote = null;
38
+
39
+ this._currentPrice = null;
40
+ this._previousPrice = null;
31
41
 
32
42
  this._data.currentPrice = null;
33
- this._data.previousPrice = null;
43
+ this._data.currentPricePrevious = null;
34
44
 
35
45
  this._data.market = null;
36
46
  this._data.marketChange = null;
@@ -50,6 +60,8 @@ module.exports = (() => {
50
60
  this._data.income = null;
51
61
  this._data.basisPrice = null;
52
62
 
63
+ this._data.newsExists = false;
64
+
53
65
  this._excluded = false;
54
66
 
55
67
  calculateStaticData(this);
@@ -57,32 +69,75 @@ module.exports = (() => {
57
69
 
58
70
  this._quoteChangedEvent = new Event(this);
59
71
  this._excludedChangeEvent = new Event(this);
72
+ this._newsExistsChangedEvent = new Event(this);
60
73
  }
61
74
 
75
+ /**
76
+ * The portfolio of the encapsulated position.
77
+ *
78
+ * @public
79
+ * @returns {Object}
80
+ */
62
81
  get portfolio() {
63
82
  return this._portfolio;
64
83
  }
65
84
 
85
+ /**
86
+ * The encapsulated position.
87
+ *
88
+ * @public
89
+ * @returns {Object}
90
+ */
66
91
  get position() {
67
92
  return this._position;
68
93
  }
69
94
 
95
+ /**
96
+ * The {@link Currency} of the encapsulated position.
97
+ *
98
+ * @public
99
+ * @returns {Object}
100
+ */
70
101
  get currency() {
71
102
  return this._currency;
72
103
  }
73
104
 
105
+ /**
106
+ * The year-to-date position summary of the encapsulated position.
107
+ *
108
+ * @public
109
+ * @returns {Object}
110
+ */
74
111
  get currentSummary() {
75
112
  return this._currentSummary;
76
113
  }
77
-
114
+
115
+ /**
116
+ * Previous year's summaries for the encapsulated position.
117
+ *
118
+ * @public
119
+ * @returns {Object}
120
+ */
78
121
  get previousSummaries() {
79
122
  return this._previousSummaries;
80
123
  }
81
124
 
125
+ /**
126
+ * Various data regarding the encapsulated position.
127
+ *
128
+ * @public
129
+ * @returns {*}
130
+ */
82
131
  get data() {
83
132
  return this._data;
84
133
  }
85
134
 
135
+ /**
136
+ * The current quote for the symbol of the encapsulated position.
137
+ *
138
+ * @public
139
+ * @returns {null|{Object}}
140
+ */
86
141
  get quote() {
87
142
  return this._currentQuote;
88
143
  }
@@ -91,13 +146,22 @@ module.exports = (() => {
91
146
  return this._excluded;
92
147
  }
93
148
 
149
+ /**
150
+ * Sets the current quote -- causing position-level data (e.g. market value) to
151
+ * be recalculated.
152
+ *
153
+ * @public
154
+ * @param {Object} quote
155
+ */
94
156
  setQuote(quote) {
95
157
  assert.argumentIsRequired(quote, 'quote', Object);
96
158
 
97
- if (this._previousQuote === null || this._previousQuote.lastPrice !== quote.lastPrice) {
159
+ if (this._currentPricePrevious !== quote.lastPrice) {
98
160
  calculatePriceData(this, quote.lastPrice);
99
161
 
100
- this._previousQuote = this._currentQuote;
162
+ this._currentPricePrevious = this._currentPrice;
163
+ this._currentPrice = quote.lastPrice;
164
+
101
165
  this._currentQuote = quote;
102
166
 
103
167
  this._quoteChangedEvent.fire(this._currentQuote);
@@ -112,6 +176,28 @@ module.exports = (() => {
112
176
  }
113
177
  }
114
178
 
179
+ /**
180
+ * Sets a flag which indicates if news article(s) exist for the encapsulated position's
181
+ * symbol.
182
+ *
183
+ * @public
184
+ * @param {Boolean} value
185
+ */
186
+ setNewsArticleExists(value) {
187
+ assert.argumentIsRequired(value, 'value', Boolean);
188
+
189
+ if (this._data.newsExists !== value) {
190
+ this._newsExistsChangedEvent.fire(this._data.newsExists = value);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Registers an observer for quote changes, which is fired after internal recalculations
196
+ * of position data are complete.
197
+ *
198
+ * @public
199
+ * @param {Function} handler
200
+ */
115
201
  registerQuoteChangeHandler(handler) {
116
202
  this._quoteChangedEvent.register(handler);
117
203
  }
@@ -120,6 +206,16 @@ module.exports = (() => {
120
206
  this._excludedChangeEvent.register(handler);
121
207
  }
122
208
 
209
+ /**
210
+ * Registers an observer changes to the status of news existence.
211
+ *
212
+ * @public
213
+ * @param {Function} handler
214
+ */
215
+ registerNewsExistsChangeHandler(handler) {
216
+ this._newsExistsChangedEvent.register(handler);
217
+ }
218
+
123
219
  toString() {
124
220
  return '[PositionItem]';
125
221
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.127",
3
+ "version": "1.0.131",
4
4
  "description": "Common classes used by the Portfolio system",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",
@@ -738,11 +738,11 @@ module.exports = (() => {
738
738
 
739
739
  /**
740
740
  * A container for positions which groups the positions into one or more
741
- * trees for aggregation and display purposes. For example, perhaps a positions
742
- * grouped first by asset class then by position is desired.
741
+ * trees for aggregation and display purposes. For example, positions could be
742
+ * grouped first by asset class then by position.
743
743
  *
744
744
  * Furthermore, the container performs aggregation (driven primarily by price
745
- * changes) for each level of grouping in the internal tree(s).
745
+ * changes) for each level of grouping.
746
746
  *
747
747
  * @public
748
748
  * @param {Array.<PositionTreeDefinition>} definitions
@@ -935,10 +935,14 @@ module.exports = (() => {
935
935
  }, { });
936
936
  }
937
937
 
938
- get defaultCurrency() {
939
- return this._defaultCurrency;
940
- }
941
-
938
+ /**
939
+ * Returns a distinct list of all symbols used by the positions
940
+ * within the container.
941
+ *
942
+ * @public
943
+ * @param {Boolean} display - If true, all "display" symbols are returned; otherwise Barchart symbols are returned.
944
+ * @returns {Array.<String>}
945
+ */
942
946
  getPositionSymbols(display) {
943
947
  const symbols = this._items.reduce((symbols, item) => {
944
948
  const position = item.position;
@@ -961,6 +965,14 @@ module.exports = (() => {
961
965
  return array.unique(symbols);
962
966
  }
963
967
 
968
+ /**
969
+ * Updates the quote for a single symbol; causing updates to any grouping
970
+ * level that contains the position(s) for the symbol.
971
+ *
972
+ * @public
973
+ * @param {String} symbol
974
+ * @param {Object} quote
975
+ */
964
976
  setPositionQuote(symbol, quote) {
965
977
  assert.argumentIsRequired(symbol, 'symbol', String);
966
978
  assert.argumentIsRequired(quote, 'quote', Object);
@@ -970,14 +982,34 @@ module.exports = (() => {
970
982
  }
971
983
  }
972
984
 
985
+ /**
986
+ * Returns all forex symbols that are required to do currency translations.
987
+ *
988
+ * @public
989
+ * @returns {Array.<String>}
990
+ */
973
991
  getForexSymbols() {
974
992
  return this._forexSymbols;
975
993
  }
976
994
 
995
+ /**
996
+ * Returns all current forex quotes.
997
+ *
998
+ * @public
999
+ * @returns {Array.<Object>}
1000
+ */
977
1001
  getForexQuotes() {
978
1002
  return this._forexQuotes;
979
1003
  }
980
1004
 
1005
+ /**
1006
+ * Updates the forex quote for a single currency pair; causing updates to
1007
+ * any grouping level that contains that requires translation.
1008
+ *
1009
+ * @public
1010
+ * @param {String} symbol
1011
+ * @param {Object} quote
1012
+ */
981
1013
  setForexQuote(symbol, quote) {
982
1014
  assert.argumentIsRequired(symbol, 'symbol', String);
983
1015
  assert.argumentIsRequired(quote, 'quote', Object);
@@ -995,10 +1027,40 @@ module.exports = (() => {
995
1027
  Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
996
1028
  }
997
1029
 
998
- setPositionFundamentals(data) {
1030
+ /**
1031
+ * Updates fundamental data for a single symbol.
1032
+ *
1033
+ * @public
1034
+ * @param {String} symbol
1035
+ * @param {Object} data
1036
+ */
1037
+ setPositionFundamentalData(symbol, data) {
999
1038
  return;
1000
1039
  }
1001
1040
 
1041
+ /**
1042
+ * Indicates if a news article exists for a symbol.
1043
+ *
1044
+ * @public
1045
+ * @param {String} symbol
1046
+ * @param {Boolean} exists
1047
+ */
1048
+ setNewsArticleExists(symbol, exists) {
1049
+ assert.argumentIsRequired(symbol, 'symbol', String);
1050
+ assert.argumentIsRequired(exists, 'exists', Boolean);
1051
+
1052
+ if (this._symbols.hasOwnProperty(symbol)) {
1053
+ this._symbols[symbol].forEach(item => item.setNewsArticleExists(exists));
1054
+ }
1055
+ }
1056
+
1057
+ /**
1058
+ * Returns a single level of grouping from one of the internal trees.
1059
+ *
1060
+ * @param {String} name
1061
+ * @param {Array.<String> keys
1062
+ * @returns {PositionGroup}
1063
+ */
1002
1064
  getGroup(name, keys) {
1003
1065
  assert.argumentIsRequired(name, 'name', String);
1004
1066
  assert.argumentIsArray(keys, 'keys', Number);
@@ -1006,6 +1068,14 @@ module.exports = (() => {
1006
1068
  return findNode(this._trees[name], keys).getValue();
1007
1069
  }
1008
1070
 
1071
+ /**
1072
+ * Returns all child groups from a level of grouping within one of
1073
+ * the internal trees.
1074
+ *
1075
+ * @param {String} name
1076
+ * @param {Array.<String> keys
1077
+ * @returns {Array.<PositionGroup>}
1078
+ */
1009
1079
  getGroups(name, keys) {
1010
1080
  assert.argumentIsRequired(name, 'name', String);
1011
1081
  assert.argumentIsArray(keys, 'keys', Number);
@@ -1017,8 +1087,6 @@ module.exports = (() => {
1017
1087
  assert.argumentIsRequired(name, 'name', String);
1018
1088
  assert.argumentIsRequired(executor, 'executor', Function);
1019
1089
 
1020
- assert.argumentIsRequired(executor, 'executor', Function);
1021
-
1022
1090
  this._trees[name].walk(group => group.setSuspended(true), false, false);
1023
1091
 
1024
1092
  executor(this);
@@ -1072,7 +1140,17 @@ module.exports = (() => {
1072
1140
  'use strict';
1073
1141
 
1074
1142
  /**
1143
+ * A grouping of {@link PositionItem} instances. The group aggregates from across
1144
+ * all the positions and performs currency translation, as necessary.
1145
+ *
1075
1146
  * @public
1147
+ * @param {PositionContainer} container
1148
+ * @param {PositionGroup|null} parent
1149
+ * @param {Array.<PositionItem>} items
1150
+ * @param {Currency} currency
1151
+ * @param {String} key
1152
+ * @param {String} description
1153
+ * @param {Boolean=} single
1076
1154
  */
1077
1155
  class PositionGroup {
1078
1156
  constructor(container, parent, items, currency, key, description, single) {
@@ -1098,8 +1176,15 @@ module.exports = (() => {
1098
1176
 
1099
1177
  this._dataFormat.key = this._key;
1100
1178
  this._dataFormat.description = this._description;
1101
-
1179
+ this._dataFormat.newsExists = false;
1102
1180
  this._dataFormat.quantity = null;
1181
+ this._dataFormat.basisPrice = null;
1182
+
1183
+ this._dataActual.key = this._key;
1184
+ this._dataActual.description = this._description;
1185
+ this._dataActual.newsExists = false;
1186
+ this._dataActual.quantity = null;
1187
+ this._dataActual.basisPrice = null;
1103
1188
 
1104
1189
  if (this._single) {
1105
1190
  const item = items[0];
@@ -1178,35 +1263,85 @@ module.exports = (() => {
1178
1263
 
1179
1264
  calculatePriceData(this, this._container.getForexQuotes(), sender, false);
1180
1265
  });
1266
+
1267
+ if (this._single) {
1268
+ item.registerNewsExistsChangeHandler((exists, sender) => {
1269
+ this._dataFormat.newsExists = exists;
1270
+ this._dataFormat.newsExists = exists;
1271
+ });
1272
+ }
1181
1273
  });
1182
1274
 
1183
1275
  this.refresh();
1184
1276
  }
1185
1277
 
1278
+ /**
1279
+ * The key of the group.
1280
+ *
1281
+ * @public
1282
+ * @returns {String}
1283
+ */
1186
1284
  get key() {
1187
1285
  return this._key;
1188
1286
  }
1189
1287
 
1288
+ /**
1289
+ * The description of the group.
1290
+ *
1291
+ * @public
1292
+ * @returns {String}
1293
+ */
1190
1294
  get description() {
1191
1295
  return this._description;
1192
1296
  }
1193
1297
 
1298
+ /**
1299
+ * The {@link Currency} which all aggregated data is presented in.
1300
+ *
1301
+ * @public
1302
+ * @returns {Currency}
1303
+ */
1194
1304
  get currency() {
1195
1305
  return this._currency;
1196
1306
  }
1197
1307
 
1308
+ /**
1309
+ * The {@link PositionItem} instances which for which aggregated data is compiled.
1310
+ *
1311
+ * @public
1312
+ * @returns {Currency}
1313
+ */
1198
1314
  get items() {
1199
1315
  return this._items;
1200
1316
  }
1201
1317
 
1318
+ /**
1319
+ * The string-based, human-readable aggregated data for the group.
1320
+ *
1321
+ * @public
1322
+ * @returns {Object}
1323
+ */
1202
1324
  get data() {
1203
1325
  return this._dataFormat;
1204
1326
  }
1205
1327
 
1328
+ /**
1329
+ * The raw aggregated data for the group (typically {@link Decimal} instances).
1330
+ *
1331
+ * @public
1332
+ * @returns {Object}
1333
+ */
1206
1334
  get actual() {
1207
1335
  return this._dataActual;
1208
1336
  }
1209
1337
 
1338
+ /**
1339
+ * Indicates if the group will only contain one {@link PositionItem} -- that is,
1340
+ * indicates if the group represents a single position.
1341
+ *
1342
+ * @public
1343
+ * @returns {Boolean}
1344
+ */
1210
1345
  get single() {
1211
1346
  return this._single;
1212
1347
  }
@@ -1215,10 +1350,22 @@ module.exports = (() => {
1215
1350
  return this._suspended;
1216
1351
  }
1217
1352
 
1353
+ /**
1354
+ * Indicates if the group should be excluded from higher-level aggregations.
1355
+ *
1356
+ * @public
1357
+ * @returns {Boolean}
1358
+ */
1218
1359
  get excluded() {
1219
1360
  return this._excluded;
1220
1361
  }
1221
1362
 
1363
+ /**
1364
+ * Causes aggregated data to be recalculated using a new exchange rate.
1365
+ *
1366
+ * @public
1367
+ * @param {Rate} rate
1368
+ */
1222
1369
  setForexRate(rate) {
1223
1370
  if (!this._bypassCurrencyTranslation) {
1224
1371
  this.refresh();
@@ -1247,6 +1394,11 @@ module.exports = (() => {
1247
1394
  }
1248
1395
  }
1249
1396
 
1397
+ /**
1398
+ * Causes all aggregated data to be recalculated.
1399
+ *
1400
+ * @public
1401
+ */
1250
1402
  refresh() {
1251
1403
  const rates = this._container.getForexQuotes();
1252
1404
 
@@ -1254,6 +1406,12 @@ module.exports = (() => {
1254
1406
  calculatePriceData(this, rates, null, true);
1255
1407
  }
1256
1408
 
1409
+ /**
1410
+ * Causes the percent of the position, with respect to the parent container's
1411
+ * total, to be recalculated.
1412
+ *
1413
+ * @public
1414
+ */
1257
1415
  refreshMarketPercent() {
1258
1416
  calculateMarketPercent(this, this._container.getForexQuotes(), true);
1259
1417
  }
@@ -1307,7 +1465,7 @@ module.exports = (() => {
1307
1465
 
1308
1466
  const items = group._items;
1309
1467
 
1310
- group._bypassCurrencyTranslation = items.some(item => item.currency !== currency);
1468
+ group._bypassCurrencyTranslation = items.every(item => item.currency === currency);
1311
1469
 
1312
1470
  const translate = (item, value) => {
1313
1471
  let translated;
@@ -1359,8 +1517,11 @@ module.exports = (() => {
1359
1517
  if (group.single) {
1360
1518
  const item = group._items[0];
1361
1519
 
1362
- format.quantity = formatDecimal(item.position.snapshot.open, 2);
1363
- format.basisPrice = formatCurrency(item.data.basisPrice, currency);
1520
+ actual.quantity = item.position.snapshot.open;
1521
+ actual.basisPrice = item.data.basisPrice;
1522
+
1523
+ format.quantity = formatDecimal(actual.quantity, 2);
1524
+ format.basisPrice = formatCurrency(actual.basisPrice, currency);
1364
1525
  }
1365
1526
  }
1366
1527
 
@@ -1534,7 +1695,15 @@ module.exports = (() => {
1534
1695
  'use strict';
1535
1696
 
1536
1697
  /**
1698
+ * A container for a single position, which handles quote changes and
1699
+ * notifies observers -- which are typically parent-level {@link PositionGroup}
1700
+ * instances.
1701
+ *
1537
1702
  * @public
1703
+ * @param {Object} portfolio
1704
+ * @param {Object} position
1705
+ * @param {Object} currentSummary
1706
+ * @param {Array.<Object>} previousSummaries
1538
1707
  */
1539
1708
  class PositionItem {
1540
1709
  constructor(portfolio, position, currentSummary, previousSummaries) {
@@ -1550,10 +1719,12 @@ module.exports = (() => {
1550
1719
  this._data.basis = null;
1551
1720
 
1552
1721
  this._currentQuote = null;
1553
- this._previousQuote = null;
1722
+
1723
+ this._currentPrice = null;
1724
+ this._previousPrice = null;
1554
1725
 
1555
1726
  this._data.currentPrice = null;
1556
- this._data.previousPrice = null;
1727
+ this._data.currentPricePrevious = null;
1557
1728
 
1558
1729
  this._data.market = null;
1559
1730
  this._data.marketChange = null;
@@ -1573,6 +1744,8 @@ module.exports = (() => {
1573
1744
  this._data.income = null;
1574
1745
  this._data.basisPrice = null;
1575
1746
 
1747
+ this._data.newsExists = false;
1748
+
1576
1749
  this._excluded = false;
1577
1750
 
1578
1751
  calculateStaticData(this);
@@ -1580,32 +1753,75 @@ module.exports = (() => {
1580
1753
 
1581
1754
  this._quoteChangedEvent = new Event(this);
1582
1755
  this._excludedChangeEvent = new Event(this);
1756
+ this._newsExistsChangedEvent = new Event(this);
1583
1757
  }
1584
1758
 
1759
+ /**
1760
+ * The portfolio of the encapsulated position.
1761
+ *
1762
+ * @public
1763
+ * @returns {Object}
1764
+ */
1585
1765
  get portfolio() {
1586
1766
  return this._portfolio;
1587
1767
  }
1588
1768
 
1769
+ /**
1770
+ * The encapsulated position.
1771
+ *
1772
+ * @public
1773
+ * @returns {Object}
1774
+ */
1589
1775
  get position() {
1590
1776
  return this._position;
1591
1777
  }
1592
1778
 
1779
+ /**
1780
+ * The {@link Currency} of the encapsulated position.
1781
+ *
1782
+ * @public
1783
+ * @returns {Object}
1784
+ */
1593
1785
  get currency() {
1594
1786
  return this._currency;
1595
1787
  }
1596
1788
 
1789
+ /**
1790
+ * The year-to-date position summary of the encapsulated position.
1791
+ *
1792
+ * @public
1793
+ * @returns {Object}
1794
+ */
1597
1795
  get currentSummary() {
1598
1796
  return this._currentSummary;
1599
1797
  }
1600
-
1798
+
1799
+ /**
1800
+ * Previous year's summaries for the encapsulated position.
1801
+ *
1802
+ * @public
1803
+ * @returns {Object}
1804
+ */
1601
1805
  get previousSummaries() {
1602
1806
  return this._previousSummaries;
1603
1807
  }
1604
1808
 
1809
+ /**
1810
+ * Various data regarding the encapsulated position.
1811
+ *
1812
+ * @public
1813
+ * @returns {*}
1814
+ */
1605
1815
  get data() {
1606
1816
  return this._data;
1607
1817
  }
1608
1818
 
1819
+ /**
1820
+ * The current quote for the symbol of the encapsulated position.
1821
+ *
1822
+ * @public
1823
+ * @returns {null|{Object}}
1824
+ */
1609
1825
  get quote() {
1610
1826
  return this._currentQuote;
1611
1827
  }
@@ -1614,13 +1830,22 @@ module.exports = (() => {
1614
1830
  return this._excluded;
1615
1831
  }
1616
1832
 
1833
+ /**
1834
+ * Sets the current quote -- causing position-level data (e.g. market value) to
1835
+ * be recalculated.
1836
+ *
1837
+ * @public
1838
+ * @param {Object} quote
1839
+ */
1617
1840
  setQuote(quote) {
1618
1841
  assert.argumentIsRequired(quote, 'quote', Object);
1619
1842
 
1620
- if (this._previousQuote === null || this._previousQuote.lastPrice !== quote.lastPrice) {
1843
+ if (this._currentPricePrevious !== quote.lastPrice) {
1621
1844
  calculatePriceData(this, quote.lastPrice);
1622
1845
 
1623
- this._previousQuote = this._currentQuote;
1846
+ this._currentPricePrevious = this._currentPrice;
1847
+ this._currentPrice = quote.lastPrice;
1848
+
1624
1849
  this._currentQuote = quote;
1625
1850
 
1626
1851
  this._quoteChangedEvent.fire(this._currentQuote);
@@ -1635,6 +1860,28 @@ module.exports = (() => {
1635
1860
  }
1636
1861
  }
1637
1862
 
1863
+ /**
1864
+ * Sets a flag which indicates if news article(s) exist for the encapsulated position's
1865
+ * symbol.
1866
+ *
1867
+ * @public
1868
+ * @param {Boolean} value
1869
+ */
1870
+ setNewsArticleExists(value) {
1871
+ assert.argumentIsRequired(value, 'value', Boolean);
1872
+
1873
+ if (this._data.newsExists !== value) {
1874
+ this._newsExistsChangedEvent.fire(this._data.newsExists = value);
1875
+ }
1876
+ }
1877
+
1878
+ /**
1879
+ * Registers an observer for quote changes, which is fired after internal recalculations
1880
+ * of position data are complete.
1881
+ *
1882
+ * @public
1883
+ * @param {Function} handler
1884
+ */
1638
1885
  registerQuoteChangeHandler(handler) {
1639
1886
  this._quoteChangedEvent.register(handler);
1640
1887
  }
@@ -1643,6 +1890,16 @@ module.exports = (() => {
1643
1890
  this._excludedChangeEvent.register(handler);
1644
1891
  }
1645
1892
 
1893
+ /**
1894
+ * Registers an observer changes to the status of news existence.
1895
+ *
1896
+ * @public
1897
+ * @param {Function} handler
1898
+ */
1899
+ registerNewsExistsChangeHandler(handler) {
1900
+ this._newsExistsChangedEvent.register(handler);
1901
+ }
1902
+
1646
1903
  toString() {
1647
1904
  return '[PositionItem]';
1648
1905
  }