@barchart/portfolio-api-common 1.0.159 → 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,132 +156,51 @@ 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
- });
162
+ createGroups.call(this, tree, tree, this._items, treeDefinition, treeDefinition.definitions);
163
+
164
+ map[treeDefinition.name] = tree;
193
165
 
194
- const empty = missingGroups.map((group) => {
195
- return new PositionGroup(this, parent, [ ], group.currency, group.key, group.description);
196
- });
166
+ return map;
167
+ }, { });
168
+ }
197
169
 
198
- const compositeGroups = populatedGroups.concat(empty);
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);
199
174
 
200
- let builder;
175
+ const key = portfolio.portfolio;
201
176
 
202
- if (levelDefinition.requiredGroups.length !== 0) {
203
- const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
204
- map[group.description] = index;
177
+ if (!this._portfolios.hasOwnProperty(key)) {
178
+ this._portfolios[key] = portfolio;
205
179
 
206
- return map;
207
- }, { });
180
+ this._definitions.forEach((treeDefinition) => {
181
+ const tree = this._trees[treeDefinition.name];
208
182
 
209
- const getIndex = (description) => {
210
- if (ordering.hasOwnProperty(description)) {
211
- return ordering[description];
212
- } else {
213
- return Number.MAX_VALUE;
214
- }
215
- };
183
+ treeDefinition.definitions.forEach((levelDefinition) => {
184
+ const requiredGroup = levelDefinition.generateRequiredGroup(portfolio);
216
185
 
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
- }
186
+ if (requiredGroup !== null) {
227
187
 
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
- };
188
+ }
189
+ });
190
+ });
191
+ }
192
+ }
273
193
 
274
- compositeGroups.forEach((group) => {
275
- const childTree = currentTree.addChild(group);
194
+ removePortfolio(portfolio) {
276
195
 
277
- initializeGroupObservers(group, childTree);
196
+ }
278
197
 
279
- addGroupBinding(group, group.registerMarketPercentChangeHandler(() => {
280
- currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
281
- }));
198
+ mutatePosition(position, summary) {
282
199
 
283
- createGroups(childTree, group.items, array.dropLeft(levelDefinitions));
284
- });
285
- };
200
+ }
286
201
 
287
- createGroups(tree, this._items, treeDefinition.definitions);
288
-
289
- map[treeDefinition.name] = tree;
202
+ removePosition(position) {
290
203
 
291
- return map;
292
- }, { });
293
204
  }
294
205
 
