@barchart/portfolio-api-common 1.0.130 → 1.0.134

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
@@ -108,6 +108,20 @@ module.exports = (() => {
108
108
  return map;
109
109
  }, { });
110
110
 
111
+ this._symbolsDisplay = this._items.reduce((map, item) => {
112
+ const symbol = extractSymbolForDisplay(item.position);
113
+
114
+ if (symbol) {
115
+ if (!map.hasOwnProperty(symbol)) {
116
+ map[symbol] = [ ];
117
+ }
118
+
119
+ map[symbol].push(item);
120
+ }
121
+
122
+ return map;
123
+ }, { });
124
+
111
125
  this._currencies = this._items.reduce((map, item) => {
112
126
  const position = item.position;
113
127
 
@@ -219,10 +233,14 @@ module.exports = (() => {
219
233
  }, { });
220
234
  }
221
235
 
222
- get defaultCurrency() {
223
- return this._defaultCurrency;
224
- }
225
-
236
+ /**
237
+ * Returns a distinct list of all symbols used by the positions
238
+ * within the container.
239
+ *
240
+ * @public
241
+ * @param {Boolean} display - If true, all "display" symbols are returned; otherwise Barchart symbols are returned.
242
+ * @returns {Array.<String>}
243
+ */
226
244
  getPositionSymbols(display) {
227
245
  const symbols = this._items.reduce((symbols, item) => {
228
246
  const position = item.position;
@@ -245,6 +263,14 @@ module.exports = (() => {
245
263
  return array.unique(symbols);
246
264
  }
247
265
 
266
+ /**
267
+ * Updates the quote for a single symbol; causing updates to any grouping
268
+ * level that contains the position(s) for the symbol.
269
+ *
270
+ * @public
271
+ * @param {String} symbol
272
+ * @param {Object} quote
273
+ */
248
274
  setPositionQuote(symbol, quote) {
249
275
  assert.argumentIsRequired(symbol, 'symbol', String);
250
276
  assert.argumentIsRequired(quote, 'quote', Object);
@@ -254,14 +280,34 @@ module.exports = (() => {
254
280
  }
255
281
  }
256
282
 
283
+ /**
284
+ * Returns all forex symbols that are required to do currency translations.
285
+ *
286
+ * @public
287
+ * @returns {Array.<String>}
288
+ */
257
289
  getForexSymbols() {
258
290
  return this._forexSymbols;
259
291
  }
260
292
 
293
+ /**
294
+ * Returns all current forex quotes.
295
+ *
296
+ * @public
297
+ * @returns {Array.<Object>}
298
+ */
261
299
  getForexQuotes() {
262
300
  return this._forexQuotes;
263
301
  }
264
302
 
303
+ /**
304
+ * Updates the forex quote for a single currency pair; causing updates to
305
+ * any grouping level that contains that requires translation.
306
+ *
307
+ * @public
308
+ * @param {String} symbol
309
+ * @param {Object} quote
310
+ */
265
311
  setForexQuote(symbol, quote) {
266
312
  assert.argumentIsRequired(symbol, 'symbol', String);
267
313
  assert.argumentIsRequired(quote, 'quote', Object);
@@ -279,19 +325,55 @@ module.exports = (() => {
279
325
  Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
280
326
  }
281
327
 
282
- setPositionFundamentals(symbol, data) {
283
- return;
328
+ /**
329
+ * Updates fundamental data for a single symbol.
330
+ *
331
+ * @public
332
+ * @param {String} symbol
333
+ * @param {Object} data
334
+ */
335
+ setPositionFundamentalData(symbol, data) {
336
+ assert.argumentIsRequired(symbol, 'symbol', String);
337
+ assert.argumentIsRequired(data, 'data', Object);
338
+
339
+ if (this._symbols.hasOwnProperty(symbol)) {
340
+ this._symbols[symbol].forEach(item => item.setPositionFundamentalData(data));
341
+ }
284
342
  }
285
343
 
286
- setNewsArticleExists(symbol, exists) {
344
+ /**
345
+ * Indicates if a news article exists for a symbol.
346
+ *
347
+ * @public
348
+ * @param {String} symbol
349
+ * @param {Boolean} display
350
+ * @param {Boolean} exists
351
+ */
352
+ setNewsArticleExists(symbol, display, exists) {
287
353
  assert.argumentIsRequired(symbol, 'symbol', String);
354
+ assert.argumentIsRequired(display, 'display', Boolean);
288
355
  assert.argumentIsRequired(exists, 'exists', Boolean);
289
356
 
290
- if (this._symbols.hasOwnProperty(symbol)) {
291
- this._symbols[symbol].forEach(item => item.setNewsArticleExists(exists));
357
+ let map;
358
+
359
+ if (display) {
360
+ map = this._symbolsDisplay;
361
+ } else {
362
+ map = this._symbols;
363
+ }
364
+
365
+ if (map.hasOwnProperty(symbol)) {
366
+ map[symbol].forEach(item => item.setNewsArticleExists(exists));
292
367
  }
293
368
  }
294
369
 
370
+ /**
371
+ * Returns a single level of grouping from one of the internal trees.
372
+ *
373
+ * @param {String} name
374
+ * @param {Array.<String> keys
375
+ * @returns {PositionGroup}
376
+ */
295
377
  getGroup(name, keys) {
296
378
  assert.argumentIsRequired(name, 'name', String);
297
379
  assert.argumentIsArray(keys, 'keys', Number);
@@ -299,6 +381,14 @@ module.exports = (() => {
299
381
  return findNode(this._trees[name], keys).getValue();
300
382
  }
301
383
 
384
+ /**
385
+ * Returns all child groups from a level of grouping within one of
386
+ * the internal trees.
387
+ *
388
+ * @param {String} name
389
+ * @param {Array.<String> keys
390
+ * @returns {Array.<PositionGroup>}
391
+ */
302
392
  getGroups(name, keys) {
303
393
  assert.argumentIsRequired(name, 'name', String);
304
394
  assert.argumentIsArray(keys, 'keys', Number);
@@ -310,8 +400,6 @@ module.exports = (() => {
310
400
  assert.argumentIsRequired(name, 'name', String);
311
401
  assert.argumentIsRequired(executor, 'executor', Function);
312
402
 
313
- assert.argumentIsRequired(executor, 'executor', Function);
314
-
315
403
  this._trees[name].walk(group => group.setSuspended(true), false, false);
316
404
 
317
405
  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) {
@@ -30,6 +40,7 @@ module.exports = (() => {
30
40
  this._suspended = false;
31
41
 
32
42
  this._marketPercentChangeEvent = new Event(this);
43
+ this._excludedChangeEvent = new Event(this);
33
44
 
34
45
  this._dataFormat = { };
35
46
  this._dataActual = { };
@@ -52,10 +63,12 @@ module.exports = (() => {
52
63
  this._dataFormat.portfolio = item.portfolio.portfolio;
53
64
  this._dataFormat.position = item.position.position;
54
65
  this._dataFormat.instrument = item.position.instrument;
66
+ this._dataFormat.fundamental = item.fundamental || { };
55
67
  } else {
56
68
  this._dataFormat.portfolio = null;
57
69
  this._dataFormat.position = null;
58
70
  this._dataFormat.instrument = null;
71
+ this._dataFormat.fundamental = { };
59
72
  }
60
73
 
61
74
  this._dataFormat.quoteLast = null;
@@ -126,8 +139,12 @@ module.exports = (() => {
126
139
 
127
140
  if (this._single) {
128
141
  item.registerNewsExistsChangeHandler((exists, sender) => {
142
+ this._dataActual.newsExists = exists;
129
143
  this._dataFormat.newsExists = exists;
130
- this._dataFormat.newsExists = exists;
144
+ });
145
+
146
+ item._fundamentalDataChangeEvent((data, sender) => {
147
+ this._dataFormat.fundamental = data;
131
148
  });
132
149
  }
133
150
  });
@@ -135,30 +152,73 @@ module.exports = (() => {
135
152
  this.refresh();
136
153
  }
137
154
 
155
+ /**
156
+ * The key of the group.
157
+ *
158
+ * @public
159
+ * @returns {String}
160
+ */
138
161
  get key() {
139
162
  return this._key;
140
163
  }
141
164
 
165
+ /**
166
+ * The description of the group.
167
+ *
168
+ * @public
169
+ * @returns {String}
170
+ */
142
171
  get description() {
143
172
  return this._description;
144
173
  }
145
174
 
175
+ /**
176
+ * The {@link Currency} which all aggregated data is presented in.
177
+ *
178
+ * @public
179
+ * @returns {Currency}
180
+ */
146
181
  get currency() {
147
182
  return this._currency;
148
183
  }
149
184
 
185
+ /**
186
+ * The {@link PositionItem} instances which for which aggregated data is compiled.
187
+ *
188
+ * @public
189
+ * @returns {Currency}
190
+ */
150
191
  get items() {
151
192
  return this._items;
152
193
  }
153
194
 
195
+ /**
196
+ * The string-based, human-readable aggregated data for the group.
197
+ *
198
+ * @public
199
+ * @returns {Object}
200
+ */
154
201
  get data() {
155
202
  return this._dataFormat;
156
203
  }
157
204
 
205
+ /**
206
+ * The raw aggregated data for the group (typically {@link Decimal} instances).
207
+ *
208
+ * @public
209
+ * @returns {Object}
210
+ */
158
211
  get actual() {
159
212
  return this._dataActual;
160
213
  }
161
214
 
215
+ /**
216
+ * Indicates if the group will only contain one {@link PositionItem} -- that is,
217
+ * indicates if the group represents a single position.
218
+ *
219
+ * @public
220
+ * @returns {Boolean}
221
+ */
162
222
  get single() {
163
223
  return this._single;
164
224
  }
@@ -167,10 +227,22 @@ module.exports = (() => {
167
227
  return this._suspended;
168
228
  }
169
229
 
230
+ /**
231
+ * Indicates if the group should be excluded from higher-level aggregations.
232
+ *
233
+ * @public
234
+ * @returns {Boolean}
235
+ */
170
236
  get excluded() {
171
237
  return this._excluded;
172
238
  }
173
239
 
240
+ /**
241
+ * Causes aggregated data to be recalculated using a new exchange rate.
242
+ *
243
+ * @public
244
+ * @param {Rate} rate
245
+ */
174
246
  setForexRate(rate) {
175
247
  if (!this._bypassCurrencyTranslation) {
176
248
  this.refresh();
@@ -181,11 +253,7 @@ module.exports = (() => {
181
253
  assert.argumentIsRequired(value, 'value', Boolean);
182
254
 
183
255
  if (this._excluded !== value) {
184
- this._container.startTransaction(() => {
185
- this._items.forEach((item) => {
186
- item.setExcluded(value);
187
- });
188
- });
256
+ this._excludedChangeEvent(this._excluded = value);
189
257
  }
190
258
  }
191
259
 
@@ -199,6 +267,11 @@ module.exports = (() => {
199
267
  }
200
268
  }
201
269
 
270
+ /**
271
+ * Causes all aggregated data to be recalculated.
272
+ *
273
+ * @public
274
+ */
202
275
  refresh() {
203
276
  const rates = this._container.getForexQuotes();
204
277
 
@@ -206,6 +279,12 @@ module.exports = (() => {
206
279
  calculatePriceData(this, rates, null, true);
207
280
  }
208
281
 
282
+ /**
283
+ * Causes the percent of the position, with respect to the parent container's
284
+ * total, to be recalculated.
285
+ *
286
+ * @public
287
+ */
209
288
  refreshMarketPercent() {
210
289
  calculateMarketPercent(this, this._container.getForexQuotes(), true);
211
290
  }
@@ -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;
@@ -40,7 +50,7 @@ module.exports = (() => {
40
50
 
41
51
  this._data.unrealized = null;
42
52
  this._data.unrealizedChange = null;
43
-
53
+
44
54
  this._data.summaryTotalCurrent = null;
45
55
  this._data.summaryTotalCurrentChange = null;
46
56
 
@@ -51,70 +61,127 @@ module.exports = (() => {
51
61
  this._data.basisPrice = null;
52
62
 
53
63
  this._data.newsExists = false;
54
-
55
- this._excluded = false;
64
+ this._data.fundamental = { };
56
65
 
57
66
  calculateStaticData(this);
58
67
  calculatePriceData(this, null);
59
68
 
60
69
  this._quoteChangedEvent = new Event(this);
61
- this._excludedChangeEvent = new Event(this);
62
70
  this._newsExistsChangedEvent = new Event(this);
71
+ this._fundamentalDataChangeEvent = new Event(this);
63
72
  }
64
73
 
74
+ /**
75
+ * The portfolio of the encapsulated position.
76
+ *
77
+ * @public
78
+ * @returns {Object}
79
+ */
65
80
  get portfolio() {
66
81
  return this._portfolio;
67
82
  }
68
83
 
84
+ /**
85
+ * The encapsulated position.
86
+ *
87
+ * @public
88
+ * @returns {Object}
89
+ */
69
90
  get position() {
70
91
  return this._position;
71
92
  }
72
93
 
94
+ /**
95
+ * The {@link Currency} of the encapsulated position.
96
+ *
97
+ * @public
98
+ * @returns {Object}
99
+ */
73
100
  get currency() {
74
101
  return this._currency;
75
102
  }
76
103
 
104
+ /**
105
+ * The year-to-date position summary of the encapsulated position.
106
+ *
107
+ * @public
108
+ * @returns {Object}
109
+ */
77
110
  get currentSummary() {
78
111
  return this._currentSummary;
79
112
  }
80
-
113
+
114
+ /**
115
+ * Previous year's summaries for the encapsulated position.
116
+ *
117
+ * @public
118
+ * @returns {Object}
119
+ */
81
120
  get previousSummaries() {
82
121
  return this._previousSummaries;
83
122
  }
84
123
 
124
+ /**
125
+ * Various data regarding the encapsulated position.
126
+ *
127
+ * @public
128
+ * @returns {*}
129
+ */
85
130
  get data() {
86
131
  return this._data;
87
132
  }
88
133
 
134
+ /**
135
+ * The current quote for the symbol of the encapsulated position.
136
+ *
137
+ * @public
138
+ * @returns {null|{Object}}
139
+ */
89
140
  get quote() {
90
141
  return this._currentQuote;
91
142
  }
92
143
 
93
- get excluded() {
94
- return this._excluded;
95
- }
96
-
144
+ /**
145
+ * Sets the current quote -- causing position-level data (e.g. market value) to
146
+ * be recalculated.
147
+ *
148
+ * @public
149
+ * @param {Object} quote
150
+ */
97
151
  setQuote(quote) {
98
152
  assert.argumentIsRequired(quote, 'quote', Object);
99
153
 
100
- if (this._previousQuote === null || this._previousQuote.lastPrice !== quote.lastPrice) {
154
+ if (this._currentPricePrevious !== quote.lastPrice) {
101
155
  calculatePriceData(this, quote.lastPrice);
102
156
 
103
- this._previousQuote = this._currentQuote;
157
+ this._currentPricePrevious = this._currentPrice;
158
+ this._currentPrice = quote.lastPrice;
159
+
104
160
  this._currentQuote = quote;
105
161
 
106
162
  this._quoteChangedEvent.fire(this._currentQuote);
107
163
  }
108
164
  }
109
165
 
110
- setExcluded(value) {
111
- assert.argumentIsRequired(value, 'value', Boolean);
166
+ /**
167
+ * Sets fundamental data for the position.
168
+ *
169
+ * @public
170
+ * @param {Object} data
171
+ */
172
+ setPositionFundamentalData(data) {
173
+ assert.argumentIsRequired(data, 'data', Object);
112
174
 
113
- if (this._excluded !== value) {
114
- this._excludedChangeEvent.fire(this._excluded = value);
115
- }
175
+ this._fundamentalDataChangeEvent(this._data.fundamental = data);
116
176
  }
117
177
 
178
+ /**
179
+ * Sets a flag which indicates if news article(s) exist for the encapsulated position's
180
+ * symbol.
181
+ *
182
+ * @public
183
+ * @param {Boolean} value
184
+ */
118
185
  setNewsArticleExists(value) {
119
186
  assert.argumentIsRequired(value, 'value', Boolean);
120
187
 
@@ -123,14 +190,33 @@ module.exports = (() => {
123
190
  }
124
191
  }
125
192
 
193
+ /**
194
+ * Registers an observer for quote changes, which is fired after internal recalculations
195
+ * of position data are complete.
196
+ *
197
+ * @public
198
+ * @param {Function} handler
199
+ */
126
200
  registerQuoteChangeHandler(handler) {
127
201
  this._quoteChangedEvent.register(handler);
128
202
  }
129
203
 
130
- registerExcludedChangeHandler(handler) {
131
- this._excludedChangeEvent.register(handler);
204
+ /**
205
+ * Registers an observer for fundamental data changes.
206
+ *
207
+ * @public
208
+ * @param {Function} handler
209
+ */
210
+ registerFundamentalDataChangeHandler(handler) {
211
+ this._fundamentalDataChangeEvent.register(handler);
132
212
  }
133
213
 
214
+ /**
215
+ * Registers an observer changes to the status of news existence.
216
+ *
217
+ * @public
218
+ * @param {Function} handler
219
+ */
134
220
  registerNewsExistsChangeHandler(handler) {
135
221
  this._newsExistsChangedEvent.register(handler);
136
222
  }
@@ -259,10 +345,10 @@ module.exports = (() => {
259
345
  data.unrealizedChange = Decimal.ZERO;
260
346
  }
261
347
  }
262
-
348
+
263
349
  function calculateSummaryTotal(summary) {
264
350
  let returnRef;
265
-
351
+
266
352
  if (summary) {
267
353
  const period = summary.period;
268
354
 
@@ -270,7 +356,7 @@ module.exports = (() => {
270
356
  } else {
271
357
  returnRef = Decimal.ZERO;
272
358
  }
273
-
359
+
274
360
  return returnRef;
275
361
  }
276
362
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.130",
3
+ "version": "1.0.134",
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
@@ -824,6 +824,20 @@ module.exports = (() => {
824
824
  return map;
825
825
  }, { });
826
826
 
827
+ this._symbolsDisplay = this._items.reduce((map, item) => {
828
+ const symbol = extractSymbolForDisplay(item.position);
829
+
830
+ if (symbol) {
831
+ if (!map.hasOwnProperty(symbol)) {
832
+ map[symbol] = [ ];
833
+ }
834
+
835
+ map[symbol].push(item);
836
+ }
837
+
838
+ return map;
839
+ }, { });
840
+
827
841
  this._currencies = this._items.reduce((map, item) => {
828
842
  const position = item.position;
829
843
 
@@ -935,10 +949,14 @@ module.exports = (() => {
935
949
  }, { });
936
950
  }
937
951
 
938
- get defaultCurrency() {
939
- return this._defaultCurrency;
940
- }
941
-
952
+ /**
953
+ * Returns a distinct list of all symbols used by the positions
954
+ * within the container.
955
+ *
956
+ * @public
957
+ * @param {Boolean} display - If true, all "display" symbols are returned; otherwise Barchart symbols are returned.
958
+ * @returns {Array.<String>}
959
+ */
942
960
  getPositionSymbols(display) {
943
961
  const symbols = this._items.reduce((symbols, item) => {
944
962
  const position = item.position;
@@ -961,6 +979,14 @@ module.exports = (() => {
961
979
  return array.unique(symbols);
962
980
  }
963
981
 
982
+ /**
983
+ * Updates the quote for a single symbol; causing updates to any grouping
984
+ * level that contains the position(s) for the symbol.
985
+ *
986
+ * @public
987
+ * @param {String} symbol
988
+ * @param {Object} quote
989
+ */
964
990
  setPositionQuote(symbol, quote) {
965
991
  assert.argumentIsRequired(symbol, 'symbol', String);
966
992
  assert.argumentIsRequired(quote, 'quote', Object);
@@ -970,14 +996,34 @@ module.exports = (() => {
970
996
  }
971
997
  }
972
998
 
999
+ /**
1000
+ * Returns all forex symbols that are required to do currency translations.
1001
+ *
1002
+ * @public
1003
+ * @returns {Array.<String>}
1004
+ */
973
1005
  getForexSymbols() {
974
1006
  return this._forexSymbols;
975
1007
  }
976
1008
 
1009
+ /**
1010
+ * Returns all current forex quotes.
1011
+ *
1012
+ * @public
1013
+ * @returns {Array.<Object>}
1014
+ */
977
1015
  getForexQuotes() {
978
1016
  return this._forexQuotes;
979
1017
  }
980
1018
 
1019
+ /**
1020
+ * Updates the forex quote for a single currency pair; causing updates to
1021
+ * any grouping level that contains that requires translation.
1022
+ *
1023
+ * @public
1024
+ * @param {String} symbol
1025
+ * @param {Object} quote
1026
+ */
981
1027
  setForexQuote(symbol, quote) {
982
1028
  assert.argumentIsRequired(symbol, 'symbol', String);
983
1029
  assert.argumentIsRequired(quote, 'quote', Object);
@@ -995,19 +1041,55 @@ module.exports = (() => {
995
1041
  Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
996
1042
  }
997
1043
 
998
- setPositionFundamentals(symbol, data) {
999
- return;
1044
+ /**
1045
+ * Updates fundamental data for a single symbol.
1046
+ *
1047
+ * @public
1048
+ * @param {String} symbol
1049
+ * @param {Object} data
1050
+ */
1051
+ setPositionFundamentalData(symbol, data) {
1052
+ assert.argumentIsRequired(symbol, 'symbol', String);
1053
+ assert.argumentIsRequired(data, 'data', Object);
1054
+
1055
+ if (this._symbols.hasOwnProperty(symbol)) {
1056
+ this._symbols[symbol].forEach(item => item.setPositionFundamentalData(data));
1057
+ }
1000
1058
  }
1001
1059
 
1002
- setNewsArticleExists(symbol, exists) {
1060
+ /**
1061
+ * Indicates if a news article exists for a symbol.
1062
+ *
1063
+ * @public
1064
+ * @param {String} symbol
1065
+ * @param {Boolean} display
1066
+ * @param {Boolean} exists
1067
+ */
1068
+ setNewsArticleExists(symbol, display, exists) {
1003
1069
  assert.argumentIsRequired(symbol, 'symbol', String);
1070
+ assert.argumentIsRequired(display, 'display', Boolean);
1004
1071
  assert.argumentIsRequired(exists, 'exists', Boolean);
1005
1072
 
1006
- if (this._symbols.hasOwnProperty(symbol)) {
1007
- this._symbols[symbol].forEach(item => item.setNewsArticleExists(exists));
1073
+ let map;
1074
+
1075
+ if (display) {
1076
+ map = this._symbolsDisplay;
1077
+ } else {
1078
+ map = this._symbols;
1079
+ }
1080
+
1081
+ if (map.hasOwnProperty(symbol)) {
1082
+ map[symbol].forEach(item => item.setNewsArticleExists(exists));
1008
1083
  }
1009
1084
  }
1010
1085
 
1086
+ /**
1087
+ * Returns a single level of grouping from one of the internal trees.
1088
+ *
1089
+ * @param {String} name
1090
+ * @param {Array.<String> keys
1091
+ * @returns {PositionGroup}
1092
+ */
1011
1093
  getGroup(name, keys) {
1012
1094
  assert.argumentIsRequired(name, 'name', String);
1013
1095
  assert.argumentIsArray(keys, 'keys', Number);
@@ -1015,6 +1097,14 @@ module.exports = (() => {
1015
1097
  return findNode(this._trees[name], keys).getValue();
1016
1098
  }
1017
1099
 
1100
+ /**
1101
+ * Returns all child groups from a level of grouping within one of
1102
+ * the internal trees.
1103
+ *
1104
+ * @param {String} name
1105
+ * @param {Array.<String> keys
1106
+ * @returns {Array.<PositionGroup>}
1107
+ */
1018
1108
  getGroups(name, keys) {
1019
1109
  assert.argumentIsRequired(name, 'name', String);
1020
1110
  assert.argumentIsArray(keys, 'keys', Number);
@@ -1026,8 +1116,6 @@ module.exports = (() => {
1026
1116
  assert.argumentIsRequired(name, 'name', String);
1027
1117
  assert.argumentIsRequired(executor, 'executor', Function);
1028
1118
 
1029
- assert.argumentIsRequired(executor, 'executor', Function);
1030
-
1031
1119
  this._trees[name].walk(group => group.setSuspended(true), false, false);
1032
1120
 
1033
1121
  executor(this);
@@ -1081,7 +1169,17 @@ module.exports = (() => {
1081
1169
  'use strict';
1082
1170
 
1083
1171
  /**
1172
+ * A grouping of {@link PositionItem} instances. The group aggregates from across
1173
+ * all the positions and performs currency translation, as necessary.
1174
+ *
1084
1175
  * @public
1176
+ * @param {PositionContainer} container
1177
+ * @param {PositionGroup|null} parent
1178
+ * @param {Array.<PositionItem>} items
1179
+ * @param {Currency} currency
1180
+ * @param {String} key
1181
+ * @param {String} description
1182
+ * @param {Boolean=} single
1085
1183
  */
1086
1184
  class PositionGroup {
1087
1185
  constructor(container, parent, items, currency, key, description, single) {
@@ -1101,6 +1199,7 @@ module.exports = (() => {
1101
1199
  this._suspended = false;
1102
1200
 
1103
1201
  this._marketPercentChangeEvent = new Event(this);
1202
+ this._excludedChangeEvent = new Event(this);
1104
1203
 
1105
1204
  this._dataFormat = { };
1106
1205
  this._dataActual = { };
@@ -1123,10 +1222,12 @@ module.exports = (() => {
1123
1222
  this._dataFormat.portfolio = item.portfolio.portfolio;
1124
1223
  this._dataFormat.position = item.position.position;
1125
1224
  this._dataFormat.instrument = item.position.instrument;
1225
+ this._dataFormat.fundamental = item.fundamental || { };
1126
1226
  } else {
1127
1227
  this._dataFormat.portfolio = null;
1128
1228
  this._dataFormat.position = null;
1129
1229
  this._dataFormat.instrument = null;
1230
+ this._dataFormat.fundamental = { };
1130
1231
  }
1131
1232
 
1132
1233
  this._dataFormat.quoteLast = null;
@@ -1197,8 +1298,12 @@ module.exports = (() => {
1197
1298
 
1198
1299
  if (this._single) {
1199
1300
  item.registerNewsExistsChangeHandler((exists, sender) => {
1301
+ this._dataActual.newsExists = exists;
1200
1302
  this._dataFormat.newsExists = exists;
1201
- this._dataFormat.newsExists = exists;
1303
+ });
1304
+
1305
+ item._fundamentalDataChangeEvent((data, sender) => {
1306
+ this._dataFormat.fundamental = data;
1202
1307
  });
1203
1308
  }
1204
1309
  });
@@ -1206,30 +1311,73 @@ module.exports = (() => {
1206
1311
  this.refresh();
1207
1312
  }
1208
1313
 
1314
+ /**
1315
+ * The key of the group.
1316
+ *
1317
+ * @public
1318
+ * @returns {String}
1319
+ */
1209
1320
  get key() {
1210
1321
  return this._key;
1211
1322
  }
1212
1323
 
1324
+ /**
1325
+ * The description of the group.
1326
+ *
1327
+ * @public
1328
+ * @returns {String}
1329
+ */
1213
1330
  get description() {
1214
1331
  return this._description;
1215
1332
  }
1216
1333
 
1334
+ /**
1335
+ * The {@link Currency} which all aggregated data is presented in.
1336
+ *
1337
+ * @public
1338
+ * @returns {Currency}
1339
+ */
1217
1340
  get currency() {
1218
1341
  return this._currency;
1219
1342
  }
1220
1343
 
1344
+ /**
1345
+ * The {@link PositionItem} instances which for which aggregated data is compiled.
1346
+ *
1347
+ * @public
1348
+ * @returns {Currency}
1349
+ */
1221
1350
  get items() {
1222
1351
  return this._items;
1223
1352
  }
1224
1353
 
1354
+ /**
1355
+ * The string-based, human-readable aggregated data for the group.
1356
+ *
1357
+ * @public
1358
+ * @returns {Object}
1359
+ */
1225
1360
  get data() {
1226
1361
  return this._dataFormat;
1227
1362
  }
1228
1363
 
1364
+ /**
1365
+ * The raw aggregated data for the group (typically {@link Decimal} instances).
1366
+ *
1367
+ * @public
1368
+ * @returns {Object}
1369
+ */
1229
1370
  get actual() {
1230
1371
  return this._dataActual;
1231
1372
  }
1232
1373
 
1374
+ /**
1375
+ * Indicates if the group will only contain one {@link PositionItem} -- that is,
1376
+ * indicates if the group represents a single position.
1377
+ *
1378
+ * @public
1379
+ * @returns {Boolean}
1380
+ */
1233
1381
  get single() {
1234
1382
  return this._single;
1235
1383
  }
@@ -1238,10 +1386,22 @@ module.exports = (() => {
1238
1386
  return this._suspended;
1239
1387
  }
1240
1388
 
1389
+ /**
1390
+ * Indicates if the group should be excluded from higher-level aggregations.
1391
+ *
1392
+ * @public
1393
+ * @returns {Boolean}
1394
+ */
1241
1395
  get excluded() {
1242
1396
  return this._excluded;
1243
1397
  }
1244
1398
 
1399
+ /**
1400
+ * Causes aggregated data to be recalculated using a new exchange rate.
1401
+ *
1402
+ * @public
1403
+ * @param {Rate} rate
1404
+ */
1245
1405
  setForexRate(rate) {
1246
1406
  if (!this._bypassCurrencyTranslation) {
1247
1407
  this.refresh();
@@ -1252,11 +1412,7 @@ module.exports = (() => {
1252
1412
  assert.argumentIsRequired(value, 'value', Boolean);
1253
1413
 
1254
1414
  if (this._excluded !== value) {
1255
- this._container.startTransaction(() => {
1256
- this._items.forEach((item) => {
1257
- item.setExcluded(value);
1258
- });
1259
- });
1415
+ this._excludedChangeEvent(this._excluded = value);
1260
1416
  }
1261
1417
  }
1262
1418
 
@@ -1270,6 +1426,11 @@ module.exports = (() => {
1270
1426
  }
1271
1427
  }
1272
1428
 
1429
+ /**
1430
+ * Causes all aggregated data to be recalculated.
1431
+ *
1432
+ * @public
1433
+ */
1273
1434
  refresh() {
1274
1435
  const rates = this._container.getForexQuotes();
1275
1436
 
@@ -1277,6 +1438,12 @@ module.exports = (() => {
1277
1438
  calculatePriceData(this, rates, null, true);
1278
1439
  }
1279
1440
 
1441
+ /**
1442
+ * Causes the percent of the position, with respect to the parent container's
1443
+ * total, to be recalculated.
1444
+ *
1445
+ * @public
1446
+ */
1280
1447
  refreshMarketPercent() {
1281
1448
  calculateMarketPercent(this, this._container.getForexQuotes(), true);
1282
1449
  }
@@ -1560,7 +1727,15 @@ module.exports = (() => {
1560
1727
  'use strict';
1561
1728
 
1562
1729
  /**
1730
+ * A container for a single position, which handles quote changes and
1731
+ * notifies observers -- which are typically parent-level {@link PositionGroup}
1732
+ * instances.
1733
+ *
1563
1734
  * @public
1735
+ * @param {Object} portfolio
1736
+ * @param {Object} position
1737
+ * @param {Object} currentSummary
1738
+ * @param {Array.<Object>} previousSummaries
1564
1739
  */
1565
1740
  class PositionItem {
1566
1741
  constructor(portfolio, position, currentSummary, previousSummaries) {
@@ -1576,10 +1751,12 @@ module.exports = (() => {
1576
1751
  this._data.basis = null;
1577
1752
 
1578
1753
  this._currentQuote = null;
1579
- this._previousQuote = null;
1754
+
1755
+ this._currentPrice = null;
1756
+ this._previousPrice = null;
1580
1757
 
1581
1758
  this._data.currentPrice = null;
1582
- this._data.previousPrice = null;
1759
+ this._data.currentPricePrevious = null;
1583
1760
 
1584
1761
  this._data.market = null;
1585
1762
  this._data.marketChange = null;
@@ -1589,7 +1766,7 @@ module.exports = (() => {
1589
1766
 
1590
1767
  this._data.unrealized = null;
1591
1768
  this._data.unrealizedChange = null;
1592
-
1769
+
1593
1770
  this._data.summaryTotalCurrent = null;
1594
1771
  this._data.summaryTotalCurrentChange = null;
1595
1772
 
@@ -1600,70 +1777,127 @@ module.exports = (() => {
1600
1777
  this._data.basisPrice = null;
1601
1778
 
1602
1779
  this._data.newsExists = false;
1603
-
1604
- this._excluded = false;
1780
+ this._data.fundamental = { };
1605
1781
 
1606
1782
  calculateStaticData(this);
1607
1783
  calculatePriceData(this, null);
1608
1784
 
1609
1785
  this._quoteChangedEvent = new Event(this);
1610
- this._excludedChangeEvent = new Event(this);
1611
1786
  this._newsExistsChangedEvent = new Event(this);
1787
+ this._fundamentalDataChangeEvent = new Event(this);
1612
1788
  }
1613
1789
 
1790
+ /**
1791
+ * The portfolio of the encapsulated position.
1792
+ *
1793
+ * @public
1794
+ * @returns {Object}
1795
+ */
1614
1796
  get portfolio() {
1615
1797
  return this._portfolio;
1616
1798
  }
1617
1799
 
1800
+ /**
1801
+ * The encapsulated position.
1802
+ *
1803
+ * @public
1804
+ * @returns {Object}
1805
+ */
1618
1806
  get position() {
1619
1807
  return this._position;
1620
1808
  }
1621
1809
 
1810
+ /**
1811
+ * The {@link Currency} of the encapsulated position.
1812
+ *
1813
+ * @public
1814
+ * @returns {Object}
1815
+ */
1622
1816
  get currency() {
1623
1817
  return this._currency;
1624
1818
  }
1625
1819
 
1820
+ /**
1821
+ * The year-to-date position summary of the encapsulated position.
1822
+ *
1823
+ * @public
1824
+ * @returns {Object}
1825
+ */
1626
1826
  get currentSummary() {
1627
1827
  return this._currentSummary;
1628
1828
  }
1629
-
1829
+
1830
+ /**
1831
+ * Previous year's summaries for the encapsulated position.
1832
+ *
1833
+ * @public
1834
+ * @returns {Object}
1835
+ */
1630
1836
  get previousSummaries() {
1631
1837
  return this._previousSummaries;
1632
1838
  }
1633
1839
 
1840
+ /**
1841
+ * Various data regarding the encapsulated position.
1842
+ *
1843
+ * @public
1844
+ * @returns {*}
1845
+ */
1634
1846
  get data() {
1635
1847
  return this._data;
1636
1848
  }
1637
1849
 
1850
+ /**
1851
+ * The current quote for the symbol of the encapsulated position.
1852
+ *
1853
+ * @public
1854
+ * @returns {null|{Object}}
1855
+ */
1638
1856
  get quote() {
1639
1857
  return this._currentQuote;
1640
1858
  }
1641
1859
 
1642
- get excluded() {
1643
- return this._excluded;
1644
- }
1645
-
1860
+ /**
1861
+ * Sets the current quote -- causing position-level data (e.g. market value) to
1862
+ * be recalculated.
1863
+ *
1864
+ * @public
1865
+ * @param {Object} quote
1866
+ */
1646
1867
  setQuote(quote) {
1647
1868
  assert.argumentIsRequired(quote, 'quote', Object);
1648
1869
 
1649
- if (this._previousQuote === null || this._previousQuote.lastPrice !== quote.lastPrice) {
1870
+ if (this._currentPricePrevious !== quote.lastPrice) {
1650
1871
  calculatePriceData(this, quote.lastPrice);
1651
1872
 
1652
- this._previousQuote = this._currentQuote;
1873
+ this._currentPricePrevious = this._currentPrice;
1874
+ this._currentPrice = quote.lastPrice;
1875
+
1653
1876
  this._currentQuote = quote;
1654
1877
 
1655
1878
  this._quoteChangedEvent.fire(this._currentQuote);
1656
1879
  }
1657
1880
  }
1658
1881
 
1659
- setExcluded(value) {
1660
- assert.argumentIsRequired(value, 'value', Boolean);
1882
+ /**
1883
+ * Sets fundamental data for the position.
1884
+ *
1885
+ * @public
1886
+ * @param {Object} data
1887
+ */
1888
+ setPositionFundamentalData(data) {
1889
+ assert.argumentIsRequired(data, 'data', Object);
1661
1890
 
1662
- if (this._excluded !== value) {
1663
- this._excludedChangeEvent.fire(this._excluded = value);
1664
- }
1891
+ this._fundamentalDataChangeEvent(this._data.fundamental = data);
1665
1892
  }
1666
1893
 
1894
+ /**
1895
+ * Sets a flag which indicates if news article(s) exist for the encapsulated position's
1896
+ * symbol.
1897
+ *
1898
+ * @public
1899
+ * @param {Boolean} value
1900
+ */
1667
1901
  setNewsArticleExists(value) {
1668
1902
  assert.argumentIsRequired(value, 'value', Boolean);
1669
1903
 
@@ -1672,14 +1906,33 @@ module.exports = (() => {
1672
1906
  }
1673
1907
  }
1674
1908
 
1909
+ /**
1910
+ * Registers an observer for quote changes, which is fired after internal recalculations
1911
+ * of position data are complete.
1912
+ *
1913
+ * @public
1914
+ * @param {Function} handler
1915
+ */
1675
1916
  registerQuoteChangeHandler(handler) {
1676
1917
  this._quoteChangedEvent.register(handler);
1677
1918
  }
1678
1919
 
1679
- registerExcludedChangeHandler(handler) {
1680
- this._excludedChangeEvent.register(handler);
1920
+ /**
1921
+ * Registers an observer for fundamental data changes.
1922
+ *
1923
+ * @public
1924
+ * @param {Function} handler
1925
+ */
1926
+ registerFundamentalDataChangeHandler(handler) {
1927
+ this._fundamentalDataChangeEvent.register(handler);
1681
1928
  }
1682
1929
 
1930
+ /**
1931
+ * Registers an observer changes to the status of news existence.
1932
+ *
1933
+ * @public
1934
+ * @param {Function} handler
1935
+ */
1683
1936
  registerNewsExistsChangeHandler(handler) {
1684
1937
  this._newsExistsChangedEvent.register(handler);
1685
1938
  }
@@ -1808,10 +2061,10 @@ module.exports = (() => {
1808
2061
  data.unrealizedChange = Decimal.ZERO;
1809
2062
  }
1810
2063
  }
1811
-
2064
+
1812
2065
  function calculateSummaryTotal(summary) {
1813
2066
  let returnRef;
1814
-
2067
+
1815
2068
  if (summary) {
1816
2069
  const period = summary.period;
1817
2070
 
@@ -1819,7 +2072,7 @@ module.exports = (() => {
1819
2072
  } else {
1820
2073
  returnRef = Decimal.ZERO;
1821
2074
  }
1822
-
2075
+
1823
2076
  return returnRef;
1824
2077
  }
1825
2078