@barchart/portfolio-api-common 1.0.164 → 1.0.168

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.
@@ -11,7 +11,9 @@ const array = require('@barchart/common-js/lang/array'),
11
11
 
12
12
  const PositionSummaryFrame = require('./../data/PositionSummaryFrame');
13
13
 
14
- const PositionTreeDefinition = require('./definitions/PositionTreeDefinition');
14
+ const PositionLevelDefinition = require('./definitions/PositionLevelDefinition'),
15
+ PositionLevelType = require('./definitions/PositionLevelType'),
16
+ PositionTreeDefinition = require('./definitions/PositionTreeDefinition');
15
17
 
16
18
  const PositionGroup = require('./PositionGroup'),
17
19
  PositionItem = require('./PositionItem');
@@ -128,10 +130,9 @@ module.exports = (() => {
128
130
  }, { });
129
131
 
130
132
  this._currencies = this._items.reduce((map, item) => {
131
- const position = item.position;
133
+ const currency = extractCurrency(item.position);
132
134
 
133
- if (position.instrument && position.instrument.currency) {
134
- const currency = position.instrument.currency;
135
+ if (currency) {
135
136
  const code = currency.code;
136
137
 
137
138
  if (!map.hasOwnProperty(code)) {
@@ -167,6 +168,13 @@ module.exports = (() => {
167
168
  }, { });
168
169
  }
169
170
 
171
+ /**
172
+ * Adds a new portfolio to the container, injecting it into aggregation
173
+ * trees, as necessary.
174
+ *
175
+ * @public
176
+ * @param {Object} portfolio
177
+ */
170
178
  addPortfolio(portfolio) {
171
179
  assert.argumentIsRequired(portfolio, 'portfolio', Object);
172
180
  assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
@@ -209,7 +217,7 @@ module.exports = (() => {
209
217
  if (group.definition === parentLevelDefinition) {
210
218
  parentTrees.push(groupTree);
211
219
  }
212
- });
220
+ }, false, false);
213
221
  }
214
222
 
215
223
  const overrideRequiredGroups = [ portfolioRequiredGroup ];
@@ -222,8 +230,35 @@ module.exports = (() => {
222
230
  }
223
231
  }
224
232
 
233
+ /**
234
+ * Removes an existing portfolio, and all of it's positions, from the container. This
235
+ * also triggers removal of the portfolio and it's positions from any applicable
236
+ * aggregation trees.
237
+ *
238
+ * @public
239
+ * @param {Object} portfolio
240
+ */
225
241
  removePortfolio(portfolio) {
242
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
243
+ assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
244
+
245
+ this.startTransaction(() => {
246
+ const itemsToRemove = getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(i => removePositionItem.call(this, i));
226
247
 
248
+ itemsToRemove.forEach(item => removePositionItem.call(this, item));
249
+
250
+ delete this._portfolios[portfolio.portfolio];
251
+
252
+ Object.keys(this._trees).forEach((key) => {
253
+ const tree = this._tree[key];
254
+
255
+ tree.walk((group, groupNode) => {
256
+ if (group.definition.type === PositionLevelType.PORTFOLIO && group.key === PositionLevelDefinition.getKeyForPortfolioGroup(portfolio)) {
257
+ groupNode.sever();
258
+ }
259
+ }, true, false);
260
+ });
261
+ });
227
262
  }
228
263
 
229
264
  mutatePosition(position, summary) {
@@ -231,7 +266,7 @@ module.exports = (() => {
231
266
  }
232
267
 
233
268
  removePosition(position) {
234
-
269
+ removePositionItem.call(this, this._items.find((item) => item.position.position === position));
235
270
  }
236
271
 
237
272
  /**
@@ -314,7 +349,6 @@ module.exports = (() => {
314
349
  assert.argumentIsRequired(quote, 'quote', Object);
315
350
 
316
351
  const rate = Rate.fromPair(quote.lastPrice, symbol);
317
-
318
352
  const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
319
353
 
320
354
  if (index < 0) {
@@ -408,6 +442,16 @@ module.exports = (() => {
408
442
  return findNode(this._trees[name], keys).getChildren().map(node => node.getValue());
409
443
  }
410
444
 
445
+ /**
446
+ * Returns all portfolios in the container.
447
+ *
448
+ * @public
449
+ * @return {Array.<Object>}
450
+ */
451
+ getPortfolios() {
452
+ return Object.keys(this._portfolios).map(id => this._portfolios[id]);
453
+ }
454
+
411
455
  /**
412
456
  * Returns all positions for the given portfolio.
413
457
  *
@@ -416,24 +460,42 @@ module.exports = (() => {
416
460
  * @return {Array.<Object>}
417
461
  */
418
462
  getPositions(portfolio) {
419
- return this._items.reduce((positions, item) => {
420
- if (item.position.portfolio === portfolio) {
421
- positions.push(item);
422
- }
463
+ assert.argumentIsRequired(portfolio, 'portfolio', String);
423
464
 
424
- return positions;
425
- }, []);
465
+ return getPositionItemsForPortfolio(this._items, portfolio).map(item => item.position);
426
466
  }
427
467
 
428
- startTransaction(name, executor) {
429
- assert.argumentIsRequired(name, 'name', String);
468
+ /**
469
+ * Pauses aggregation calculations during the processing of an action.
470
+ *
471
+ * @public
472
+ * @param {Function} executor
473
+ * @param {String=|Array.<String>=} names
474
+ */
475
+ startTransaction(executor, names) {
476
+ let namesToUse;
477
+
478
+ if (is.array(names)) {
479
+ assert.argumentIsArray(names, 'names', String);
480
+
481
+ namesToUse = names;
482
+ } else {
483
+ assert.argumentIsOptional(names, 'names', String);
484
+
485
+ if (names) {
486
+ namesToUse = [ names ];
487
+ } else {
488
+ namesToUse = Object.keys(this._trees);
489
+ }
490
+ }
491
+
430
492
  assert.argumentIsRequired(executor, 'executor', Function);
431
493
 
432
- this._trees[name].walk(group => group.setSuspended(true), false, false);
494
+ namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(true), false, false));
433
495
 
434
496
  executor(this);
435
497
 
436
- this._trees[name].walk(group => group.setSuspended(false), false, false);
498
+ namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(false), false, false));
437
499
  }
438
500
 
439
501
  toString() {
@@ -465,6 +527,14 @@ module.exports = (() => {
465
527
  }
466
528
  }
467
529
 
530
+ function extractCurrency(position) {
531
+ if (position.instrument && position.instrument.currency) {
532
+ return position.instrument.currency;
533
+ } else {
534
+ return null;
535
+ }
536
+ }
537
+
468
538
  function addGroupBinding(group, dispoable) {
469
539
  const id = group.id;
470
540
 
@@ -594,5 +664,46 @@ module.exports = (() => {
594
664
  });
595
665
  }
596
666
 
667
+ function getPositionItemsForPortfolio(items, portfolio) {
668
+ return items.reduce((positionItems, item) => {
669
+ if (item.position.portfolio === portfolio) {
670
+ positionItems.push(item.position);
671
+ }
672
+
673
+ return positionItems;
674
+ }, [ ]);
675
+ }
676
+
677
+ function removePositionItem(positionItem) {
678
+ if (!positionItem) {
679
+ return;
680
+ }
681
+
682
+ delete this._summariesCurrent[positionItem.position.position];
683
+ delete this._summariesPrevious[positionItem.position.position];
684
+
685
+ array.remove(this._items, i => i === positionItem);
686
+
687
+ const barchartSymbol = extractSymbolForBarchart(positionItem.position);
688
+
689
+ if (this._symbols.hasOwnProperty(barchartSymbol)) {
690
+ array.remove(this._symbols[barchartSymbol], i => i === positionItem);
691
+ }
692
+
693
+ const displaySymbol = extractSymbolForDisplay(positionItem.position);
694
+
695
+ if (this._symbolsDisplay.hasOwnProperty(displaySymbol)) {
696
+ array.remove(this._symbols[displaySymbol], i => i === positionItem);
697
+ }
698
+
699
+ const currency = extractCurrency(positionItem.position);
700
+
701
+ if (currency && this._currencies.hasOwnProperty(currency.code)) {
702
+ array.remove(this._currencies[currency.code], i => i === positionItem);
703
+ }
704
+
705
+ positionItem.dispose();
706
+ }
707
+
597
708
  return PositionContainer;
598
709
  })();