295
206
  /**
@@ -523,5 +434,132 @@ module.exports = (() => {
523
434
  }
524
435
  }
525
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
+
526
564
  return PositionContainer;
527
565
  })();
@@ -171,8 +171,8 @@ module.exports = (() => {
171
171
  this._dataFormat.quoteTime = this._dataActual.quoteTime;
172
172
  this._dataFormat.quoteVolume = formatNumber(this._dataActual.quoteVolume, 0);
173
173
 
174
- const quoteChangePositive = is.number(this._dataActual.quoteChange) && this._dataActual.quoteChange > 0;
175
- const quoteChangeNegative = is.number(this._dataActual.quoteChange) && this._dataActual.quoteChange < 0;
174
+ const quoteChangePositive = quote.lastPriceDirection === 'up';
175
+ const quoteChangeNegative = quote.lastPriceDirection === 'down';
176
176
 
177
177
  setTimeout(() => this._dataFormat.quoteChangeDirection = { up: quoteChangePositive, down: quoteChangeNegative }, 0);
178
178
  this._dataFormat.quoteChangeNegative = quoteChangeNegative;
@@ -294,6 +294,16 @@ module.exports = (() => {
294
294
  return this._excluded;
295
295
  }
296
296
 
297
+ addItems(items) {
298
+
299
+ this.refresh();
300
+ }
301
+
302
+ removeItems(items) {
303
+
304
+ this.refresh();
305
+ }
306
+
297
307
  /**
298
308
  * Sets the list of items which are excluded from group aggregation calculations.
299
309
  *
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.159",
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,132 +872,51 @@ 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
- }, [ ]);
878
+ createGroups.call(this, tree, tree, this._items, treeDefinition, treeDefinition.definitions);
879
+
880
+ map[treeDefinition.name] = tree;
904
881
 
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
- });
882
+ return map;
883
+ }, { });
884
+ }
909
885
 
910
- const empty = missingGroups.map((group) => {
911
- return new PositionGroup(this, parent, [ ], group.currency, group.key, group.description);
912
- });
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);
913
890
 
914
- const compositeGroups = populatedGroups.concat(empty);
891
+ const key = portfolio.portfolio;
915
892
 
916
- let builder;
893
+ if (!this._portfolios.hasOwnProperty(key)) {
894
+ this._portfolios[key] = portfolio;
917
895
 
918
- if (levelDefinition.requiredGroups.length !== 0) {
919
- const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
920
- map[group.description] = index;
896
+ this._definitions.forEach((treeDefinition) => {
897
+ const tree = this._trees[treeDefinition.name];
921
898
 
922
- return map;
923
- }, { });
899
+ treeDefinition.definitions.forEach((levelDefinition) => {
900
+ const requiredGroup = levelDefinition.generateRequiredGroup(portfolio);
924
901
 
925
- const getIndex = (description) => {
926
- if (ordering.hasOwnProperty(description)) {
927
- return ordering[description];
928
- } else {
929
- return Number.MAX_VALUE;
930
- }
931
- };
902
+ if (requiredGroup !== null) {
932
903
 
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
- }
904
+ }
905
+ });
906
+ });
907
+ }
908
+ }
943
909
 
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
- };
910
+ removePortfolio(portfolio) {
989
911
 
990
- compositeGroups.forEach((group) => {
991
- const childTree = currentTree.addChild(group);
912
+ }
992
913
 
993
- initializeGroupObservers(group, childTree);
914
+ mutatePosition(position, summary) {
994
915
 
995
- addGroupBinding(group, group.registerMarketPercentChangeHandler(() => {
996
- currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
997
- }));
916
+ }
998
917
 
999
- createGroups(childTree, group.items, array.dropLeft(levelDefinitions));
1000
- });
1001
- };
918
+ removePosition(position) {
1002
919
 
1003
- createGroups(tree, this._items, treeDefinition.definitions);
1004
-
1005
- map[treeDefinition.name] = tree;
1006
-
1007
- return map;
1008
- }, { });
1009
920
  }
1010
921
 
1011
922
  /**
@@ -1239,6 +1150,133 @@ module.exports = (() => {
1239
1150
  }
1240
1151
  }
1241
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
+
1242
1280
  return PositionContainer;
1243
1281
  })();
1244
1282
 
@@ -1416,8 +1454,8 @@ module.exports = (() => {
1416
1454
  this._dataFormat.quoteTime = this._dataActual.quoteTime;
1417
1455
  this._dataFormat.quoteVolume = formatNumber(this._dataActual.quoteVolume, 0);
1418
1456
 
1419
- const quoteChangePositive = is.number(this._dataActual.quoteChange) && this._dataActual.quoteChange > 0;
1420
- const quoteChangeNegative = is.number(this._dataActual.quoteChange) && this._dataActual.quoteChange < 0;
1457
+ const quoteChangePositive = quote.lastPriceDirection === 'up';
1458
+ const quoteChangeNegative = quote.lastPriceDirection === 'down';
1421
1459
 
1422
1460
  setTimeout(() => this._dataFormat.quoteChangeDirection = { up: quoteChangePositive, down: quoteChangeNegative }, 0);
1423
1461
  this._dataFormat.quoteChangeNegative = quoteChangeNegative;
@@ -1539,6 +1577,16 @@ module.exports = (() => {
1539
1577
  return this._excluded;
1540
1578
  }
1541
1579
 
1580
+ addItems(items) {
1581
+
1582
+ this.refresh();
1583
+ }
1584
+
1585
+ removeItems(items) {
1586
+
1587
+ this.refresh();
1588
+ }
1589
+
1542
1590
  /**
1543
1591
  * Sets the list of items which are excluded from group aggregation calculations.
1544
1592
  *
@@ -2295,8 +2343,11 @@ module.exports = (() => {
2295
2343
 
2296
2344
  },{"./../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
2345
  const assert = require('@barchart/common-js/lang/assert'),
2346
+ Currency = require('@barchart/common-js/lang/Currency'),
2298
2347
  is = require('@barchart/common-js/lang/is');
2299
2348
 
2349
+ const InstrumentType = require('./../../data/InstrumentType');
2350
+
2300
2351
  module.exports = (() => {
2301
2352
  'use strict';
2302
2353
 
@@ -2311,12 +2362,12 @@ module.exports = (() => {
2311
2362
  * @param {PositionLevelDefinition~descriptionSelector} descriptionSelector
2312
2363
  * @param {PositionLevelDefinition~currencySelector} currencySelector
2313
2364
  * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
2314
- * @param {Array.<PositionLevelDefinition~RequiredGroup>=} requiredGroups
2315
2365
  * @param {Boolean=} single
2316
2366
  * @param {Boolean=} aggregateCash
2367
+ * @param {Function=} injectPositions
2317
2368
  */
