@barchart/portfolio-api-common 1.0.160 → 1.0.164

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.
@@ -48,17 +48,9 @@ module.exports = (() => {
48
48
  const currentSummaryFrame = PositionSummaryFrame.YTD;
49
49
  const currentSummaryRange = array.last(currentSummaryFrame.getRecentRanges(0));
50
50
 
51
- this._groupBindings = { };
52
-
53
- const addGroupBinding = (group, dispoable) => {
54
- const id = group.id;
55
-
56
- if (!this._groupBindings.hasOwnProperty(id)) {
57
- this._groupBindings[id] = new DisposableStack();
58
- }
51
+ this._definitions = definitions;
59
52
 
60
- this._groupBindings[id].push(dispoable);
61
- };
53
+ this._groupBindings = { };
62
54
 
63
55
  this._portfolios = portfolios.reduce((map, portfolio) => {
64
56
  map[portfolio.portfolio] = portfolio;
@@ -164,132 +156,82 @@ module.exports = (() => {
164
156
  return Rate.fromPair(Decimal.ONE, symbol);
165
157
  });
166
158
 
167
- this._trees = definitions.reduce((map, treeDefinition) => {
159
+ this._trees = this._definitions.reduce((map, treeDefinition) => {
168
160
  const tree = new Tree();
169
161
 
170
- const createGroups = (currentTree, items, levelDefinitions) => {
171
- if (levelDefinitions.length === 0) {
172
- return;
173
- }
174
-
175
- const parent = currentTree.getValue() || null;
176
-
177
- const levelDefinition = levelDefinitions[0];
178
-
179
- const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
180
- const populatedGroups = Object.keys(populatedObjects).reduce((list, key) => {
181
- const items = populatedObjects[key];
182
- const first = items[0];
162
+ createGroups.call(this, tree, tree, this._items, treeDefinition, treeDefinition.definitions);
163
+
164
+ map[treeDefinition.name] = tree;
183
165
 
184
- list.push(new PositionGroup(this, parent, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1, levelDefinition.aggregateCash));
166
+ return map;
167
+ }, { });
168
+ }
185
169
 
186
- return list;
187
- }, [ ]);
170
+ addPortfolio(portfolio) {
171
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
172
+ assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
173
+ assert.argumentIsRequired(portfolio.name, 'portfolio.name', String);
188
174
 
189
- const missingGroups = array.difference(levelDefinition.requiredGroups.map(group => group.key), populatedGroups.map(group => group.key))
190
- .map((key) => {
191
- return levelDefinition.requiredGroups.find(g => g.key === key);
192
- });
175
+ const key = portfolio.portfolio;
193
176
 
194
- const empty = missingGroups.map((group) => {
195
- return new PositionGroup(this, parent, [ ], group.currency, group.key, group.description);
196
- });
177
+ if (!this._portfolios.hasOwnProperty(key)) {
178
+ this._portfolios[key] = portfolio;
197
179
 
198
- const compositeGroups = populatedGroups.concat(empty);
180
+ this._definitions.forEach((treeDefinition) => {
181
+ const tree = this._trees[treeDefinition.name];
182
+ const levelDefinitions = treeDefinition.definitions;
199
183
 
200
- let builder;
184
+ let portfolioRequiredGroup = null;
201
185
 
202
- if (levelDefinition.requiredGroups.length !== 0) {
203
- const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
204
- map[group.description] = index;
186
+ let portfolioLevelDefinition = null;
187
+ let portfolioLevelDefinitionIndex = null;
205
188
 
206
- return map;
207
- }, { });
189
+ levelDefinitions.forEach((levelDefinition, i) => {
190
+ if (portfolioRequiredGroup === null) {
191
+ portfolioRequiredGroup = levelDefinition.generateRequiredGroup(portfolio);
208
192
 
209
- const getIndex = (description) => {
210
- if (ordering.hasOwnProperty(description)) {
211
- return ordering[description];
212
- } else {
213
- return Number.MAX_VALUE;
193
+ if (portfolioRequiredGroup !== null) {
194
+ portfolioLevelDefinition = levelDefinition;
195
+ portfolioLevelDefinitionIndex = i;
214
196
  }
215
- };
197
+ }
198
+ });
216
199
 
217
- builder = ComparatorBuilder.startWith((a, b) => {
218
- return comparators.compareNumbers(getIndex(a.description), getIndex(b.description));
219
- }).thenBy((a, b) => {
220
- return comparators.compareStrings(a.description, b.description);
221
- });
222
- } else {
223
- builder = ComparatorBuilder.startWith((a, b) => {
224
- return comparators.compareStrings(a.description, b.description);
225
- });
226
- }
200
+ if (portfolioRequiredGroup !== null) {
201
+ let parentTrees = [ ];
227
202
 
228
- compositeGroups.sort(builder.toComparator());
203
+ if (portfolioLevelDefinitionIndex === 0) {
204
+ parentTrees.push(tree);
205
+ } else {
206
+ const parentLevelDefinition = levelDefinitions[ portfolioLevelDefinitionIndex - 1 ];
229
207
 
230
- const initializeGroupObservers = (group, groupTree) => {
231
- addGroupBinding(group, group.registerGroupExcludedChangeHandler((excluded, sender) => {
232
- groupTree.climb((parentGroup) => {
233
- if (parentGroup) {
234
- let excludedItems = [];
208
+ tree.walk((group, groupTree) => {
209
+ if (group.definition === parentLevelDefinition) {
210
+ parentTrees.push(groupTree);
211
+ }
212
+ });
213
+ }
235
214
 
236
- currentTree.walk((childGroup) => {
237
- if (childGroup.excluded) {
238
- excludedItems = excludedItems.concat(childGroup.items);
239
- }
240
- }, false, false);
215
+ const overrideRequiredGroups = [ portfolioRequiredGroup ];
241
216
 
242
- parentGroup.setExcludedItems(array.unique(excludedItems));
243
- }
244
- }, false);
245
-
246
- if (treeDefinition.exclusionDependencies.length > 0) {
247
- const dependantTrees = treeDefinition.exclusionDependencies.reduce((trees, name) => {
248
- if (this._trees.hasOwnProperty(name)) {
249
- trees.push(this._trees[name]);
250
- }
251
-
252
- return trees;
253
- }, [ ]);
254
-
255
- if (dependantTrees.length > 0) {
256
- let excludedItems = [ ];
257
-
258
- tree.walk((childGroup) => {
259
- if (childGroup.excluded) {
260
- excludedItems = excludedItems.concat(childGroup.items);
261
- }
262
- }, false, false);
263
-
264
- dependantTrees.forEach((dependantTrees) => {
265
- dependantTrees.walk((childGroup) => {
266
- childGroup.setExcludedItems(excludedItems);
267
- }, false, false);
268
- });
269
- }
270
- }
271
- }));
272
- };
217
+ parentTrees.forEach((t) => {
218
+ createGroups.call(this, tree, t, [ ], treeDefinition, levelDefinitions.slice(portfolioLevelDefinitionIndex), overrideRequiredGroups);
219
+ });
220
+ }
221
+ });
222
+ }
223
+ }
273
224
 
274
- compositeGroups.forEach((group) => {
275
- const childTree = currentTree.addChild(group);
225
+ removePortfolio(portfolio) {
276
226
 
277
- initializeGroupObservers(group, childTree);
227
+ }
278
228
 
279
- addGroupBinding(group, group.registerMarketPercentChangeHandler(() => {
280
- currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
281
- }));
229
+ mutatePosition(position, summary) {
282
230
 
283
- createGroups(childTree, group.items, array.dropLeft(levelDefinitions));
284
- });
285
- };
231
+ }
286
232
 
287
- createGroups(tree, this._items, treeDefinition.definitions);
288
-
289
- map[treeDefinition.name] = tree;
233
+ removePosition(position) {
290
234
 
291
- return map;
292
- }, { });
293
235
  }
294
236
 
295
237
  /**
@@ -523,5 +465,134 @@ module.exports = (() => {
523
465
  }
524
466
  }
525
467
 
468
+ function addGroupBinding(group, dispoable) {
469
+ const id = group.id;
470
+
471
+ if (!this._groupBindings.hasOwnProperty(id)) {
472
+ this._groupBindings[id] = new DisposableStack();
473
+ }
474
+
475
+ this._groupBindings[id].push(dispoable);
476
+ }
477
+
478
+ function createGroups(parentTree, currentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
479
+ if (levelDefinitions.length === 0) {
480
+ return;
481
+ }
482
+
483
+ const parent = currentTree.getValue() || null;
484
+
485
+ const levelDefinition = levelDefinitions[0];
486
+
487
+ const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
488
+ const populatedGroups = Object.keys(populatedObjects).reduce((list, key) => {
489
+ const items = populatedObjects[key];
490
+ const first = items[0];
491
+
492
+ list.push(new PositionGroup(this, parent, levelDefinition, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1, levelDefinition.aggregateCash));
493
+
494
+ return list;
495
+ }, [ ]);
496
+
497
+ const requiredGroupsToUse = overrideRequiredGroups || levelDefinition.requiredGroups;
498
+
499
+ const missingGroups = array.difference(requiredGroupsToUse.map(group => group.key), populatedGroups.map(group => group.key))
500
+ .map((key) => {
501
+ return requiredGroupsToUse.find(g => g.key === key);
502
+ });
503
+
504
+ const empty = missingGroups.map((group) => {
505
+ return new PositionGroup(this, parent, levelDefinition, [ ], group.currency, group.key, group.description);
506
+ });
507
+
508
+ const compositeGroups = populatedGroups.concat(empty);
509
+
510
+ let builder;
511
+
512
+ if (requiredGroupsToUse.length !== 0) {
513
+ const ordering = requiredGroupsToUse.reduce((map, group, index) => {
514
+ map[group.description] = index;
515
+
516
+ return map;
517
+ }, { });
518
+
519
+ const getIndex = (description) => {
520
+ if (ordering.hasOwnProperty(description)) {
521
+ return ordering[description];
522
+ } else {
523
+ return Number.MAX_VALUE;
524
+ }
525
+ };
526
+
527
+ builder = ComparatorBuilder.startWith((a, b) => {
528
+ return comparators.compareNumbers(getIndex(a.description), getIndex(b.description));
529
+ }).thenBy((a, b) => {
530
+ return comparators.compareStrings(a.description, b.description);
531
+ });
532
+ } else {
533
+ builder = ComparatorBuilder.startWith((a, b) => {
534
+ return comparators.compareStrings(a.description, b.description);
535
+ });
536
+ }
537
+
538
+ compositeGroups.sort(builder.toComparator());
539
+
540
+ const initializeGroupObservers = (group, groupTree) => {
541
+ addGroupBinding.call(this, group, group.registerGroupExcludedChangeHandler((excluded, sender) => {
542
+ groupTree.climb((parentGroup) => {
543
+ if (parentGroup) {
544
+ let excludedItems = [];
545
+
546
+ currentTree.walk((childGroup) => {
547
+ if (childGroup.excluded) {
548
+ excludedItems = excludedItems.concat(childGroup.items);
549
+ }
550
+ }, false, false);
551
+
552
+ parentGroup.setExcludedItems(array.unique(excludedItems));
553
+ }
554
+ }, false);
555
+
556
+ if (treeDefinition.exclusionDependencies.length > 0) {
557
+ const dependantTrees = treeDefinition.exclusionDependencies.reduce((trees, name) => {
558
+ if (this._trees.hasOwnProperty(name)) {
559
+ trees.push(this._trees[name]);
560
+ }
561
+
562
+ return trees;
563
+ }, [ ]);
564
+
565
+ if (dependantTrees.length > 0) {
566
+ let excludedItems = [ ];
567
+
568
+ parentTree.walk((childGroup) => {
569
+ if (childGroup.excluded) {
570
+ excludedItems = excludedItems.concat(childGroup.items);
571
+ }
572
+ }, false, false);
573
+
574
+ dependantTrees.forEach((dependantTrees) => {
575
+ dependantTrees.walk((childGroup) => {
576
+ childGroup.setExcludedItems(excludedItems);
577
+ }, false, false);
578
+ });
579
+ }
580
+ }
581
+ }));
582
+ };
583
+
584
+ compositeGroups.forEach((group) => {
585
+ const childTree = currentTree.addChild(group);
586
+
587
+ initializeGroupObservers(group, childTree);
588
+
589
+ addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
590
+ currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
591
+ }));
592
+
593
+ createGroups.call(this, parentTree, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
594
+ });
595
+ }
596
+
526
597
  return PositionContainer;
527
598
  })();
@@ -22,6 +22,7 @@ module.exports = (() => {
22
22
  * @public
23
23
  * @param {PositionContainer} container
24
24
  * @param {PositionGroup|null} parent
25
+ * @param {LevelDefinition} definition
25
26
  * @param {Array.<PositionItem>} items
26
27
  * @param {Currency} currency
27
28
  * @param {String} key
@@ -30,8 +31,10 @@ module.exports = (() => {
30
31
  * @param {Boolean=} aggregateCash
31
32
  */
32
33
  class PositionGroup {
33
- constructor(container, parent, items, currency, key, description, single, aggregateCash) {
34
+ constructor(container, parent, definition, items, currency, key, description, single, aggregateCash) {
34
35
  this._id = counter++;
36
+
37
+ this._definition = definition;
35
38
  this._container = container;
36
39
  this._parent = parent || null;
37
40
 
@@ -209,6 +212,16 @@ module.exports = (() => {
209
212
  return this._id;
210
213
  }
211
214
 
215
+ /**
216
+ * The {@link LevelDefinition} which was used to generate this group.
217
+ *
218
+ * @public
219
+ * @returns {LevelDefinition}
220
+ */
221
+ get definition() {
222
+ return this._definition;
223
+ }
224
+
212
225
  /**
213
226
  * The key of the group.
214
227
  *
@@ -294,6 +307,16 @@ module.exports = (() => {
294
307
  return this._excluded;
295
308
  }
296
309
 
310
+ addItems(items) {
311
+
312
+ this.refresh();
313
+ }
314
+
315
+ removeItems(items) {
316
+
317
+ this.refresh();
318
+ }
319
+
297
320
  /**
298
321
  * Sets the list of items which are excluded from group aggregation calculations.
299
322
  *
@@ -1,6 +1,9 @@
1
1
  const assert = require('@barchart/common-js/lang/assert'),
2
+ Currency = require('@barchart/common-js/lang/Currency'),
2
3
  is = require('@barchart/common-js/lang/is');
3
4
 
5
+ const InstrumentType = require('./../../data/InstrumentType');
6
+
4
7
  module.exports = (() => {
5
8
  'use strict';
6
9
 
@@ -15,12 +18,12 @@ module.exports = (() => {
15
18
  * @param {PositionLevelDefinition~descriptionSelector} descriptionSelector
16
19
  * @param {PositionLevelDefinition~currencySelector} currencySelector
17
20
  * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
18
- * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
19
21
  * @param {Boolean=} single
20
22
  * @param {Boolean=} aggregateCash
23
+ * @param {Function=} injectPositions
21
24
  */
22
25
  class PositionLevelDefinition {
23
- constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash) {
26
+ constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash, requiredGroupGenerator) {
24
27
  assert.argumentIsRequired(name, 'name', String);
25
28
  assert.argumentIsRequired(keySelector, 'keySelector', Function);
26
29
  assert.argumentIsRequired(descriptionSelector, 'descriptionSelector', Function);
@@ -32,6 +35,7 @@ module.exports = (() => {
32
35
 
33
36
  assert.argumentIsOptional(single, 'single', Boolean);
34
37
  assert.argumentIsOptional(aggregateCash, 'aggregateCash', Boolean);
38
+ assert.argumentIsOptional(requiredGroupGenerator, 'requiredGroupGenerator', Function);
35
39
 
36
40
  this._name = name;
37
41
 
@@ -40,8 +44,11 @@ module.exports = (() => {
40
44
  this._currencySelector = currencySelector;
41
45
 
42
46
  this._requiredGroups = requiredGroups || [ ];
47
+
43
48
  this._single = is.boolean(single) && single;
44
49
  this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
50
+
51
+ this._requiredGroupGenerator = requiredGroupGenerator || (input => null);
45
52
  }
46
53
 
47
54
  /**
@@ -118,6 +125,96 @@ module.exports = (() => {
118
125
  return this._aggregateCash;
119
126
  }
120
127
 
128
+ /**
129
+ * Given an input, potentially creates a new {@link PositionLevelDefinition~RequiredGroup}.
130
+ *
131
+ * @public
132
+ * @param {*} input
133
+ * @returns {PositionLevelDefinition~RequiredGroup|null}
134
+ */
135
+ generateRequiredGroup(input) {
136
+ const requiredGroup = this._requiredGroupGenerator(input);
137
+
138
+ if (requiredGroup !== null) {
139
+ this._requiredGroups.push(requiredGroup);
140
+ }
141
+
142
+ return requiredGroup;
143
+ }
144
+
145
+ /**
146
+ * Builds a {@link PositionLevelDefinition~RequiredGroup} for a portfolio.
147
+ *
148
+ * @public
149
+ * @static
150
+ * @param {Object} portfolio
151
+ * @return {PositionLevelDefinition~RequiredGroup}
152
+ */
153
+ static buildRequiredGroupForPortfolio(portfolio) {
154
+ return {
155
+ key: PositionLevelDefinition.getKeyForPortfolioGroup(portfolio),
156
+ description: PositionLevelDefinition.getDescriptionForPortfolioGroup(portfolio),
157
+ currency: Currency.CAD
158
+ };
159
+ }
160
+
161
+ static getKeyForPortfolioGroup(portfolio) {
162
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
163
+
164
+ return portfolio.portfolio;
165
+ }
166
+
167
+ static getDescriptionForPortfolioGroup(portfolio) {
168
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
169
+
170
+ return portfolio.name;
171
+ }
172
+
173
+ static getRequiredGroupGeneratorForPortfolio() {
174
+ return (portfolio) => {
175
+ let requiredGroup;
176
+
177
+ if (is.object(portfolio) && is.string(portfolio.portfolio) && is.string(portfolio.name)) {
178
+ requiredGroup = PositionLevelDefinition.buildRequiredGroupForPortfolio(portfolio);
179
+ } else {
180
+ requiredGroup = null;
181
+ }
182
+
183
+ return requiredGroup;
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Builds a {@link PositionLevelDefinition~RequiredGroup} for an asset class.
189
+ *
190
+ * @public
191
+ * @static
192
+ * @param {InstrumentType} type
193
+ * @param {Currency} currency
194
+ * @return {PositionLevelDefinition~RequiredGroup}
195
+ */
196
+ static buildRequiredGroupForAssetClass(type, currency) {
197
+ return {
198
+ key: PositionLevelDefinition.getKeyForAssetClassGroup(type, currency),
199
+ description: PositionLevelDefinition.getDescriptionForAssetClassGroup(type, currency),
200
+ currency: currency
201
+ };
202
+ }
203
+
204
+ static getKeyForAssetClassGroup(type, currency) {
205
+ assert.argumentIsRequired(type, 'type', InstrumentType, 'InstrumentType');
206
+ assert.argumentIsRequired(currency, 'currency', Currency, 'Currency');
207
+
208
+ return `${type.code}|${currency.code}`;
209
+ }
210
+
211
+ static getDescriptionForAssetClassGroup(type, currency) {
212
+ assert.argumentIsRequired(type, 'type', InstrumentType, 'InstrumentType');
213
+ assert.argumentIsRequired(currency, 'currency', Currency, 'Currency');
214
+
215
+ return `${type.alternateDescription}${currency.code === 'CAD' ? '' : ` (${currency.alternateDescription})`}`;
216
+ }
217
+
121
218
  toString() {
122
219
  return '[PositionLevelDefinition]';
123
220
  }
@@ -43,7 +43,7 @@ module.exports = (() => {
43
43
  * bottom-most level of the tree (i.e. leaf nodes).
44
44
  *
45
45
  * @public
46
- * @returns {Array.<PositionTreeDefinition>}
46
+ * @returns {Array.<PositionLevelDefinitions>}
47
47
  */
48
48
  get definitions() {
49
49
  return this._definitions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.160",
3
+ "version": "1.0.164",
4
4
  "description": "Common classes used by the Portfolio system",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",
@@ -764,17 +764,9 @@ module.exports = (() => {
764
764
  const currentSummaryFrame = PositionSummaryFrame.YTD;
765
765
  const currentSummaryRange = array.last(currentSummaryFrame.getRecentRanges(0));
766
766
 
767
- this._groupBindings = { };
768
-
769
- const addGroupBinding = (group, dispoable) => {
770
- const id = group.id;
771
-
772
- if (!this._groupBindings.hasOwnProperty(id)) {
773
- this._groupBindings[id] = new DisposableStack();
774
- }
767
+ this._definitions = definitions;
775
768
 
776
- this._groupBindings[id].push(dispoable);
777
- };
769
+ this._groupBindings = { };
778
770
 
779
771
  this._portfolios = portfolios.reduce((map, portfolio) => {
780
772
  map[portfolio.portfolio] = portfolio;
@@ -880,132 +872,82 @@ module.exports = (() => {
880
872
  return Rate.fromPair(Decimal.ONE, symbol);
881
873
  });
882
874
 
883
- this._trees = definitions.reduce((map, treeDefinition) => {
875
+ this._trees = this._definitions.reduce((map, treeDefinition) => {
884
876
  const tree = new Tree();
885
877
 
886
- const createGroups = (currentTree, items, levelDefinitions) => {
887
- if (levelDefinitions.length === 0) {
888
- return;
889
- }
878
+ createGroups.call(this, tree, tree, this._items, treeDefinition, treeDefinition.definitions);
879
+
880
+ map[treeDefinition.name] = tree;
890
881
 
891
- const parent = currentTree.getValue() || null;
882
+ return map;
883
+ }, { });
884
+ }
892
885
 
893
- const levelDefinition = levelDefinitions[0];
886
+ addPortfolio(portfolio) {
887
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
888
+ assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
889
+ assert.argumentIsRequired(portfolio.name, 'portfolio.name', String);
894
890
 
895
- const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
896
- const populatedGroups = Object.keys(populatedObjects).reduce((list, key) => {
897
- const items = populatedObjects[key];
898
- const first = items[0];
891
+ const key = portfolio.portfolio;
899
892
 
900
- list.push(new PositionGroup(this, parent, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1, levelDefinition.aggregateCash));
893
+ if (!this._portfolios.hasOwnProperty(key)) {
894
+ this._portfolios[key] = portfolio;
901
895
 
902
- return list;
903
- }, [ ]);
896
+ this._definitions.forEach((treeDefinition) => {
897
+ const tree = this._trees[treeDefinition.name];
898
+ const levelDefinitions = treeDefinition.definitions;
904
899
 
905
- const missingGroups = array.difference(levelDefinition.requiredGroups.map(group => group.key), populatedGroups.map(group => group.key))
906
- .map((key) => {
907
- return levelDefinition.requiredGroups.find(g => g.key === key);
908
- });
900
+ let portfolioRequiredGroup = null;
909
901
 
910
- const empty = missingGroups.map((group) => {
911
- return new PositionGroup(this, parent, [ ], group.currency, group.key, group.description);
912
- });
902
+ let portfolioLevelDefinition = null;
903
+ let portfolioLevelDefinitionIndex = null;
913
904
 
914
- const compositeGroups = populatedGroups.concat(empty);
905
+ levelDefinitions.forEach((levelDefinition, i) => {
906
+ if (portfolioRequiredGroup === null) {
907
+ portfolioRequiredGroup = levelDefinition.generateRequiredGroup(portfolio);
915
908
 
916
- let builder;
909
+ if (portfolioRequiredGroup !== null) {
910
+ portfolioLevelDefinition = levelDefinition;
911
+ portfolioLevelDefinitionIndex = i;
912
+ }
913
+ }
914
+ });
917
915
 
918
- if (levelDefinition.requiredGroups.length !== 0) {
919
- const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
920
- map[group.description] = index;
916
+ if (portfolioRequiredGroup !== null) {
917
+ let parentTrees = [ ];
921
918
 
922
- return map;
923
- }, { });
919
+ if (portfolioLevelDefinitionIndex === 0) {
920
+ parentTrees.push(tree);
921
+ } else {
922
+ const parentLevelDefinition = levelDefinitions[ portfolioLevelDefinitionIndex - 1 ];
924
923
 
925
- const getIndex = (description) => {
926
- if (ordering.hasOwnProperty(description)) {
927
- return ordering[description];
928
- } else {
929
- return Number.MAX_VALUE;
930
- }
931
- };
924
+ tree.walk((group, groupTree) => {
925
+ if (group.definition === parentLevelDefinition) {
926
+ parentTrees.push(groupTree);
927
+ }
928
+ });
929
+ }
932
930
 
933
- builder = ComparatorBuilder.startWith((a, b) => {
934
- return comparators.compareNumbers(getIndex(a.description), getIndex(b.description));
935
- }).thenBy((a, b) => {
936
- return comparators.compareStrings(a.description, b.description);
937
- });
938
- } else {
939
- builder = ComparatorBuilder.startWith((a, b) => {
940
- return comparators.compareStrings(a.description, b.description);
931
+ const overrideRequiredGroups = [ portfolioRequiredGroup ];
932
+
933
+ parentTrees.forEach((t) => {
934
+ createGroups.call(this, tree, t, [ ], treeDefinition, levelDefinitions.slice(portfolioLevelDefinitionIndex), overrideRequiredGroups);
941
935
  });
942
936
  }
937
+ });
938
+ }
939
+ }
943
940
 
944
- compositeGroups.sort(builder.toComparator());
945
-
946
- const initializeGroupObservers = (group, groupTree) => {
947
- addGroupBinding(group, group.registerGroupExcludedChangeHandler((excluded, sender) => {
948
- groupTree.climb((parentGroup) => {
949
- if (parentGroup) {
950
- let excludedItems = [];
951
-
952
- currentTree.walk((childGroup) => {
953
- if (childGroup.excluded) {
954
- excludedItems = excludedItems.concat(childGroup.items);
955
- }
956
- }, false, false);
957
-
958
- parentGroup.setExcludedItems(array.unique(excludedItems));
959
- }
960
- }, false);
961
-
962
- if (treeDefinition.exclusionDependencies.length > 0) {
963
- const dependantTrees = treeDefinition.exclusionDependencies.reduce((trees, name) => {
964
- if (this._trees.hasOwnProperty(name)) {
965
- trees.push(this._trees[name]);
966
- }
967
-
968
- return trees;
969
- }, [ ]);
970
-
971
- if (dependantTrees.length > 0) {
972
- let excludedItems = [ ];
973
-
974
- tree.walk((childGroup) => {
975
- if (childGroup.excluded) {
976
- excludedItems = excludedItems.concat(childGroup.items);
977
- }
978
- }, false, false);
979
-
980
- dependantTrees.forEach((dependantTrees) => {
981
- dependantTrees.walk((childGroup) => {
982
- childGroup.setExcludedItems(excludedItems);
983
- }, false, false);
984
- });
985
- }
986
- }
987
- }));
988
- };
941
+ removePortfolio(portfolio) {
989
942
 
990
- compositeGroups.forEach((group) => {
991
- const childTree = currentTree.addChild(group);
943
+ }
992
944
 
993
- initializeGroupObservers(group, childTree);
945
+ mutatePosition(position, summary) {
994
946
 
995
- addGroupBinding(group, group.registerMarketPercentChangeHandler(() => {
996
- currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
997
- }));
947
+ }
998
948
 
999
- createGroups(childTree, group.items, array.dropLeft(levelDefinitions));
1000
- });
1001
- };
949
+ removePosition(position) {
1002
950
 
1003
- createGroups(tree, this._items, treeDefinition.definitions);
1004
-
1005
- map[treeDefinition.name] = tree;
1006
-
1007
- return map;
1008
- }, { });
1009
951
  }
1010
952
 
1011
953
  /**
@@ -1239,6 +1181,135 @@ module.exports = (() => {
1239
1181
  }
1240
1182
  }
1241
1183
 
1184
+ function addGroupBinding(group, dispoable) {
1185
+ const id = group.id;
1186
+
1187
+ if (!this._groupBindings.hasOwnProperty(id)) {
1188
+ this._groupBindings[id] = new DisposableStack();
1189
+ }
1190
+
1191
+ this._groupBindings[id].push(dispoable);
1192
+ }
1193
+
1194
+ function createGroups(parentTree, currentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
1195
+ if (levelDefinitions.length === 0) {
1196
+ return;
1197
+ }
1198
+
1199
+ const parent = currentTree.getValue() || null;
1200
+
1201
+ const levelDefinition = levelDefinitions[0];
1202
+
1203
+ const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
1204
+ const populatedGroups = Object.keys(populatedObjects).reduce((list, key) => {
1205
+ const items = populatedObjects[key];
1206
+ const first = items[0];
1207
+
1208
+ list.push(new PositionGroup(this, parent, levelDefinition, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1, levelDefinition.aggregateCash));
1209
+
1210
+ return list;
1211
+ }, [ ]);
1212
+
1213
+ const requiredGroupsToUse = overrideRequiredGroups || levelDefinition.requiredGroups;
1214
+
1215
+ const missingGroups = array.difference(requiredGroupsToUse.map(group => group.key), populatedGroups.map(group => group.key))
1216
+ .map((key) => {
1217
+ return requiredGroupsToUse.find(g => g.key === key);
1218
+ });
1219
+
1220
+ const empty = missingGroups.map((group) => {
1221
+ return new PositionGroup(this, parent, levelDefinition, [ ], group.currency, group.key, group.description);
1222
+ });
1223
+
1224
+ const compositeGroups = populatedGroups.concat(empty);
1225
+
1226
+ let builder;
1227
+
1228
+ if (requiredGroupsToUse.length !== 0) {
1229
+ const ordering = requiredGroupsToUse.reduce((map, group, index) => {
1230
+ map[group.description] = index;
1231
+
1232
+ return map;
1233
+ }, { });
1234
+
1235
+ const getIndex = (description) => {
1236
+ if (ordering.hasOwnProperty(description)) {
1237
+ return ordering[description];
1238
+ } else {
1239
+ return Number.MAX_VALUE;
1240
+ }
1241
+ };
1242
+
1243
+ builder = ComparatorBuilder.startWith((a, b) => {
1244
+ return comparators.compareNumbers(getIndex(a.description), getIndex(b.description));
1245
+ }).thenBy((a, b) => {
1246
+ return comparators.compareStrings(a.description, b.description);
1247
+ });
1248
+ } else {
1249
+ builder = ComparatorBuilder.startWith((a, b) => {
1250
+ return comparators.compareStrings(a.description, b.description);
1251
+ });
1252
+ }
1253
+
1254
+ compositeGroups.sort(builder.toComparator());
1255
+
1256
+ const initializeGroupObservers = (group, groupTree) => {
1257
+ addGroupBinding.call(this, group, group.registerGroupExcludedChangeHandler((excluded, sender) => {
1258
+ groupTree.climb((parentGroup) => {
1259
+ if (parentGroup) {
1260
+ let excludedItems = [];
1261
+
1262
+ currentTree.walk((childGroup) => {
1263
+ if (childGroup.excluded) {
1264
+ excludedItems = excludedItems.concat(childGroup.items);
1265
+ }
1266
+ }, false, false);
1267
+
1268
+ parentGroup.setExcludedItems(array.unique(excludedItems));
1269
+ }
1270
+ }, false);
1271
+
1272
+ if (treeDefinition.exclusionDependencies.length > 0) {
1273
+ const dependantTrees = treeDefinition.exclusionDependencies.reduce((trees, name) => {
1274
+ if (this._trees.hasOwnProperty(name)) {
1275
+ trees.push(this._trees[name]);
1276
+ }
1277
+
1278
+ return trees;
1279
+ }, [ ]);
1280
+
1281
+ if (dependantTrees.length > 0) {
1282
+ let excludedItems = [ ];
1283
+
1284
+ parentTree.walk((childGroup) => {
1285
+ if (childGroup.excluded) {
1286
+ excludedItems = excludedItems.concat(childGroup.items);
1287
+ }
1288
+ }, false, false);
1289
+
1290
+ dependantTrees.forEach((dependantTrees) => {
1291
+ dependantTrees.walk((childGroup) => {
1292
+ childGroup.setExcludedItems(excludedItems);
1293
+ }, false, false);
1294
+ });
1295
+ }
1296
+ }
1297
+ }));
1298
+ };
1299
+
1300
+ compositeGroups.forEach((group) => {
1301
+ const childTree = currentTree.addChild(group);
1302
+
1303
+ initializeGroupObservers(group, childTree);
1304
+
1305
+ addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
1306
+ currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
1307
+ }));
1308
+
1309
+ createGroups.call(this, parentTree, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
1310
+ });
1311
+ }
1312
+
1242
1313
  return PositionContainer;
1243
1314
  })();
1244
1315
 
@@ -1267,6 +1338,7 @@ module.exports = (() => {
1267
1338
  * @public
1268
1339
  * @param {PositionContainer} container
1269
1340
  * @param {PositionGroup|null} parent
1341
+ * @param {LevelDefinition} definition
1270
1342
  * @param {Array.<PositionItem>} items
1271
1343
  * @param {Currency} currency
1272
1344
  * @param {String} key
@@ -1275,8 +1347,10 @@ module.exports = (() => {
1275
1347
  * @param {Boolean=} aggregateCash
1276
1348
  */
1277
1349
  class PositionGroup {
1278
- constructor(container, parent, items, currency, key, description, single, aggregateCash) {
1350
+ constructor(container, parent, definition, items, currency, key, description, single, aggregateCash) {
1279
1351
  this._id = counter++;
1352
+
1353
+ this._definition = definition;
1280
1354
  this._container = container;
1281
1355
  this._parent = parent || null;
1282
1356
 
@@ -1454,6 +1528,16 @@ module.exports = (() => {
1454
1528
  return this._id;
1455
1529
  }
1456
1530
 
1531
+ /**
1532
+ * The {@link LevelDefinition} which was used to generate this group.
1533
+ *
1534
+ * @public
1535
+ * @returns {LevelDefinition}
1536
+ */
1537
+ get definition() {
1538
+ return this._definition;
1539
+ }
1540
+
1457
1541
  /**
1458
1542
  * The key of the group.
1459
1543
  *
@@ -1539,6 +1623,16 @@ module.exports = (() => {
1539
1623
  return this._excluded;
1540
1624
  }
1541
1625
 
1626
+ addItems(items) {
1627
+
1628
+ this.refresh();
1629
+ }
1630
+
1631
+ removeItems(items) {
1632
+
1633
+ this.refresh();
1634
+ }
1635
+
1542
1636
  /**
1543
1637
  * Sets the list of items which are excluded from group aggregation calculations.
1544
1638
  *
@@ -2295,8 +2389,11 @@ module.exports = (() => {
2295
2389
 
2296
2390
  },{"./../data/InstrumentType":1,"@barchart/common-js/lang/Currency":14,"@barchart/common-js/lang/Decimal":16,"@barchart/common-js/lang/array":20,"@barchart/common-js/lang/assert":21,"@barchart/common-js/lang/is":23,"@barchart/common-js/messaging/Event":25}],7:[function(require,module,exports){
2297
2391
  const assert = require('@barchart/common-js/lang/assert'),
2392
+ Currency = require('@barchart/common-js/lang/Currency'),
2298
2393
  is = require('@barchart/common-js/lang/is');
2299
2394
 
2395
+ const InstrumentType = require('./../../data/InstrumentType');
2396
+
2300
2397
  module.exports = (() => {
2301
2398
  'use strict';
2302
2399
 
@@ -2311,12 +2408,12 @@ module.exports = (() => {
2311
2408
  * @param {PositionLevelDefinition~descriptionSelector} descriptionSelector
2312
2409
  * @param {PositionLevelDefinition~currencySelector} currencySelector
2313
2410
  * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
2314
- * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
2315
2411
  * @param {Boolean=} single
2316
2412
  * @param {Boolean=} aggregateCash
2413
+ * @param {Function=} injectPositions
2317
2414
  */
2318
2415
  class PositionLevelDefinition {
2319
- constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash) {
2416
+ constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash, requiredGroupGenerator) {
2320
2417
  assert.argumentIsRequired(name, 'name', String);
2321
2418
  assert.argumentIsRequired(keySelector, 'keySelector', Function);
2322
2419
  assert.argumentIsRequired(descriptionSelector, 'descriptionSelector', Function);
@@ -2328,6 +2425,7 @@ module.exports = (() => {
2328
2425
 
2329
2426
  assert.argumentIsOptional(single, 'single', Boolean);
2330
2427
  assert.argumentIsOptional(aggregateCash, 'aggregateCash', Boolean);
2428
+ assert.argumentIsOptional(requiredGroupGenerator, 'requiredGroupGenerator', Function);
2331
2429
 
2332
2430
  this._name = name;
2333
2431
 
@@ -2336,8 +2434,11 @@ module.exports = (() => {
2336
2434
  this._currencySelector = currencySelector;
2337
2435
 
2338
2436
  this._requiredGroups = requiredGroups || [ ];
2437
+
2339
2438
  this._single = is.boolean(single) && single;
2340
2439
  this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
2440
+
2441
+ this._requiredGroupGenerator = requiredGroupGenerator || (input => null);
2341
2442
  }
2342
2443
 
2343
2444
  /**
@@ -2414,6 +2515,96 @@ module.exports = (() => {
2414
2515
  return this._aggregateCash;
2415
2516
  }
2416
2517
 
2518
+ /**
2519
+ * Given an input, potentially creates a new {@link PositionLevelDefinition~RequiredGroup}.
2520
+ *
2521
+ * @public
2522
+ * @param {*} input
2523
+ * @returns {PositionLevelDefinition~RequiredGroup|null}
2524
+ */
2525
+ generateRequiredGroup(input) {
2526
+ const requiredGroup = this._requiredGroupGenerator(input);
2527
+
2528
+ if (requiredGroup !== null) {
2529
+ this._requiredGroups.push(requiredGroup);
2530
+ }
2531
+
2532
+ return requiredGroup;
2533
+ }
2534
+
2535
+ /**
2536
+ * Builds a {@link PositionLevelDefinition~RequiredGroup} for a portfolio.
2537
+ *
2538
+ * @public
2539
+ * @static
2540
+ * @param {Object} portfolio
2541
+ * @return {PositionLevelDefinition~RequiredGroup}
2542
+ */
2543
+ static buildRequiredGroupForPortfolio(portfolio) {
2544
+ return {
2545
+ key: PositionLevelDefinition.getKeyForPortfolioGroup(portfolio),
2546
+ description: PositionLevelDefinition.getDescriptionForPortfolioGroup(portfolio),
2547
+ currency: Currency.CAD
2548
+ };
2549
+ }
2550
+
2551
+ static getKeyForPortfolioGroup(portfolio) {
2552
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
2553
+
2554
+ return portfolio.portfolio;
2555
+ }
2556
+
2557
+ static getDescriptionForPortfolioGroup(portfolio) {
2558
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
2559
+
2560
+ return portfolio.name;
2561
+ }
2562
+
2563
+ static getRequiredGroupGeneratorForPortfolio() {
2564
+ return (portfolio) => {
2565
+ let requiredGroup;
2566
+
2567
+ if (is.object(portfolio) && is.string(portfolio.portfolio) && is.string(portfolio.name)) {
2568
+ requiredGroup = PositionLevelDefinition.buildRequiredGroupForPortfolio(portfolio);
2569
+ } else {
2570
+ requiredGroup = null;
2571
+ }
2572
+
2573
+ return requiredGroup;
2574
+ };
2575
+ }
2576
+
2577
+ /**
2578
+ * Builds a {@link PositionLevelDefinition~RequiredGroup} for an asset class.
2579
+ *
2580
+ * @public
2581
+ * @static
2582
+ * @param {InstrumentType} type
2583
+ * @param {Currency} currency
2584
+ * @return {PositionLevelDefinition~RequiredGroup}
2585
+ */
2586
+ static buildRequiredGroupForAssetClass(type, currency) {
2587
+ return {
2588
+ key: PositionLevelDefinition.getKeyForAssetClassGroup(type, currency),
2589
+ description: PositionLevelDefinition.getDescriptionForAssetClassGroup(type, currency),
2590
+ currency: currency
2591
+ };
2592
+ }
2593
+
2594
+ static getKeyForAssetClassGroup(type, currency) {
2595
+ assert.argumentIsRequired(type, 'type', InstrumentType, 'InstrumentType');
2596
+ assert.argumentIsRequired(currency, 'currency', Currency, 'Currency');
2597
+
2598
+ return `${type.code}|${currency.code}`;
2599
+ }
2600
+
2601
+ static getDescriptionForAssetClassGroup(type, currency) {
2602
+ assert.argumentIsRequired(type, 'type', InstrumentType, 'InstrumentType');
2603
+ assert.argumentIsRequired(currency, 'currency', Currency, 'Currency');
2604
+
2605
+ return `${type.alternateDescription}${currency.code === 'CAD' ? '' : ` (${currency.alternateDescription})`}`;
2606
+ }
2607
+
2417
2608
  toString() {
2418
2609
  return '[PositionLevelDefinition]';
2419
2610
  }
@@ -2463,7 +2654,7 @@ module.exports = (() => {
2463
2654
  return PositionLevelDefinition;
2464
2655
  })();
2465
2656
 
2466
- },{"@barchart/common-js/lang/assert":21,"@barchart/common-js/lang/is":23}],8:[function(require,module,exports){
2657
+ },{"./../../data/InstrumentType":1,"@barchart/common-js/lang/Currency":14,"@barchart/common-js/lang/assert":21,"@barchart/common-js/lang/is":23}],8:[function(require,module,exports){
2467
2658
  const assert = require('@barchart/common-js/lang/assert');
2468
2659
 
2469
2660
  const PositionLevelDefinition = require('./PositionLevelDefinition');
@@ -2509,7 +2700,7 @@ module.exports = (() => {
2509
2700
  * bottom-most level of the tree (i.e. leaf nodes).
2510
2701
  *
2511
2702
  * @public
2512
- * @returns {Array.<PositionTreeDefinition>}
2703
+ * @returns {Array.<PositionLevelDefinitions>}
2513
2704
  */
2514
2705
  get definitions() {
2515
2706
  return this._definitions;