@@ -2,6 +2,7 @@ const array = require('@barchart/common-js/lang/array'),
2
2
  assert = require('@barchart/common-js/lang/assert'),
3
3
  Currency = require('@barchart/common-js/lang/Currency'),
4
4
  Decimal = require('@barchart/common-js/lang/Decimal'),
5
+ Disposable = require('@barchart/common-js/lang/Disposable'),
5
6
  DisposableStack = require('@barchart/common-js/collections/specialized/DisposableStack'),
6
7
  Event = require('@barchart/common-js/messaging/Event'),
7
8
  formatter = require('@barchart/common-js/lang/formatter'),
@@ -149,7 +150,7 @@ module.exports = (() => {
149
150
  this._dataFormat.portfolioType = null;
150
151
 
151
152
  this._items.forEach((item) => {
152
- this._disposeStack.push(item.registerQuoteChangeHandler((quote, sender) => {
153
+ const quoteBinding = item.registerQuoteChangeHandler((quote, sender) => {
153
154
  if (this._single) {
154
155
  const precision = sender.position.instrument.currency.precision;
155
156
 
@@ -185,18 +186,39 @@ module.exports = (() => {
185
186
  }
186
187
 
187
188
  calculatePriceData(this, this._container.getForexQuotes(), sender, false);
188
- }));
189
+ });
190
+
191
+ let newsBinding = Disposable.getEmpty();
192
+ let fundamentalBinding = Disposable.getEmpty();
189
193
 
190
194
  if (this._single) {
191
- this._disposeStack.push(item.registerNewsExistsChangeHandler((exists, sender) => {
195
+ newsBinding = item.registerNewsExistsChangeHandler((exists, sender) => {
192
196
  this._dataActual.newsExists = exists;
193
197
  this._dataFormat.newsExists = exists;
194
- }));
198
+ });
195
199
 
196
- this._disposeStack.push(item.registerFundamentalDataChangeHandler((data, sender) => {
200
+ fundamentalBinding = item.registerFundamentalDataChangeHandler((data, sender) => {
197
201
  this._dataFormat.fundamental = data;
198
- }));
202
+ });
199
203
  }
204
+
205
+ this._disposeStack.push(quoteBinding);
206
+ this._disposeStack.push(newsBinding);
207
+ this._disposeStack.push(fundamentalBinding);
208
+
209
+ this._disposeStack.push(item.registerPositionItemDisposeHandler(() => {
210
+ quoteBinding.dispose();
211
+ newsBinding.dispose();
212
+ fundamentalBinding.dispose();
213
+
214
+ array.remove(this._items, i => i === item);
215
+ array.remove(this._excludedItems, i => i === item);
216
+ array.remove(this._consideredItems, i => i === item);
217
+
218
+ delete this._excludedItemMap[item.position.position];
219
+
220
+ this.refresh();
221
+ }));
200
222
  });
201
223
 
202
224
  this.refresh();
@@ -373,22 +395,35 @@ module.exports = (() => {
373
395
  }
374
396
  }
375
397
 
398
+ /**
399
+ * Stops (or starts) group-level aggregation calculations.
400
+ *
401
+ * @public
402
+ * @param {Boolean} value
403
+ */
376
404
  setSuspended(value) {
377
405
  assert.argumentIsRequired(value, 'value', Boolean);
378
406
 
379
407
  if (this._suspended !== value) {
380
- if (this._suspended = value) {
408
+ this._suspended = value;
409
+
410
+ if (!this._suspended) {
381
411
  this.refresh();
382
412
  }
383
413
  }
384
414
  }
385
415
 
386
416
  /**
387
- * Causes all aggregated data to be recalculated.
417
+ * Causes all aggregated data to be recalculated (assuming the group has not
418
+ * been suspended).
388
419
  *
389
420
  * @public
390
421
  */
391
422
  refresh() {
423
+ if (this._suspended) {
424
+ return;
425
+ }
426
+
392
427
  const rates = this._container.getForexQuotes();
393
428
 
394
429
  calculateStaticData(this, rates);
@@ -2,6 +2,7 @@ const array = require('@barchart/common-js/lang/array'),
2
2
  assert = require('@barchart/common-js/lang/assert'),
3
3
  Currency = require('@barchart/common-js/lang/Currency'),
4
4
  Decimal = require('@barchart/common-js/lang/Decimal'),
5
+ Disposable = require('@barchart/common-js/lang/Disposable'),
5
6
  Event = require('@barchart/common-js/messaging/Event'),
6
7
  is = require('@barchart/common-js/lang/is');
7
8
 
@@ -21,8 +22,10 @@ module.exports = (() => {
21
22
  * @param {Object} currentSummary
22
23
  * @param {Array.<Object>} previousSummaries
23
24
  */
24
- class PositionItem {
25
+ class PositionItem extends Disposable {
25
26
  constructor(portfolio, position, currentSummary, previousSummaries) {
27
+ super();
28
+
26
29
  this._portfolio = portfolio;
27
30
  this._position = position;
28
31
  this._currency = position.instrument.currency || Currency.CAD;
@@ -67,6 +70,7 @@ module.exports = (() => {
67
70
  this._quoteChangedEvent = new Event(this);
68
71
  this._newsExistsChangedEvent = new Event(this);
69
72
  this._fundamentalDataChangeEvent = new Event(this);
73
+ this._positionItemDisposeEvent = new Event(this);
70
74
  }
71
75
 
72
76
  /**
@@ -149,6 +153,10 @@ module.exports = (() => {
149
153
  setQuote(quote) {
150
154
  assert.argumentIsRequired(quote, 'quote', Object);
151
155
 
156
+ if (this.getIsDisposed()) {
157
+ return;
158
+ }
159
+
152
160
  if (this._currentPricePrevious !== quote.lastPrice) {
153
161
  calculatePriceData(this, quote.lastPrice);
154
162
 
@@ -170,6 +178,10 @@ module.exports = (() => {
170
178
  setPositionFundamentalData(data) {
171
179
  assert.argumentIsRequired(data, 'data', Object);
172
180
 
181
+ if (this.getIsDisposed()) {
182
+ return;
183
+ }
184
+
173
185
  this._fundamentalDataChangeEvent.fire(this._data.fundamental = data);
174
186
  }
175
187
 
@@ -183,6 +195,10 @@ module.exports = (() => {
183
195
  setNewsArticleExists(value) {
184
196
  assert.argumentIsRequired(value, 'value', Boolean);
185
197
 
198
+ if (this.getIsDisposed()) {
199
+ return;
200
+ }
201
+
186
202
  if (this._data.newsExists !== value) {
187
203
  this._newsExistsChangedEvent.fire(this._data.newsExists = value);
188
204
  }
@@ -222,6 +238,26 @@ module.exports = (() => {
222
238
  return this._newsExistsChangedEvent.register(handler);
223
239
  }
224
240
 
241
+ /**
242
+ * Registers an observer for object disposal.
243
+ *
244
+ * @public
245
+ * @param {Function} handler
246
+ * @returns {Disposable}
247
+ */
248
+ registerPositionItemDisposeHandler(handler) {
249
+ return this._positionItemDisposeEvent.register(handler);
250
+ }
251
+
252
+ _onDispose() {
253
+ this._positionItemDisposeEvent.fire(this);
254
+
255
+ this._quoteChangedEvent.clear();
256
+ this._newsExistsChangedEvent.clear();
257
+ this._fundamentalDataChangeEvent.clear();
258
+ this._positionItemDisposeEvent.clear();
259
+ }
260
+
225
261
  toString() {
226
262
  return '[PositionItem]';
227
263
  }
@@ -4,6 +4,8 @@ const assert = require('@barchart/common-js/lang/assert'),
4
4
 
5
5
  const InstrumentType = require('./../../data/InstrumentType');
6
6
 
7
+ const PositionLevelType = require('./PositionLevelType');
8
+
7
9
  module.exports = (() => {
8
10
  'use strict';
9
11
 
@@ -20,11 +22,12 @@ module.exports = (() => {
20
22
  * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
21
23
  * @param {Boolean=} single
22
24
  * @param {Boolean=} aggregateCash
23
- * @param {Function=} injectPositions
25
+ * @param {Function=} requiredGroupGenerator
24
26
  */
25
27
  class PositionLevelDefinition {
26
- constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash, requiredGroupGenerator) {
28
+ constructor(name, type, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash, requiredGroupGenerator) {
27
29
  assert.argumentIsRequired(name, 'name', String);
30
+ assert.argumentIsRequired(type, 'type', PositionLevelType, 'PositionLevelType');
28
31
  assert.argumentIsRequired(keySelector, 'keySelector', Function);
29
32
  assert.argumentIsRequired(descriptionSelector, 'descriptionSelector', Function);
30
33
  assert.argumentIsRequired(currencySelector, 'currencySelector', Function);
@@ -38,6 +41,7 @@ module.exports = (() => {
38
41
  assert.argumentIsOptional(requiredGroupGenerator, 'requiredGroupGenerator', Function);
39
42
 
40
43
  this._name = name;
44
+ this._type = type;
41
45
 
42
46
  this._keySelector = keySelector;
43
47
  this._descriptionSelector = descriptionSelector;
@@ -61,6 +65,16 @@ module.exports = (() => {
61
65
  return this._name;
62
66
  }
63
67
 
68
+ /**
69
+ * A general description of the type of items grouped together.
70
+ *
71
+ * @public
72
+ * @return {PositionLevelType}
73
+ */
74
+ get type() {
75
+ return this._type;
76
+ }
77
+
64
78
  /**
65
79
  * A function, when given a {@link PositionItem} returns a string that is used
66
80
  * to group {@link PositionItem} instances into different groups.
@@ -0,0 +1,29 @@
1
+ const Enum = require('@barchart/common-js/lang/Enum');
2
+
3
+ module.exports = (() => {
4
+ 'use strict';
5
+
6
+ class PositionLevelType extends Enum {
7
+ constructor(code) {
8
+ super(code, code);
9
+ }
10
+
11
+ static get PORTFOLIO() {
12
+ return portfolio;
13
+ }
14
+
15
+ static get POSITION() {
16
+ return position;
17
+ }
18
+
19
+ static get OTHER() {
20
+ return other;
21
+ }
22
+ }
23
+
24
+ const portfolio = new PositionLevelType('PORTFOLIO');
25
+ const position = new PositionLevelType('POSITION');
26
+ const other = new PositionLevelType('OTHER');
27
+
28
+ return PositionLevelType;
29
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.164",
3
+ "version": "1.0.168",
4
4
  "description": "Common classes used by the Portfolio system",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",