@barchart/portfolio-api-common 1.0.162 → 1.0.163

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,127 +156,10 @@ 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];
183
-
184
- list.push(new PositionGroup(this, parent, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1, levelDefinition.aggregateCash));
185
-
186
- return list;
187
- }, [ ]);
188
-
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
- });
193
-
194
- const empty = missingGroups.map((group) => {
195
- return new PositionGroup(this, parent, [ ], group.currency, group.key, group.description);
196
- });
197
-
198
- const compositeGroups = populatedGroups.concat(empty);
199
-
200
- let builder;
201
-
202
- if (levelDefinition.requiredGroups.length !== 0) {
203
- const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
204
- map[group.description] = index;
205
-
206
- return map;
207
- }, { });
208
-
209
- const getIndex = (description) => {
210
- if (ordering.hasOwnProperty(description)) {
211
- return ordering[description];
212
- } else {
213
- return Number.MAX_VALUE;
214
- }
215
- };
216
-
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
- }
227
-
228
- compositeGroups.sort(builder.toComparator());
229
-
230
- const initializeGroupObservers = (group, groupTree) => {
231
- addGroupBinding(group, group.registerGroupExcludedChangeHandler((excluded, sender) => {
232
- groupTree.climb((parentGroup) => {
233
- if (parentGroup) {
234
- let excludedItems = [];
235
-
236
- currentTree.walk((childGroup) => {
237
- if (childGroup.excluded) {
238
- excludedItems = excludedItems.concat(childGroup.items);
239
- }
240
- }, false, false);
241
-
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
- };
273
-
274
- compositeGroups.forEach((group) => {
275
- const childTree = currentTree.addChild(group);
276
-
277
- initializeGroupObservers(group, childTree);
278
-
279
- addGroupBinding(group, group.registerMarketPercentChangeHandler(() => {
280
- currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
281
- }));
282
-
283
- createGroups(childTree, group.items, array.dropLeft(levelDefinitions));
284
- });
285
- };
286
-
287
- createGroups(tree, this._items, treeDefinition.definitions);
162
+ createGroups.call(this, tree, tree, this._items, treeDefinition, treeDefinition.definitions);
288
163
 
289
164
  map[treeDefinition.name] = tree;
290
165
 
@@ -293,7 +168,27 @@ module.exports = (() => {
293
168
  }
294
169
 
295
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);
174
+
175
+ const key = portfolio.portfolio;
296
176
 
177
+ if (!this._portfolios.hasOwnProperty(key)) {
178
+ this._portfolios[key] = portfolio;
179
+
180
+ this._definitions.forEach((treeDefinition) => {
181
+ const tree = this._trees[treeDefinition.name];
182
+
183
+ treeDefinition.definitions.forEach((levelDefinition) => {
184
+ const requiredGroup = levelDefinition.generateRequiredGroup(portfolio);
185
+
186
+ if (requiredGroup !== null) {
187
+
188
+ }
189
+ });
190
+ });
191
+ }
297
192
  }
298
193
 
299
194
  removePortfolio(portfolio) {
@@ -539,5 +434,132 @@ module.exports = (() => {
539
434
  }
540
435
  }
541
436
 
437
+ function addGroupBinding(group, dispoable) {
438
+ const id = group.id;
439
+
440
+ if (!this._groupBindings.hasOwnProperty(id)) {
441
+ this._groupBindings[id] = new DisposableStack();
442
+ }
443
+
444
+ this._groupBindings[id].push(dispoable);
445
+ }
446
+
447
+ function createGroups(parentTree, currentTree, items, treeDefinition, levelDefinitions) {
448
+ if (levelDefinitions.length === 0) {
449
+ return;
450
+ }
451
+
452
+ const parent = currentTree.getValue() || null;
453
+
454
+ const levelDefinition = levelDefinitions[0];
455
+
456
+ const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
457
+ const populatedGroups = Object.keys(populatedObjects).reduce((list, key) => {
458
+ const items = populatedObjects[key];
459
+ const first = items[0];
460
+
461
+ list.push(new PositionGroup(this, parent, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1, levelDefinition.aggregateCash));
462
+
463
+ return list;
464
+ }, [ ]);
465
+
466
+ const missingGroups = array.difference(levelDefinition.requiredGroups.map(group => group.key), populatedGroups.map(group => group.key))
467
+ .map((key) => {
468
+ return levelDefinition.requiredGroups.find(g => g.key === key);
469
+ });
470
+
471
+ const empty = missingGroups.map((group) => {
472
+ return new PositionGroup(this, parent, [ ], group.currency, group.key, group.description);
473
+ });
474
+
475
+ const compositeGroups = populatedGroups.concat(empty);
476
+
477
+ let builder;
478
+
479
+ if (levelDefinition.requiredGroups.length !== 0) {
480
+ const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
481
+ map[group.description] = index;
482
+
483
+ return map;
484
+ }, { });
485
+
486
+ const getIndex = (description) => {
487
+ if (ordering.hasOwnProperty(description)) {
488
+ return ordering[description];
489
+ } else {
490
+ return Number.MAX_VALUE;
491
+ }
492
+ };
493
+
494
+ builder = ComparatorBuilder.startWith((a, b) => {
495
+ return comparators.compareNumbers(getIndex(a.description), getIndex(b.description));
496
+ }).thenBy((a, b) => {
497
+ return comparators.compareStrings(a.description, b.description);
498
+ });
499
+ } else {
500
+ builder = ComparatorBuilder.startWith((a, b) => {
501
+ return comparators.compareStrings(a.description, b.description);
502
+ });
503
+ }
504
+
505
+ compositeGroups.sort(builder.toComparator());
506
+
507
+ const initializeGroupObservers = (group, groupTree) => {
508
+ addGroupBinding.call(this, group, group.registerGroupExcludedChangeHandler((excluded, sender) => {
509
+ groupTree.climb((parentGroup) => {
510
+ if (parentGroup) {
511
+ let excludedItems = [];
512
+
513
+ currentTree.walk((childGroup) => {
514
+ if (childGroup.excluded) {
515
+ excludedItems = excludedItems.concat(childGroup.items);
516
+ }
517
+ }, false, false);
518
+
519
+ parentGroup.setExcludedItems(array.unique(excludedItems));
520
+ }
521
+ }, false);
522
+
523
+ if (treeDefinition.exclusionDependencies.length > 0) {
524
+ const dependantTrees = treeDefinition.exclusionDependencies.reduce((trees, name) => {
525
+ if (this._trees.hasOwnProperty(name)) {
526
+ trees.push(this._trees[name]);
527
+ }
528
+
529
+ return trees;
530
+ }, [ ]);
531
+
532
+ if (dependantTrees.length > 0) {
533
+ let excludedItems = [ ];
534
+
535
+ parentTree.walk((childGroup) => {
536
+ if (childGroup.excluded) {
537
+ excludedItems = excludedItems.concat(childGroup.items);
538
+ }
539
+ }, false, false);
540
+
541
+ dependantTrees.forEach((dependantTrees) => {
542
+ dependantTrees.walk((childGroup) => {
543
+ childGroup.setExcludedItems(excludedItems);
544
+ }, false, false);
545
+ });
546
+ }
547
+ }
548
+ }));
549
+ };
550
+
551
+ compositeGroups.forEach((group) => {
552
+ const childTree = currentTree.addChild(group);
553
+
554
+ initializeGroupObservers(group, childTree);
555
+
556
+ addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
557
+ currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
558
+ }));
559
+
560
+ createGroups.call(this, parentTree, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
561
+ });
562
+ }
563
+
542
564
  return PositionContainer;
543
565
  })();
