@barchart/portfolio-api-common 1.0.96 → 1.0.97

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.
@@ -8,7 +8,7 @@ module.exports = (() => {
8
8
  'use strict';
9
9
 
10
10
  /**
11
- * Static configuration data.
11
+ * Static utility for formatting transaction data in human-readable form.
12
12
  *
13
13
  * @public
14
14
  */
@@ -18,54 +18,66 @@ module.exports = (() => {
18
18
  }
19
19
 
20
20
  /**
21
- * Formats transactions by adding "formatted" attribute
22
- * and "instrument" attribute from position
21
+ * Maps transaction objects into new objects whose properties are human-readable or,
22
+ * optionally returns the original objects with a "formatted" property appended to
23
+ * each transaction.
23
24
  *
24
25
  * @public
25
26
  * @static
26
- * @param {Array} transactions
27
- * @param {Array} positions
28
- * @param {String} returnType - Whether the return values should be appended to the transaction
29
- *
27
+ * @param {Array<Object>} transactions
28
+ * @param {Array<Object>} positions
29
+ * @param {Boolean=} append
30
30
  * @returns {Array}
31
31
  */
32
- static format(transactions, positions, returnType) {
33
- assert.argumentIsRequired(transactions, 'transactions', Array);
34
- assert.argumentIsRequired(positions, 'positions', Array);
32
+ static format(transactions, positions, append) {
33
+ assert.argumentIsArray(transactions, 'transactions');
34
+ assert.argumentIsArray(positions, 'positions');
35
+ assert.argumentIsOptional(append, 'append', Boolean);
35
36
 
36
- const positionMap = {};
37
+ const instruments = positions.reduce((map, p) => {
38
+ map[p.position] = p.instrument;
37
39
 
38
- positions.map(p => positionMap[p.position] = p.instrument);
40
+ return map;
41
+ }, { });
39
42
 
40
- return transactions.filter(t => positionMap[t.position]).map((transaction) => {
41
- transaction.instrument = positionMap[transaction.position];
43
+ return transactions.reduce((list, transaction) => {
44
+ const position = transaction.position;
42
45
 
43
- let formatted = getBasicTransaction(transaction);
46
+ if (instruments.hasOwnProperty(position)) {
47
+ transaction.instrument = instruments[position];
44
48
 
45
- if (formatters.has(transaction.type)) {
46
- const formatterFunction = formatters.get(transaction.type);
49
+ let formatted = getBasicTransaction(transaction);
47
50
 
48
- const formattedTransaction = formatterFunction(transaction);
51
+ if (formatters.has(transaction.type)) {
52
+ const formatterFunction = formatters.get(transaction.type);
53
+ const formattedTransaction = formatterFunction(transaction);
49
54
 
50
- Object.keys(formattedTransaction).map((key) => {
51
- if (!is.undefined(formattedTransaction[key]) && is.fn(formattedTransaction[key].toFloat)) {
52
- const precision = transaction.instrument.currency.precision;
55
+ Object.keys(formattedTransaction).map((key) => {
56
+ if (!is.undefined(formattedTransaction[key]) && is.fn(formattedTransaction[key].toFloat)) {
57
+ const precision = transaction.instrument.currency.precision;
53
58
 
54
- formattedTransaction[key] = formatter.numberToString(formattedTransaction[key].toFloat(), precision, ',');
55
- }
56
- });
59
+ formattedTransaction[key] = formatter.numberToString(formattedTransaction[key].toFloat(), precision, ',');
60
+ }
61
+ });
57
62
 
58
- formatted = Object.assign({}, formatted, formattedTransaction);
59
- }
63
+ formatted = Object.assign({}, formatted, formattedTransaction);
64
+ }
65
+
66
+ let transactionToInsert;
60
67
 
61
- if (returnType === 'append') {
62
- transaction.formatted = formatted;
68
+ if (append) {
69
+ transaction.formatted = formatted;
63
70
 
64
- return transaction;
65
- } else {
66
- return formatted;
71
+ transactionToInsert = transaction;
72
+ } else {
73
+ transactionToInsert = formatted;
74
+ }
75
+
76
+ list.push(transactionToInsert);
67
77
  }
68
- });
78
+
79
+ return list;
80
+ }, [ ]);
69
81
  }
70
82
 
71
83
  toString() {
@@ -1,13 +1,16 @@
1
1
  const array = require('@barchart/common-js/lang/array'),
2
+ assert = require('@barchart/common-js/lang/assert'),
2
3
  ComparatorBuilder = require('@barchart/common-js/collections/sorting/ComparatorBuilder'),
3
4
  comparators = require('@barchart/common-js/collections/sorting/comparators'),
4
5
  Currency = require('@barchart/common-js/lang/Currency'),
5
- assert = require('@barchart/common-js/lang/assert'),
6
6
  is = require('@barchart/common-js/lang/is'),
7
7
  Tree = require('@barchart/common-js/collections/Tree');
8
8
 
9
9
  const PositionSummaryFrame = require('./../data/PositionSummaryFrame');
10
10
 
11
+ const PositionLevelDefinition = require('./definitions/PositionLevelDefinition'),
12
+ PositionTreeDefinition = require('./definitions/PositionTreeDefinition');
13
+
11
14
  const PositionGroup = require('./PositionGroup'),
12
15
  PositionItem = require('./PositionItem');
13
16
 
@@ -15,22 +18,32 @@ module.exports = (() => {
15
18
  'use strict';
16
19
 
17
20
  /**
21
+ * A container for positions which groups the positions into one or more
22
+ * trees for aggregation and display purposes. For example, perhaps a positions
23
+ * grouped first by asset class then by position is desired.
24
+ *
25
+ * Furthermore, the container performs aggregation (driven primarily by price
26
+ * changes) for each level of grouping in the internal tree(s).
27
+ *
18
28
  * @public
29
+ * @param {Array.<PositionTreeDefinition>} definitions
30
+ * @param {Array.<Object>} portfolios
31
+ * @param {Array.<Object>} positions
32
+ * @param {Array.<Object>} summaries
19
33
  */
20
34
  class PositionContainer {
21
- constructor(portfolios, positions, summaries, definitions, defaultCurrency, summaryFrame) {
22
- this._definitions = definitions;
23
- this._defaultCurrency = defaultCurrency || Currency.CAD;
35
+ constructor(definitions, portfolios, positions, summaries) {
36
+ assert.argumentIsArray(definitions, 'definitions', PositionTreeDefinition, 'PositionTreeDefinition');
37
+ assert.argumentIsArray(portfolios, 'portfolios');
38
+ assert.argumentIsArray(positions, 'positions');
39
+ assert.argumentIsArray(summaries, 'summaries');
24
40
 
25
- const previousSummaryFrame = summaryFrame || PositionSummaryFrame.YEARLY;
41
+ const previousSummaryFrame = PositionSummaryFrame.YEARLY;
26
42
  const previousSummaryRanges = previousSummaryFrame.getRecentRanges(0);
27
43
 
28
44
  const currentSummaryFrame = PositionSummaryFrame.YTD;
29
45
  const currentSummaryRange = array.last(currentSummaryFrame.getRecentRanges(0));
30
46
 
31
- this._summaryDescriptionCurrent = previousSummaryFrame.describeRange(array.last(previousSummaryRanges));
32
- this._summaryDescriptionPrevious = currentSummaryFrame.describeRange(currentSummaryRange);
33
-
34
47
  this._portfolios = portfolios.reduce((map, portfolio) => {
35
48
  map[portfolio.portfolio] = portfolio;
36
49
 
@@ -109,111 +122,101 @@ module.exports = (() => {
109
122
 
110
123
  return map;
111
124
  }, { });
125
+
126
+ this._trees = definitions.reduce((map, treeDefinition) => {
127
+ const tree = new Tree();
112
128
 
113
- this._tree = new Tree();
114
-
115
- const createGroups = (tree, items, definitions) => {
116
- if (definitions.length === 0) {
117
- return;
118
- }
119
-
120
- const parent = tree.getValue() || null;
121
-
122
- const currentDefinition = definitions[0];
123
- const additionalDefinitions = array.dropLeft(definitions);
124
-
125
- const populatedObjects = array.groupBy(items, currentDefinition.keySelector);
126
- const populatedGroups = Object.keys(populatedObjects).map(key => populatedObjects[key]).map((items) => {
127
- const first = items[0];
128
-
129
- return new PositionGroup(this, parent, items, currentDefinition.currencySelector(first), currentDefinition.descriptionSelector(first), currentDefinition.single && items.length === 1);
130
- });
129
+ const createGroups = (currentTree, items, levelDefinitions) => {
130
+ if (levelDefinitions.length === 0) {
131
+ return;
132
+ }
131
133
 
132
- const missingGroups = array.difference(currentDefinition.requiredGroups.map(group => group.description), populatedGroups.map(group => group.description));
134
+ const parent = currentTree.getValue() || null;
133
135
 
134
- const empty = missingGroups.map((description) => {
135
- return new PositionGroup(this, parent, [ ], currentDefinition.requiredGroups.find(group => group.description === description).currency, description);
136
- });
136
+ const levelDefinition = levelDefinitions[0];
137
137
 
138
- const compositeGroups = populatedGroups.concat(empty);
138
+ const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
139
+ const populatedGroups = Object.keys(populatedObjects).map(key => populatedObjects[key]).map((items) => {
140
+ const first = items[0];
139
141
 
140
- let builder;
142
+ return new PositionGroup(this, parent, items, levelDefinition.currencySelector(first), levelDefinition.descriptionSelector(first), levelDefinition.single && items.length === 1);
143
+ });
141
144
 
142
- if (currentDefinition.requiredGroups.length !== 0) {
143
- const ordering = currentDefinition.requiredGroups.reduce((map, group, index) => {
144
- map[group.description] = index;
145
+ const missingGroups = array.difference(levelDefinition.requiredGroups.map(group => group.description), populatedGroups.map(group => group.description));
145
146
 
146
- return map;
147
- }, { });
147
+ const empty = missingGroups.map((description) => {
148
+ return new PositionGroup(this, parent, [ ], levelDefinition.requiredGroups.find(group => group.description === description).currency, description);
149
+ });
148
150
 
149
- const getIndex = (description) => {
150
- if (ordering.hasOwnProperty(description)) {
151
- return ordering[description];
152
- } else {
153
- return Number.MAX_VALUE;
154
- }
155
- };
151
+ const compositeGroups = populatedGroups.concat(empty);
152
+
153
+ let builder;
154
+
155
+ if (levelDefinition.requiredGroups.length !== 0) {
156
+ const ordering = levelDefinition.requiredGroups.reduce((map, group, index) => {
157
+ map[group.description] = index;
158
+
159
+ return map;
160
+ }, { });
161
+
162
+ const getIndex = (description) => {
163
+ if (ordering.hasOwnProperty(description)) {
164
+ return ordering[description];
165
+ } else {
166
+ return Number.MAX_VALUE;
167
+ }
168
+ };
169
+
170
+ builder = ComparatorBuilder.startWith((a, b) => {
171
+ return comparators.compareNumbers(getIndex(a.description), getIndex(b.description));
172
+ }).thenBy((a, b) => {
173
+ return comparators.compareStrings(a.description, b.description);
174
+ });
175
+ } else {
176
+ builder = ComparatorBuilder.startWith((a, b) => {
177
+ return comparators.compareStrings(a.description, b.description);
178
+ });
179
+ }
156
180
 
157
- builder = ComparatorBuilder.startWith((a, b) => {
158
- return comparators.compareNumbers(getIndex(a.description), getIndex(b.description));
159
- }).thenBy((a, b) => {
160
- return comparators.compareStrings(a.description, b.description);
161
- });
162
- } else {
163
- builder = ComparatorBuilder.startWith((a, b) => {
164
- return comparators.compareStrings(a.description, b.description);
165
- });
166
- }
181
+ compositeGroups.sort(builder.toComparator());
167
182
 
168
- compositeGroups.sort(builder.toComparator());
183
+ compositeGroups.forEach((group) => {
184
+ const childTree = currentTree.addChild(group);
169
185
 
170
- compositeGroups.forEach((group) => {
171
- const child = tree.addChild(group);
186
+ group.registerMarketPercentChangeHandler(() => {
187
+ currentTree.walk((childGroup) => childGroup.refreshMarketPercent());
188
+ });
172
189
 
173
- group.registerMarketPercentChangeHandler(() => {
174
- this._tree.walk((childGroup) => childGroup.refreshMarketPercent());
190
+ createGroups(childTree, group.items, array.dropLeft(levelDefinitions));
175
191
  });
192
+ };
176
193
 
177
- createGroups(child, group.items, additionalDefinitions);
178
- });
179
- };
194
+ createGroups(tree, this._items, treeDefinition.definitions);
195
+
196
+ map[treeDefinition.name] = tree;
180
197
 
181
- createGroups(this._tree, this._items, this._definitions);
198
+ return map;
199
+ }, { });
182
200
  }
183
201
 
184
202
  get defaultCurrency() {
185
203
  return this._defaultCurrency;
186
204
  }
187
205
 
188
- getCurrentSummaryDescription() {
189
- return this._summaryDescriptionCurrent;
190
- }
191
-
192
- getPreviousSummaryDescription() {
193
- return this._summaryDescriptionPrevious;
194
- }
195
-
196
- startTransaction(executor) {
197
- assert.argumentIsRequired(executor, 'executor', Function);
198
-
199
- this._tree.walk(group => group.setSuspended(true), false, false);
200
-
201
- executor(this);
202
-
203
- this._tree.walk(group => group.setSuspended(false), false, false);
204
- }
205
-
206
- getSymbols() {
206
+ getPositionSymbols() {
207
207
  return Object.keys(this._symbols);
208
208
  }
209
209
 
210
- setPrice(symbol, price) {
211
- if (this._symbols.hasOwnProperty(symbol)) {
210
+ setPositionPrice(symbol, price) {
211
+ assert.argumentIsOptional(symbol, 'symbol', String);
212
+ assert.argumentIsOptional(price, 'price', Number);
213
+
214
+ if (this._symbols.hasOwnProperty(symbol) && is.number(price)) {
212
215
  this._symbols[symbol].forEach(item => item.setPrice(price));
213
216
  }
214
217
  }
215
218
 
216
- getCurrencySymbols() {
219
+ getForexSymbols() {
217
220
  const codes = Object.keys(this._currencies);
218
221
 
219
222
  return codes.reduce((symbols, code) => {
@@ -225,28 +228,38 @@ module.exports = (() => {
225
228
  }, [ ]);
226
229
  }
227
230
 
228
- setExchangeRate(symbol, price) {
231
+ setForexPrice(symbol, price) {
232
+ assert.argumentIsOptional(symbol, 'symbol', String);
233
+ assert.argumentIsOptional(price, 'price', Number);
229
234
 
235
+ return;
230
236
  }
231
237
 
232
- getGroup(keys) {
233
- const node = keys.reduce((tree, key) => {
234
- tree = tree.findChild(group => group.description === key);
238
+ getGroup(name, keys) {
239
+ assert.argumentIsRequired(name, 'name', String);
240
+ assert.argumentIsArray(keys, 'keys', Number);
235
241
 
236
- return tree;
237
- }, this._tree);
242
+ return findNode(this._trees[name], keys).getValue();
243
+ }
244
+
245
+ getGroups(name, keys) {
246
+ assert.argumentIsRequired(name, 'name', String);
247
+ assert.argumentIsArray(keys, 'keys', Number);
238
248
 
239
- return node.getValue();
249
+ return findNode(this._trees[name], keys).getChildren().map(node => node.getValue());
240
250
  }
241
251
 
242
- getGroups(keys) {
243
- const node = keys.reduce((tree, key) => {
244
- tree = tree.findChild(group => group.description === key);
252
+ startTransaction(name, executor) {
253
+ assert.argumentIsRequired(name, 'name', String);
254
+ assert.argumentIsRequired(executor, 'executor', Function);
255
+
256
+ assert.argumentIsRequired(executor, 'executor', Function);
257
+
258
+ this._trees[name].walk(group => group.setSuspended(true), false, false);
245
259
 
246
- return tree;
247
- }, this._tree);
260
+ executor(this);
248
261
 
249
- return node.getChildren().map((node) => node.getValue());
262
+ this._trees[name].walk(group => group.setSuspended(false), false, false);
250
263
  }
251
264
 
252
265
  toString() {
@@ -254,6 +267,14 @@ module.exports = (() => {
254
267
  }
255
268
  }
256
269
 
270
+ function findNode(tree, keys) {
271
+ return keys.reduce((tree, key) => {
272
+ tree = tree.findChild(group => group.description === key);
273
+
274
+ return tree;
275
+ }, tree);
276
+ }
277
+
257
278
  function getSummaryArray(ranges) {
258
279
  return ranges.map(range => null);
259
280
  }
@@ -0,0 +1,143 @@
1
+ const assert = require('@barchart/common-js/lang/assert'),
2
+ is = require('@barchart/common-js/lang/is');
3
+
4
+ module.exports = (() => {
5
+ 'use strict';
6
+
7
+ /**
8
+ * Defines a grouping level within a tree of positions. A level could represent a
9
+ * group of multiple positions (e.g. all equities or all positions for a portfolio).
10
+ * Alternately, a level could also represent a single position.
11
+ *
12
+ * @public
13
+ * @param {String} name
14
+ * @param {PositionLevelDefinition~keySelector} keySelector
15
+ * @param {PositionLevelDefinition~descriptionSelector} descriptionSelector
16
+ * @param {PositionLevelDefinition~currencySelector} currencySelector
17
+ * @param {Array.<String>=} requiredGroups
18
+ * @param {Boolean=} single
19
+ */
20
+ class PositionLevelDefinition {
21
+ constructor(name, keySelector, descriptionSelector, currencySelector, requiredGroups, single) {
22
+ assert.argumentIsRequired(name, 'name', String);
23
+ assert.argumentIsRequired(keySelector, 'keySelector', Function);
24
+ assert.argumentIsRequired(descriptionSelector, 'descriptionSelector', Function);
25
+ assert.argumentIsRequired(currencySelector, 'currencySelector', Function);
26
+
27
+ if (requiredGroups) {
28
+ assert.argumentIsArray(requiredGroups, 'requiredGroups', String);
29
+ }
30
+
31
+ assert.argumentIsOptional(single, 'single', Boolean);
32
+
33
+ this._name = name;
34
+
35
+ this._keySelector = keySelector;
36
+ this._descriptionSelector = descriptionSelector;
37
+ this._currencySelector = currencySelector;
38
+
39
+ this._requiredGroups = requiredGroups || [ ];
40
+ this._single = is.boolean(single) && single;
41
+ }
42
+
43
+ /**
44
+ * The name of the grouping level.
45
+ *
46
+ * @public
47
+ * @returns {String}
48
+ */
49
+ get name() {
50
+ return this._name;
51
+ }
52
+
53
+ /**
54
+ * A function, when given a {@link PositionItem} returns a string that is used
55
+ * to group {@link PositionItem} instances into different groups.
56
+ *
57
+ * @public
58
+ * @returns {PositionLevelDefinition~keySelector}
59
+ */
60
+ get keySelector() {
61
+ return this._keySelector;
62
+ }
63
+
64
+ /**
65
+ * A function, when given a {@link PositionItem} returns a string used to describe the
66
+ * group.
67
+ *
68
+ * @public
69
+ * @returns {PositionLevelDefinition~descriptionSelector}
70
+ */
71
+ get descriptionSelector() {
72
+ return this._descriptionSelector;
73
+ }
74
+
75
+ /**
76
+ * A function, when given a {@link PositionItem} returns the {@link Currency} used to
77
+ * display values for the group.
78
+ *
79
+ * @public
80
+ * @returns {PositionLevelDefinition~currencySelector}
81
+ */
82
+ get currencySelector() {
83
+ return this._currencySelector;
84
+ }
85
+
86
+ /**
87
+ * Indicates the required groups (i.e. descriptions). The allows for the creation of empty
88
+ * groups.
89
+ *
90
+ * @public
91
+ * @returns {Array<String>}
92
+ */
93
+ get requiredGroups() {
94
+ return this._requiredGroups;
95
+ }
96
+
97
+ /**
98
+ * Indicates if the grouping level is meant to only contain a single item.
99
+ *
100
+ * @public
101
+ * @returns {Boolean}
102
+ */
103
+ get single() {
104
+ return this._single;
105
+ }
106
+
107
+ toString() {
108
+ return '[PositionLevelDefinition]';
109
+ }
110
+ }
111
+
112
+ /**
113
+ * A callback used to determine the eligibility for membership of a {@link PositionItem}
114
+ * within a group.
115
+ *
116
+ * @public
117
+ * @callback PositionLevelDefinition~keySelector
118
+ * @param {PositionItem} session
119
+ * @returns {String}
120
+ */
121
+
122
+ /**
123
+ * A callback used to determine the human-readable name of a group. This function should
124
+ * return the same value for any {@link PositionItem} in the group.
125
+ *
126
+ * @public
127
+ * @callback PositionLevelDefinition~descriptionSelector
128
+ * @param {PositionItem} session
129
+ * @returns {String}
130
+ */
131
+
132
+ /**
133
+ * A callback used to determine the display {@link Currency} for the group. This function should
134
+ * return the same value for any {@link PositionItem} in the group.
135
+ *
136
+ * @public
137
+ * @callback PositionLevelDefinition~currencySelector
138
+ * @param {PositionItem} session
139
+ * @returns {Currency}
140
+ */
141
+
142
+ return PositionLevelDefinition;
143
+ })();
@@ -0,0 +1,52 @@
1
+ const assert = require('@barchart/common-js/lang/assert');
2
+
3
+ const PositionLevelDefinition = require('./PositionLevelDefinition');
4
+
5
+ module.exports = (() => {
6
+ 'use strict';
7
+
8
+ /**
9
+ * Defines the structure for a tree of positions.
10
+ *
11
+ * @public
12
+ * @param {String} name
13
+ * @param {Array.<PositionLevelDefinition>} definitions
14
+ */
15
+ class PositionTreeDefinitions {
16
+ constructor(name, definitions) {
17
+ assert.argumentIsRequired(name, 'name', String);
18
+ assert.argumentIsArray(definitions, 'definitions', PositionLevelDefinition, 'PositionLevelDefinition');
19
+
20
+ this._name = name;
21
+ this._definitions = definitions;
22
+ }
23
+
24
+ /**
25
+ * The name of the tree.
26
+ *
27
+ * @returns {String}
28
+ */
29
+ get name() {
30
+ return this._name;
31
+ }
32
+
33
+ /**
34
+ * An ordered list of {@link PositionLevelDefinitions} that describes the
35
+ * levels of the tree. The first item represents the top-most level of the
36
+ * tree (i.e. the children of the root node) and the last item represents the
37
+ * bottom-most level of the tree (i.e. leaf nodes).
38
+ *
39
+ * @public
40
+ * @returns {Array.<PositionTreeDefinition>}
41
+ */
42
+ get definitions() {
43
+ return this._definitions;
44
+ }
45
+
46
+ toString() {
47
+ return '[PositionTreeDefinitions]';
48
+ }
49
+ }
50
+
51
+ return PositionTreeDefinitions;
52
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.96",
3
+ "version": "1.0.97",
4
4
  "description": "Common classes used by the Portfolio system",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",