2318
2369
  class PositionLevelDefinition {
2319
- constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash) {
2370
+ constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single, aggregateCash, requiredGroupGenerator) {
2320
2371
  assert.argumentIsRequired(name, 'name', String);
2321
2372
  assert.argumentIsRequired(keySelector, 'keySelector', Function);
2322
2373
  assert.argumentIsRequired(descriptionSelector, 'descriptionSelector', Function);
@@ -2328,6 +2379,7 @@ module.exports = (() => {
2328
2379
 
2329
2380
  assert.argumentIsOptional(single, 'single', Boolean);
2330
2381
  assert.argumentIsOptional(aggregateCash, 'aggregateCash', Boolean);
2382
+ assert.argumentIsOptional(requiredGroupGenerator, 'requiredGroupGenerator', Function);
2331
2383
 
2332
2384
  this._name = name;
2333
2385
 
@@ -2336,8 +2388,11 @@ module.exports = (() => {
2336
2388
  this._currencySelector = currencySelector;
2337
2389
 
2338
2390
  this._requiredGroups = requiredGroups || [ ];
2391
+
2339
2392
  this._single = is.boolean(single) && single;
2340
2393
  this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
2394
+
2395
+ this._requiredGroupGenerator = requiredGroupGenerator || (input => null);
2341
2396
  }
2342
2397
 
2343
2398
  /**
@@ -2414,6 +2469,96 @@ module.exports = (() => {
2414
2469
  return this._aggregateCash;
2415
2470
  }
2416
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
+
2489
+ /**
2490
+ * Builds a {@link PositionLevelDefinition~RequiredGroup} for a portfolio.
2491
+ *
2492
+ * @public
2493
+ * @static
2494
+ * @param {Object} portfolio
2495
+ * @return {PositionLevelDefinition~RequiredGroup}
2496
+ */
2497
+ static buildRequiredGroupForPortfolio(portfolio) {
2498
+ return {
2499
+ key: PositionLevelDefinition.getKeyForPortfolioGroup(portfolio),
2500
+ description: PositionLevelDefinition.getDescriptionForPortfolioGroup(portfolio),
2501
+ currency: Currency.CAD
2502
+ };
2503
+ }
2504
+
2505
+ static getKeyForPortfolioGroup(portfolio) {
2506
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
2507
+
2508
+ return portfolio.portfolio;
2509
+ }
2510
+
2511
+ static getDescriptionForPortfolioGroup(portfolio) {
2512
+ assert.argumentIsRequired(portfolio, 'portfolio', Object);
2513
+
2514
+ return portfolio.name;
2515
+ }
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
+
2531
+ /**
2532
+ * Builds a {@link PositionLevelDefinition~RequiredGroup} for an asset class.
2533
+ *
2534
+ * @public
2535
+ * @static
2536
+ * @param {InstrumentType} type
2537
+ * @param {Currency} currency
2538
+ * @return {PositionLevelDefinition~RequiredGroup}
2539
+ */
2540
+ static buildRequiredGroupForAssetClass(type, currency) {
2541
+ return {
2542
+ key: PositionLevelDefinition.getKeyForAssetClassGroup(type, currency),
2543
+ description: PositionLevelDefinition.getDescriptionForAssetClassGroup(type, currency),
2544
+ currency: currency
2545
+ };
2546
+ }
2547
+
2548
+ static getKeyForAssetClassGroup(type, currency) {
2549
+ assert.argumentIsRequired(type, 'type', InstrumentType, 'InstrumentType');
2550
+ assert.argumentIsRequired(currency, 'currency', Currency, 'Currency');
2551
+
2552
+ return `${type.code}|${currency.code}`;
2553
+ }
2554
+
2555
+ static getDescriptionForAssetClassGroup(type, currency) {
2556
+ assert.argumentIsRequired(type, 'type', InstrumentType, 'InstrumentType');
2557
+ assert.argumentIsRequired(currency, 'currency', Currency, 'Currency');
2558
+
2559
+ return `${type.alternateDescription}${currency.code === 'CAD' ? '' : ` (${currency.alternateDescription})`}`;
2560
+ }
2561
+
2417
2562
  toString() {
2418
2563
  return '[PositionLevelDefinition]';
2419
2564
  }
@@ -2463,7 +2608,7 @@ module.exports = (() => {
2463
2608
  return PositionLevelDefinition;
2464
2609
  })();
2465
2610
 
2466
- },{"@barchart/common-js/lang/assert":21,"@barchart/common-js/lang/is":23}],8:[function(require,module,exports){
2611
+ },{"./../../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
2612
  const assert = require('@barchart/common-js/lang/assert');
2468
2613
 
2469
2614
  const PositionLevelDefinition = require('./PositionLevelDefinition');