@@ -20,9 +20,10 @@ module.exports = (() => {
20
20
  * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
21
21
  * @param {Boolean=} single
22
22
  * @param {Boolean=} aggregateCash
23
+ * @param {Function=} injectPositions
23
24
  */
24
25
  class PositionLevelDefinition {
25
- constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash) {
26
+ constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash, requiredGroupGenerator) {
26
27
  assert.argumentIsRequired(name, 'name', String);
27
28
  assert.argumentIsRequired(keySelector, 'keySelector', Function);
28
29
  assert.argumentIsRequired(descriptionSelector, 'descriptionSelector', Function);
@@ -34,6 +35,7 @@ module.exports = (() => {
34
35
 
35
36
  assert.argumentIsOptional(single, 'single', Boolean);
36
37
  assert.argumentIsOptional(aggregateCash, 'aggregateCash', Boolean);
38
+ assert.argumentIsOptional(requiredGroupGenerator, 'requiredGroupGenerator', Function);
37
39
 
38
40
  this._name = name;
39
41
 
@@ -42,8 +44,11 @@ module.exports = (() => {
42
44
  this._currencySelector = currencySelector;
43
45
 
44
46
  this._requiredGroups = requiredGroups || [ ];
47
+
45
48
  this._single = is.boolean(single) && single;
46
49
  this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
50
+
51
+ this._requiredGroupGenerator = requiredGroupGenerator || (input => null);
47
52
  }
48
53
 
49
54
  /**
@@ -120,6 +125,23 @@ module.exports = (() => {
120
125
  return this._aggregateCash;
121
126
  }
122
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
+
123
145
  /**
124
146
  * Builds a {@link PositionLevelDefinition~RequiredGroup} for a portfolio.
125
147
  *
@@ -136,7 +158,6 @@ module.exports = (() => {
136
158
  };
137
159
  }
138
160
 
139
-
140
161
  static getKeyForPortfolioGroup(portfolio) {
141
162
  assert.argumentIsRequired(portfolio, 'portfolio', Object);
142
163
 
@@ -149,6 +170,20 @@ module.exports = (() => {
149
170
  return portfolio.name;
150
171
  }
151
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
+
152
187
  /**
153
188
  * Builds a {@link PositionLevelDefinition~RequiredGroup} for an asset class.
154
189
  *
@@ -163,10 +198,9 @@ module.exports = (() => {
163
198
  key: PositionLevelDefinition.getKeyForAssetClassGroup(type, currency),
164
199
  description: PositionLevelDefinition.getDescriptionForAssetClassGroup(type, currency),
165
200
  currency: currency
166
- }
201
+ };
167
202
  }
168
203
 
169
-
170
204
  static getKeyForAssetClassGroup(type, currency) {
171
205
  assert.argumentIsRequired(type, 'type', InstrumentType, 'InstrumentType');
172
206
  assert.argumentIsRequired(currency, 'currency', Currency, 'Currency');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.162",
3
+ "version": "1.0.163",
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,127 +872,10 @@ 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
- }
890
-
891
- const parent = currentTree.getValue() || null;
892
-
893
- const levelDefinition = levelDefinitions[0];
894
-
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];
899
-
900
- list.push(new PositionGroup(this, parent, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1, levelDefinition.aggregateCash));
901
-
902
- return list;
903
- }, [ ]);
904
-
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
- });
909
-
910
- const empty = missingGroups.map((group) => {
911
- return new PositionGroup(this, parent, [ ], group.currency, group.key, group.description);
912
- });
913
-
914
- const compositeGroups = populatedGroups.concat(empty);
915
-
916
- let builder;
917
-
918
- if (levelDefinition.requiredGroups.length !== 0) {
919
- const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
920
- map[group.description] = index;
921
-
922
- return map;
923
- }, { });
924
-
925
- const getIndex = (description) => {
926
- if (ordering.hasOwnProperty(description)) {
927
- return ordering[description];
928
- } else {
929
- return Number.MAX_VALUE;
930
- }
931
- };
932
-
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);
941
- });
942
- }
943
-
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
- };
989
-
990
- compositeGroups.forEach((group) => {
991
- const childTree = currentTree.addChild(group);
992
-
993
- initializeGroupObservers(group, childTree);
994
-
995
- addGroupBinding(group, group.registerMarketPercentChangeHandler(() => {
996
- currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
997
- }));
998
-
999
- createGroups(childTree, group.items, array.dropLeft(levelDefinitions));
1000
- });
1001
- };
1002
-
1003
- createGroups(tree, this._items, treeDefinition.definitions);
878
+ createGroups.call(this, tree, tree, this._items, treeDefinition, treeDefinition.definitions);
1004
879
 
1005
880
  map[treeDefinition.name] = tree;
1006
881
 
@@ -1009,7 +884,27 @@ module.exports = (() => {
1009
884
  }
1010
885
 
1011
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);
890
+
891
+ const key = portfolio.portfolio;
892
+
893
+ if (!this._portfolios.hasOwnProperty(key)) {
894
+ this._portfolios[key] = portfolio;
895
+
896
+ this._definitions.forEach((treeDefinition) => {
897
+ const tree = this._trees[treeDefinition.name];
1012
898
 
899
+ treeDefinition.definitions.forEach((levelDefinition) => {
900
+ const requiredGroup = levelDefinition.generateRequiredGroup(portfolio);
901
+
902
+ if (requiredGroup !== null) {
903
+
904
+ }
905
+ });
906
+ });
907
+ }
1013
908
  }
1014
909
 
1015
910
  removePortfolio(portfolio) {
@@ -1255,6 +1150,133 @@ module.exports = (() => {
1255
1150
  }
1256
1151
  }
1257
1152
 
1153
+ function addGroupBinding(group, dispoable) {
1154
+ const id = group.id;
1155
+
1156
+ if (!this._groupBindings.hasOwnProperty(id)) {
1157
+ this._groupBindings[id] = new DisposableStack();
1158
+ }
1159
+
1160
+ this._groupBindings[id].push(dispoable);
1161
+ }
1162
+
1163
+ function createGroups(parentTree, currentTree, items, treeDefinition, levelDefinitions) {
1164
+ if (levelDefinitions.length === 0) {
1165
+ return;
1166
+ }
1167
+
1168
+ const parent = currentTree.getValue() || null;
1169
+
1170
+ const levelDefinition = levelDefinitions[0];
1171
+
1172
+ const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
1173
+ const populatedGroups = Object.keys(populatedObjects).reduce((list, key) => {
1174
+ const items = populatedObjects[key];
1175
+ const first = items[0];
1176
+
1177
+ list.push(new PositionGroup(this, parent, items, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1, levelDefinition.aggregateCash));
1178
+
1179
+ return list;
1180
+ }, [ ]);
1181
+
1182
+ const missingGroups = array.difference(levelDefinition.requiredGroups.map(group => group.key), populatedGroups.map(group => group.key))
1183
+ .map((key) => {
1184
+ return levelDefinition.requiredGroups.find(g => g.key === key);
1185
+ });
1186
+
1187
+ const empty = missingGroups.map((group) => {
1188
+ return new PositionGroup(this, parent, [ ], group.currency, group.key, group.description);
1189
+ });
1190
+
1191
+ const compositeGroups = populatedGroups.concat(empty);
1192
+
1193
+ let builder;
1194
+
1195
+ if (levelDefinition.requiredGroups.length !== 0) {
1196
+ const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
1197
+ map[group.description] = index;
1198
+
1199
+ return map;
1200
+ }, { });
1201
+
1202
+ const getIndex = (description) => {
1203
+ if (ordering.hasOwnProperty(description)) {
1204
+ return ordering[description];
1205
+ } else {
1206
+ return Number.MAX_VALUE;
1207
+ }
1208
+ };
1209
+
1210
+ builder = ComparatorBuilder.startWith((a, b) => {
1211
+ return comparators.compareNumbers(getIndex(a.description), getIndex(b.description));
1212
+ }).thenBy((a, b) => {
1213
+ return comparators.compareStrings(a.description, b.description);
1214
+ });
1215
+ } else {
1216
+ builder = ComparatorBuilder.startWith((a, b) => {
1217
+ return comparators.compareStrings(a.description, b.description);
1218
+ });
1219
+ }
1220
+
1221
+ compositeGroups.sort(builder.toComparator());
1222
+
1223
+ const initializeGroupObservers = (group, groupTree) => {
1224
+ addGroupBinding.call(this, group, group.registerGroupExcludedChangeHandler((excluded, sender) => {
1225
+ groupTree.climb((parentGroup) => {
1226
+ if (parentGroup) {
1227
+ let excludedItems = [];
1228
+
1229
+ currentTree.walk((childGroup) => {
1230
+ if (childGroup.excluded) {
1231
+ excludedItems = excludedItems.concat(childGroup.items);
1232
+ }
1233
+ }, false, false);
1234
+
1235
+ parentGroup.setExcludedItems(array.unique(excludedItems));
1236
+ }
1237
+ }, false);
1238
+
1239
+ if (treeDefinition.exclusionDependencies.length > 0) {
1240
+ const dependantTrees = treeDefinition.exclusionDependencies.reduce((trees, name) => {
1241
+ if (this._trees.hasOwnProperty(name)) {
1242
+ trees.push(this._trees[name]);
1243
+ }
1244
+
1245
+ return trees;
1246
+ }, [ ]);
1247
+
1248
+ if (dependantTrees.length > 0) {
1249
+ let excludedItems = [ ];
1250
+
1251
+ parentTree.walk((childGroup) => {
1252
+ if (childGroup.excluded) {
1253
+ excludedItems = excludedItems.concat(childGroup.items);
1254
+ }
1255
+ }, false, false);
1256
+
1257
+ dependantTrees.forEach((dependantTrees) => {
1258
+ dependantTrees.walk((childGroup) => {
1259
+ childGroup.setExcludedItems(excludedItems);
1260
+ }, false, false);
1261
+ });
1262
+ }
1263
+ }
1264
+ }));
1265
+ };
1266
+
1267
+ compositeGroups.forEach((group) => {
1268
+ const childTree = currentTree.addChild(group);
1269
+
1270
+ initializeGroupObservers(group, childTree);
1271
+
1272
+ addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
1273
+ currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
1274
+ }));
1275
+
1276
+ createGroups.call(this, parentTree, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
1277
+ });
1278
+ }
1279
+
1258
1280
  return PositionContainer;
1259
1281
  })();
1260
1282
 
@@ -2342,9 +2364,10 @@ module.exports = (() => {
2342
2364
  * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
2343
2365
  * @param {Boolean=} single
2344
2366
  * @param {Boolean=} aggregateCash
2367
+ * @param {Function=} injectPositions
2345
2368
  */
2346
2369
  class PositionLevelDefinition {
2347
- constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash) {
2370
+ constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash, requiredGroupGenerator) {
2348
2371
  assert.argumentIsRequired(name, 'name', String);
2349
2372
  assert.argumentIsRequired(keySelector, 'keySelector', Function);
2350
2373
  assert.argumentIsRequired(descriptionSelector, 'descriptionSelector', Function);
@@ -2356,6 +2379,7 @@ module.exports = (() => {
2356
2379
 
2357
2380
  assert.argumentIsOptional(single, 'single', Boolean);
2358
2381
  assert.argumentIsOptional(aggregateCash, 'aggregateCash', Boolean);
2382
+ assert.argumentIsOptional(requiredGroupGenerator, 'requiredGroupGenerator', Function);
2359
2383
 
2360
2384
  this._name = name;
2361
2385
 
@@ -2364,8 +2388,11 @@ module.exports = (() => {
2364
2388
  this._currencySelector = currencySelector;
2365
2389
 
2366
2390
  this._requiredGroups = requiredGroups || [ ];
2391
+
2367
2392
  this._single = is.boolean(single) && single;
2368
2393
  this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
2394
+
2395
+ this._requiredGroupGenerator = requiredGroupGenerator || (input => null);
2369
2396
  }
2370
2397
 
2371
2398
  /**
@@ -2442,6 +2469,23 @@ module.exports = (() => {
2442
2469
  return this._aggregateCash;
2443
2470
  }
2444
2471
 
2472
+ /**
2473
+ * Given an input, potentially creates a new {@link PositionLevelDefinition~RequiredGroup}.
2474
+ *
2475
+ * @public
2476
+ * @param {*} input
2477
+ * @returns {PositionLevelDefinition~RequiredGroup|null}
2478
+ */
2479
+ generateRequiredGroup(input) {
2480
+ const requiredGroup = this._requiredGroupGenerator(input);
2481
+
2482
+ if (requiredGroup !== null) {
2483
+ this._requiredGroups.push(requiredGroup);
2484
+ }
2485
+
2486
+ return requiredGroup;
2487
+ }
2488
+
2445
2489
  /**
2446
2490
  * Builds a {@link PositionLevelDefinition~RequiredGroup} for a portfolio.
2447
2491
  *
@@ -2458,7 +2502,6 @@ module.exports = (() => {
2458
2502
  };
2459
2503
  }
2460
2504
 
2461
-
2462
2505
  static getKeyForPortfolioGroup(portfolio) {
2463
2506
  assert.argumentIsRequired(portfolio, 'portfolio', Object);
2464
2507
 
@@ -2471,6 +2514,20 @@ module.exports = (() => {
2471
2514
  return portfolio.name;
2472
2515
  }
2473
2516
 
2517
+ static getRequiredGroupGeneratorForPortfolio() {
2518
+ return (portfolio) => {
2519
+ let requiredGroup;
2520
+
2521
+ if (is.object(portfolio) && is.string(portfolio.portfolio) && is.string(portfolio.name)) {
2522
+ requiredGroup = PositionLevelDefinition.buildRequiredGroupForPortfolio(portfolio);
2523
+ } else {
2524
+ requiredGroup = null;
2525
+ }
2526
+
2527
+ return requiredGroup;
2528
+ };
2529
+ }
2530
+
2474
2531
  /**
2475
2532
  * Builds a {@link PositionLevelDefinition~RequiredGroup} for an asset class.
2476
2533
  *
@@ -2485,10 +2542,9 @@ module.exports = (() => {
2485
2542
  key: PositionLevelDefinition.getKeyForAssetClassGroup(type, currency),
2486
2543
  description: PositionLevelDefinition.getDescriptionForAssetClassGroup(type, currency),
2487
2544
  currency: currency
2488
- }
2545
+ };
2489
2546
  }
2490
2547
 
2491
-
2492
2548
  static getKeyForAssetClassGroup(type, currency) {
2493
2549
  assert.argumentIsRequired(type, 'type', InstrumentType, 'InstrumentType');
2494
2550
  assert.argumentIsRequired(currency, 'currency', Currency, 'Currency');