@barchart/portfolio-api-common 1.0.68 → 1.0.72

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.
@@ -1,6 +1,7 @@
1
1
  const array = require('@barchart/common-js/lang/array'),
2
2
  assert = require('@barchart/common-js/lang/assert'),
3
3
  Day = require('@barchart/common-js/lang/Day'),
4
+ Decimal = require('@barchart/common-js/lang/Decimal'),
4
5
  Enum = require('@barchart/common-js/lang/Enum'),
5
6
  is = require('@barchart/common-js/lang/is');
6
7
 
@@ -16,23 +17,68 @@ module.exports = (() => {
16
17
  * @param {String} description
17
18
  * @param {Function} rangeCalculator
18
19
  * @param {Function} startDateCalculator
20
+ * @param {Function} descriptionCalculator
19
21
  */
20
22
  class PositionSummaryFrame extends Enum {
21
- constructor(code, description, rangeCalculator, startDateCalculator) {
23
+ constructor(code, description, rangeCalculator, startDateCalculator, descriptionCalculator) {
22
24
  super(code, description);
23
25
 
24
26
  assert.argumentIsRequired(rangeCalculator, 'rangeCalculator', Function);
27
+ assert.argumentIsRequired(startDateCalculator, 'startDateCalculator', Function);
28
+ assert.argumentIsRequired(descriptionCalculator, 'descriptionCalculator', Function);
25
29
 
26
30
  this._rangeCalculator = rangeCalculator;
27
31
  this._startDateCalculator = startDateCalculator;
32
+ this._descriptionCalculator = descriptionCalculator;
28
33
  }
29
34
 
35
+ /**
36
+ * Returns a human-readable description of the frame, given
37
+ * start and end dates.
38
+ *
39
+ * @public
40
+ * @param {Day} startDate
41
+ * @param {Day} endDate
42
+ * @return {String}
43
+ */
44
+ describeRange(startDate, endDate) {
45
+ return this._descriptionCalculator(startDate, endDate);
46
+ }
47
+
48
+ /**
49
+ * Returns the most recent ranges for the frame.
50
+ *
51
+ * @public
52
+ * @param {Number} periods
53
+ * @returns {Array.<PositionSummaryRange>}
54
+ */
55
+ getRecentRanges(periods) {
56
+ const startDate = this.getStartDate(periods);
57
+ const transaction = { date: startDate, snapshot: { open: Decimal.ONE } };
58
+
59
+ return this.getRanges([ transaction ]);
60
+ }
61
+
62
+ /**
63
+ * Returns the ranges for the set of {@link Transaction} objects.
64
+ *
65
+ * @public
66
+ * @param {Array.<Transaction>} transactions
67
+ * @returns {Array.<PositionSummaryRange>}
68
+ */
30
69
  getRanges(transactions) {
31
70
  assert.argumentIsArray(transactions, 'transactions');
32
71
 
33
72
  return this._rangeCalculator(getFilteredTransactions(transactions));
34
73
  }
35
74
 
75
+ /**
76
+ * Returns the start date for a frame, a given number of periods ago.
77
+ *
78
+ * @public
79
+ * @param {Number} periods
80
+ * @returns {Day}
81
+ */
36
82
  getStartDate(periods) {
37
83
  assert.argumentIsRequired(periods, 'periods', Number);
38
84
 
@@ -84,10 +130,19 @@ module.exports = (() => {
84
130
  }
85
131
  }
86
132
 
87
- const yearly = new PositionSummaryFrame('YEARLY', 'year', getYearlyRanges, getYearlyStartDate);
88
- const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', getQuarterlyRanges, getQuarterlyStartDate);
89
- const monthly = new PositionSummaryFrame('MONTH', 'month', getMonthlyRanges, getMonthlyStartDate);
90
- const ytd = new PositionSummaryFrame('YTD', 'year-to-date', getYearToDateRanges, getYearToDateStartDate);
133
+ const yearly = new PositionSummaryFrame('YEARLY', 'year', getYearlyRanges, getYearlyStartDate, getYearlyRangeDescription);
134
+ const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', getQuarterlyRanges, getQuarterlyStartDate, getQuarterlyRangeDescription);
135
+ const monthly = new PositionSummaryFrame('MONTH', 'month', getMonthlyRanges, getMonthlyStartDate, getMonthlyRangeDescription);
136
+ const ytd = new PositionSummaryFrame('YTD', 'year-to-date', getYearToDateRanges, getYearToDateStartDate, getYearToDateRangeDescription);
137
+
138
+ /**
139
+ * The start and and date for a {@link PositionSummaryFrame}
140
+ *
141
+ * @typedef PositionSummaryRange
142
+ * @type {Object}
143
+ * @property {Day} start
144
+ * @property {Day} end
145
+ */
91
146
 
92
147
  function getRange(start, end) {
93
148
  return {
@@ -171,6 +226,22 @@ module.exports = (() => {
171
226
  return null;
172
227
  }
173
228
 
229
+ function getYearlyRangeDescription(startDate, endDate) {
230
+ return endDate.year.toString();
231
+ }
232
+
233
+ function getQuarterlyRangeDescription(startDate, endDate) {
234
+ return '';
235
+ }
236
+
237
+ function getMonthlyRangeDescription(startDate, endDate) {
238
+ return '';
239
+ }
240
+
241
+ function getYearToDateRangeDescription(startDate, endDate) {
242
+ return '';
243
+ }
244
+
174
245
  function getFilteredTransactions(transactions) {
175
246
  return transactions.reduce((filtered, transaction) => {
176
247
  if (!transaction.snapshot.open.getIsZero() || transaction.type.closing) {
@@ -6,7 +6,7 @@ const array = require('@barchart/common-js/lang/array'),
6
6
  is = require('@barchart/common-js/lang/is'),
7
7
  Tree = require('@barchart/common-js/collections/Tree');
8
8
 
9
- const InstrumentType = require('./../data/InstrumentType');
9
+ const PositionSummaryFrame = require('./../data/PositionSummaryFrame');
10
10
 
11
11
  const PositionGroup = require('./PositionGroup'),
12
12
  PositionItem = require('./PositionItem');
@@ -18,10 +18,13 @@ module.exports = (() => {
18
18
  * @public
19
19
  */
20
20
  class PositionContainer {
21
- constructor(portfolios, positions, summaries, definitions, defaultCurrency) {
21
+ constructor(portfolios, positions, summaries, definitions, defaultCurrency, summaryFrameType) {
22
22
  this._definitions = definitions;
23
23
  this._defaultCurrency = defaultCurrency || Currency.CAD;
24
24
 
25
+ this._summaryFrame = summaryFrameType || PositionSummaryFrame.YEARLY;
26
+ this._summaryRanges = this._summaryFrame.getRecentRanges(1);
27
+
25
28
  this._portfolios = portfolios.reduce((map, portfolio) => {
26
29
  map[portfolio.portfolio] = portfolio;
27
30
 
@@ -29,13 +32,19 @@ module.exports = (() => {
29
32
  }, { });
30
33
 
31
34
  this._summaries = summaries.reduce((map, summary) => {
32
- const key = summary.position;
35
+ if (this._summaryFrame === summary.frame) {
36
+ const key = summary.position;
33
37
 
34
- if (!map.hasOwnProperty(key)) {
35
- map[key] = [ ];
36
- }
38
+ if (!map.hasOwnProperty(key)) {
39
+ map[key] = getSummaryArray(this._summaryRanges);
40
+ }
37
41
 
38
- map[key].push(summary);
42
+ const index = this._summaryRanges.findIndex(r => r.start.getIsEqual(summary.start.date) && r.end.getIsEqual(summary.end.date));
43
+
44
+ if (!(index < 0)) {
45
+ map[key][index] = summary;
46
+ }
47
+ }
39
48
 
40
49
  return map;
41
50
  }, { });
@@ -44,7 +53,7 @@ module.exports = (() => {
44
53
  const portfolio = this._portfolios[position.portfolio];
45
54
 
46
55
  if (position) {
47
- const summaries = this._summaries[position.position] || [ ];
56
+ const summaries = this._summaries[position.position] || getSummaryArray(this._summaryRanges);
48
57
 
49
58
  items.push(new PositionItem(portfolio, position, summaries));
50
59
  }
@@ -100,13 +109,13 @@ module.exports = (() => {
100
109
  const populatedGroups = Object.keys(populatedObjects).map(key => populatedObjects[key]).map((items) => {
101
110
  const first = items[0];
102
111
 
103
- return new PositionGroup(parent, items, currentDefinition.currencySelector(first), currentDefinition.descriptionSelector(first), currentDefinition.single && items.length === 1);
112
+ return new PositionGroup(this, parent, items, currentDefinition.currencySelector(first), currentDefinition.descriptionSelector(first), currentDefinition.single && items.length === 1);
104
113
  });
105
114
 
106
115
  const missingGroups = array.difference(currentDefinition.requiredGroups.map(group => group.description), populatedGroups.map(group => group.description));
107
116
 
108
117
  const empty = missingGroups.map((description) => {
109
- return new PositionGroup(parent, [ ], currentDefinition.requiredGroups.find(group => group.description === description).currency, description);
118
+ return new PositionGroup(this, parent, [ ], currentDefinition.requiredGroups.find(group => group.description === description).currency, description);
110
119
  });
111
120
 
112
121
  const compositeGroups = populatedGroups.concat(empty);
@@ -177,10 +186,20 @@ module.exports = (() => {
177
186
  }, [ ]);
178
187
  }
179
188
 
180
- setExchangeRage(symbol, price) {
189
+ setExchangeRate(symbol, price) {
181
190
 
182
191
  }
183
192
 
193
+ startTransaction(executor) {
194
+ assert.argumentIsRequired(executor, 'executor', Function);
195
+
196
+ this._tree.walk(group => group.setSuspended(true), false, false);
197
+
198
+ executor(this);
199
+
200
+ this._tree.walk(group => group.setSuspended(false), false, false);
201
+ }
202
+
184
203
  getGroup(keys) {
185
204
  const node = keys.reduce((tree, key) => {
186
205
  tree = tree.findChild(group => group.description === key);
@@ -206,5 +225,9 @@ module.exports = (() => {
206
225
  }
207
226
  }
208
227
 
228
+ function getSummaryArray(ranges) {
229
+ return ranges.map(range => null);
230
+ }
231
+
209
232
  return PositionContainer;
210
233
  })();
@@ -11,7 +11,8 @@ module.exports = (() => {
11
11
  * @public
12
12
  */
13
13
  class PositionGroup {
14
- constructor(parent, items, currency, description, single) {
14
+ constructor(container, parent, items, currency, description, single) {
15
+ this._container = container;
15
16
  this._parent = parent || null;
16
17
 
17
18
  this._items = items;
@@ -21,24 +22,37 @@ module.exports = (() => {
21
22
 
22
23
  this._single = is.boolean(single) && single;
23
24
 
25
+ this._excluded = false;
26
+ this._suspended = false;
27
+
24
28
  this._dataFormat = { };
25
29
  this._dataActual = { };
26
30
 
27
31
  this._dataFormat.description = this._description;
28
-
32
+
29
33
  this._dataActual.currentPrice = null;
30
34
  this._dataActual.previousPrice = null;
31
35
  this._dataActual.basis = null;
36
+ this._dataActual.realized = null;
37
+ this._dataActual.income = null;
32
38
  this._dataActual.market = null;
33
39
  this._dataActual.marketPercent = null;
34
40
  this._dataActual.unrealizedToday = null;
41
+ this._dataActual.total = null;
42
+ this._dataActual.summaryOneTotal = null;
43
+ this._dataActual.summaryTwoTotal = null;
35
44
 
36
45
  this._dataFormat.currentPrice = null;
37
46
  this._dataFormat.previousPrice = null;
38
47
  this._dataFormat.basis = null;
48
+ this._dataFormat.realized = null;
49
+ this._dataFormat.income = null;
39
50
  this._dataFormat.market = null;
40
51
  this._dataFormat.marketPercent = null;
41
52
  this._dataFormat.unrealizedToday = null;
53
+ this._dataFormat.total = null;
54
+ this._dataFormat.summaryOneTotal = null;
55
+ this._dataFormat.summaryTwoTotal = null;
42
56
 
43
57
  this._dataFormat.unrealizedTodayNegative = false;
44
58
 
@@ -52,12 +66,11 @@ module.exports = (() => {
52
66
  this._dataFormat.currentPrice = null;
53
67
  }
54
68
 
55
- calculatePriceData(this, sender);
69
+ calculatePriceData(this, sender, false);
56
70
  });
57
71
  });
58
72
 
59
- calculateStaticData(this);
60
- calculatePriceData(this);
73
+ this.refresh();
61
74
  }
62
75
 
63
76
  get items() {
@@ -80,6 +93,41 @@ module.exports = (() => {
80
93
  return this._single;
81
94
  }
82
95
 
96
+ get suspended() {
97
+ return this._suspended;
98
+ }
99
+
100
+ get excluded() {
101
+ return this._excluded;
102
+ }
103
+
104
+ setExcluded(value) {
105
+ assert.argumentIsRequired(value, 'value', Boolean);
106
+
107
+ if (this._excluded !== value) {
108
+ this._container.startTransaction(() => {
109
+ this._items.forEach((item) => {
110
+ item.setExcluded(value);
111
+ });
112
+ });
113
+ }
114
+ }
115
+
116
+ setSuspended(value) {
117
+ assert.argumentIsRequired(value, 'value', Boolean);
118
+
119
+ if (this._suspended !== value) {
120
+ if (this._suspended = value) {
121
+ this.refresh();
122
+ }
123
+ }
124
+ }
125
+
126
+ refresh() {
127
+ calculateStaticData(this);
128
+ calculatePriceData(this, null, true);
129
+ }
130
+
83
131
  toString() {
84
132
  return '[PositionGroup]';
85
133
  }
@@ -106,6 +154,10 @@ module.exports = (() => {
106
154
  }
107
155
 
108
156
  function calculateStaticData(group) {
157
+ if (group.suspended) {
158
+ return;
159
+ }
160
+
109
161
  const actual = group._dataActual;
110
162
  const format = group._dataFormat;
111
163
 
@@ -115,18 +167,38 @@ module.exports = (() => {
115
167
 
116
168
  let updates = items.reduce((updates, item) => {
117
169
  updates.basis = updates.basis.add(item.data.basis);
170
+ updates.realized = updates.realized.add(item.data.realized);
171
+ updates.income = updates.income.add(item.data.income);
172
+ updates.summaryOneTotal = updates.summaryOneTotal.add(item.data.summaryOneTotal);
173
+ updates.summaryTwoTotal = updates.summaryTwoTotal.add(item.data.summaryTwoTotal);
118
174
 
119
175
  return updates;
120
176
  }, {
121
- basis: Decimal.ZERO
177
+ basis: Decimal.ZERO,
178
+ realized: Decimal.ZERO,
179
+ income: Decimal.ZERO,
180
+ summaryOneTotal: Decimal.ZERO,
181
+ summaryTwoTotal: Decimal.ZERO
122
182
  });
123
183
 
124
184
  actual.basis = updates.basis;
125
-
126
- format.basis = formatCurrency(updates.basis, currency);
185
+ actual.realized = updates.realized;
186
+ actual.income = updates.income;
187
+ actual.summaryOneTotal = updates.summaryOneTotal;
188
+ actual.summaryTwoTotal = updates.summaryTwoTotal;
189
+
190
+ format.basis = formatCurrency(actual.basis, currency);
191
+ format.realized = formatCurrency(actual.basis, currency);
192
+ format.income = formatCurrency(actual.income, currency);
193
+ format.summaryOneTotal = formatCurrency(updates.summaryOneTotal, currency);
194
+ format.summaryTwoTotal = formatCurrency(updates.summaryTwoTotal, currency);
127
195
  }
128
196
 
129
- function calculatePriceData(group, item) {
197
+ function calculatePriceData(group, item, forceRefresh) {
198
+ if (group.suspended) {
199
+ return;
200
+ }
201
+
130
202
  const parent = group._parent;
131
203
 
132
204
  const actual = group._dataActual;
@@ -134,9 +206,11 @@ module.exports = (() => {
134
206
 
135
207
  const currency = group.currency;
136
208
 
209
+ const refresh = (is.boolean(forceRefresh) && forceRefresh) || (actual.market === null || actual.unrealizedToday === null || actual.total === null);
210
+
137
211
  let updates;
138
212
 
139
- if (actual.market === null || actual.unrealizedToday === null) {
213
+ if (refresh) {
140
214
  const items = group._items;
141
215
 
142
216
  updates = items.reduce((updates, item) => {
@@ -146,6 +220,7 @@ module.exports = (() => {
146
220
  return updates;
147
221
  }, {
148
222
  market: Decimal.ZERO,
223
+
149
224
  unrealizedToday: Decimal.ZERO
150
225
  });
151
226
  } else {
@@ -166,15 +241,17 @@ module.exports = (() => {
166
241
  } else {
167
242
  updates.marketPercent = null;
168
243
  }
169
-
244
+
170
245
  actual.market = updates.market;
171
246
  actual.marketPercent = updates.marketPercent;
172
247
  actual.unrealizedToday = updates.unrealizedToday;
248
+ actual.total = updates.unrealizedToday.add(actual.realized).add(actual.income);
173
249
 
174
- format.market = formatCurrency(updates.market, currency);
175
- format.marketPercent = formatPercent(updates.marketPercent, 2);
176
- format.unrealizedToday = formatCurrency(updates.unrealizedToday, currency);
250
+ format.market = formatCurrency(actual.market, currency);
251
+ format.marketPercent = formatPercent(actual.marketPercent, 2);
252
+ format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
177
253
  format.unrealizedTodayNegative = actual.unrealizedToday.getIsNegative();
254
+ format.total = formatCurrency(actual.total, currency);
178
255
  }
179
256
 
180
257
  return PositionGroup;
@@ -30,10 +30,16 @@ module.exports = (() => {
30
30
  this._data.unrealizedToday = null;
31
31
  this._data.unrealizedTodayChange = null;
32
32
 
33
+ this._data.realized = null;
34
+ this._data.income = null;
35
+
36
+ this._excluded = false;
37
+
33
38
  calculateStaticData(this);
34
39
  calculatePriceData(this, null);
35
40
 
36
41
  this._priceChangeEvent = new Event(this);
42
+ this._excludedChangeEvent = new Event(this);
37
43
  }
38
44
 
39
45
  get portfolio() {
@@ -52,7 +58,13 @@ module.exports = (() => {
52
58
  return this._data;
53
59
  }
54
60
 
61
+ get excluded() {
62
+ return this._excluded;
63
+ }
64
+
55
65
  setPrice(price) {
66
+ assert.argumentIsRequired(price, 'price', Number);
67
+
56
68
  if (this._data.price !== price) {
57
69
  calculatePriceData(this, this._data.currentPrice = price);
58
70
 
@@ -60,12 +72,22 @@ module.exports = (() => {
60
72
  }
61
73
  }
62
74
 
63
- registerPriceChangeHandler(handler) {
64
- assert.argumentIsRequired(handler, 'handler', Function);
75
+ setExcluded(value) {
76
+ assert.argumentIsRequired(value, 'value', Boolean);
65
77
 
78
+ if (this._excluded !== value) {
79
+ this._excludedChangeEvent.fire(this, this._excluded = value);
80
+ }
81
+ }
82
+
83
+ registerPriceChangeHandler(handler) {
66
84
  this._priceChangeEvent.register(handler);
67
85
  }
68
86
 
87
+ registerExcludedChangeHandler(handler) {
88
+ this._excludedChangeEvent.register(handler);
89
+ }
90
+
69
91
  toString() {
70
92
  return '[PositionItem]';
71
93
  }
@@ -74,6 +96,7 @@ module.exports = (() => {
74
96
  function calculateStaticData(item) {
75
97
  const position = item.position;
76
98
  const snapshot = item.position.snapshot;
99
+ const summaries = item.summaries;
77
100
 
78
101
  const data = item._data;
79
102
 
@@ -88,6 +111,26 @@ module.exports = (() => {
88
111
  }
89
112
 
90
113
  data.basis = basis;
114
+
115
+ data.realized = snapshot.gain;
116
+ data.income = snapshot.income;
117
+
118
+ const getSummaryTotal = (index) => {
119
+ let summaryTotal;
120
+
121
+ if (summaries.length > index && summaries[index] !== null) {
122
+ const period = summaries[index].period;
123
+
124
+ summaryTotal = period.realized.add(period.unrealized).add(period.income);
125
+ } else {
126
+ summaryTotal = Decimal.ZERO;
127
+ }
128
+
129
+ return summaryTotal;
130
+ };
131
+
132
+ data.summaryOneTotal = getSummaryTotal(0);
133
+ data.summaryTwoTotal = getSummaryTotal(1);
91
134
  }
92
135
 
93
136
  function calculatePriceData(item, price) {
@@ -155,6 +155,7 @@ module.exports = (() => {
155
155
  );
156
156
 
157
157
  const update = new PortfolioSchema(SchemaBuilder.withName('update')
158
+ .withField('portfolio', DataType.STRING)
158
159
  .withField('name', DataType.STRING)
159
160
  .withField('timezone', DataType.forEnum(Timezones, 'Timezone'), true)
160
161
  .withField('defaults.currency', DataType.forEnum(Currency, 'Currency'), true)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.0.68",
3
+ "version": "1.0.72",
4
4
  "description": "Common classes used by the Portfolio system",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",
@@ -93,6 +93,7 @@ module.exports = (() => {
93
93
  const array = require('@barchart/common-js/lang/array'),
94
94
  assert = require('@barchart/common-js/lang/assert'),
95
95
  Day = require('@barchart/common-js/lang/Day'),
96
+ Decimal = require('@barchart/common-js/lang/Decimal'),
96
97
  Enum = require('@barchart/common-js/lang/Enum'),
97
98
  is = require('@barchart/common-js/lang/is');
98
99
 
@@ -108,23 +109,68 @@ module.exports = (() => {
108
109
  * @param {String} description
109
110
  * @param {Function} rangeCalculator
110
111
  * @param {Function} startDateCalculator
112
+ * @param {Function} descriptionCalculator
111
113
  */
112
114
  class PositionSummaryFrame extends Enum {
113
- constructor(code, description, rangeCalculator, startDateCalculator) {
115
+ constructor(code, description, rangeCalculator, startDateCalculator, descriptionCalculator) {
114
116
  super(code, description);
115
117
 
116
118
  assert.argumentIsRequired(rangeCalculator, 'rangeCalculator', Function);
119
+ assert.argumentIsRequired(startDateCalculator, 'startDateCalculator', Function);
120
+ assert.argumentIsRequired(descriptionCalculator, 'descriptionCalculator', Function);
117
121
 
118
122
  this._rangeCalculator = rangeCalculator;
119
123
  this._startDateCalculator = startDateCalculator;
124
+ this._descriptionCalculator = descriptionCalculator;
125
+ }
126
+
127
+ /**
128
+ * Returns a human-readable description of the frame, given
129
+ * start and end dates.
130
+ *
131
+ * @public
132
+ * @param {Day} startDate
133
+ * @param {Day} endDate
134
+ * @return {String}
135
+ */
136
+ describeRange(startDate, endDate) {
137
+ return this._descriptionCalculator(startDate, endDate);
120
138
  }
121
139
 
140
+ /**
141
+ * Returns the most recent ranges for the frame.
142
+ *
143
+ * @public
144
+ * @param {Number} periods
145
+ * @returns {Array.<PositionSummaryRange>}
146
+ */
147
+ getRecentRanges(periods) {
148
+ const startDate = this.getStartDate(periods);
149
+ const transaction = { date: startDate, snapshot: { open: Decimal.ONE } };
150
+
151
+ return this.getRanges([ transaction ]);
152
+ }
153
+
154
+ /**
155
+ * Returns the ranges for the set of {@link Transaction} objects.
156
+ *
157
+ * @public
158
+ * @param {Array.<Transaction>} transactions
159
+ * @returns {Array.<PositionSummaryRange>}
160
+ */
122
161
  getRanges(transactions) {
123
162
  assert.argumentIsArray(transactions, 'transactions');
124
163
 
125
164
  return this._rangeCalculator(getFilteredTransactions(transactions));
126
165
  }
127
166
 
167
+ /**
168
+ * Returns the start date for a frame, a given number of periods ago.
169
+ *
170
+ * @public
171
+ * @param {Number} periods
172
+ * @returns {Day}
173
+ */
128
174
  getStartDate(periods) {
129
175
  assert.argumentIsRequired(periods, 'periods', Number);
130
176
 
@@ -176,10 +222,19 @@ module.exports = (() => {
176
222
  }
177
223
  }
178
224
 
179
- const yearly = new PositionSummaryFrame('YEARLY', 'year', getYearlyRanges, getYearlyStartDate);
180
- const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', getQuarterlyRanges, getQuarterlyStartDate);
181
- const monthly = new PositionSummaryFrame('MONTH', 'month', getMonthlyRanges, getMonthlyStartDate);
182
- const ytd = new PositionSummaryFrame('YTD', 'year-to-date', getYearToDateRanges, getYearToDateStartDate);
225
+ const yearly = new PositionSummaryFrame('YEARLY', 'year', getYearlyRanges, getYearlyStartDate, getYearlyRangeDescription);
226
+ const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', getQuarterlyRanges, getQuarterlyStartDate, getQuarterlyRangeDescription);
227
+ const monthly = new PositionSummaryFrame('MONTH', 'month', getMonthlyRanges, getMonthlyStartDate, getMonthlyRangeDescription);
228
+ const ytd = new PositionSummaryFrame('YTD', 'year-to-date', getYearToDateRanges, getYearToDateStartDate, getYearToDateRangeDescription);
229
+
230
+ /**
231
+ * The start and and date for a {@link PositionSummaryFrame}
232
+ *
233
+ * @typedef PositionSummaryRange
234
+ * @type {Object}
235
+ * @property {Day} start
236
+ * @property {Day} end
237
+ */
183
238
 
184
239
  function getRange(start, end) {
185
240
  return {
@@ -263,6 +318,22 @@ module.exports = (() => {
263
318
  return null;
264
319
  }
265
320
 
321
+ function getYearlyRangeDescription(startDate, endDate) {
322
+ return endDate.year.toString();
323
+ }
324
+
325
+ function getQuarterlyRangeDescription(startDate, endDate) {
326
+ return '';
327
+ }
328
+
329
+ function getMonthlyRangeDescription(startDate, endDate) {
330
+ return '';
331
+ }
332
+
333
+ function getYearToDateRangeDescription(startDate, endDate) {
334
+ return '';
335
+ }
336
+
266
337
  function getFilteredTransactions(transactions) {
267
338
  return transactions.reduce((filtered, transaction) => {
268
339
  if (!transaction.snapshot.open.getIsZero() || transaction.type.closing) {
@@ -276,7 +347,7 @@ module.exports = (() => {
276
347
  return PositionSummaryFrame;
277
348
  })();
278
349
 
279
- },{"@barchart/common-js/lang/Day":12,"@barchart/common-js/lang/Enum":15,"@barchart/common-js/lang/array":16,"@barchart/common-js/lang/assert":17,"@barchart/common-js/lang/is":19}],3:[function(require,module,exports){
350
+ },{"@barchart/common-js/lang/Day":12,"@barchart/common-js/lang/Decimal":13,"@barchart/common-js/lang/Enum":15,"@barchart/common-js/lang/array":16,"@barchart/common-js/lang/assert":17,"@barchart/common-js/lang/is":19}],3:[function(require,module,exports){
280
351
  const assert = require('@barchart/common-js/lang/assert'),
281
352
  Enum = require('@barchart/common-js/lang/Enum');
282
353
 
@@ -612,7 +683,7 @@ const array = require('@barchart/common-js/lang/array'),
612
683
  is = require('@barchart/common-js/lang/is'),
613
684
  Tree = require('@barchart/common-js/collections/Tree');
614
685
 
615
- const InstrumentType = require('./../data/InstrumentType');
686
+ const PositionSummaryFrame = require('./../data/PositionSummaryFrame');
616
687
 
617
688
  const PositionGroup = require('./PositionGroup'),
618
689
  PositionItem = require('./PositionItem');
@@ -624,10 +695,13 @@ module.exports = (() => {
624
695
  * @public
625
696
  */
626
697
  class PositionContainer {
627
- constructor(portfolios, positions, summaries, definitions, defaultCurrency) {
698
+ constructor(portfolios, positions, summaries, definitions, defaultCurrency, summaryFrameType) {
628
699
  this._definitions = definitions;
629
700
  this._defaultCurrency = defaultCurrency || Currency.CAD;
630
701
 
702
+ this._summaryFrame = summaryFrameType || PositionSummaryFrame.YEARLY;
703
+ this._summaryRanges = this._summaryFrame.getRecentRanges(1);
704
+
631
705
  this._portfolios = portfolios.reduce((map, portfolio) => {
632
706
  map[portfolio.portfolio] = portfolio;
633
707
 
@@ -635,13 +709,19 @@ module.exports = (() => {
635
709
  }, { });
636
710
 
637
711
  this._summaries = summaries.reduce((map, summary) => {
638
- const key = summary.position;
712
+ if (this._summaryFrame === summary.frame) {
713
+ const key = summary.position;
639
714
 
640
- if (!map.hasOwnProperty(key)) {
641
- map[key] = [ ];
642
- }
715
+ if (!map.hasOwnProperty(key)) {
716
+ map[key] = getSummaryArray(this._summaryRanges);
717
+ }
643
718
 
644
- map[key].push(summary);
719
+ const index = this._summaryRanges.findIndex(r => r.start.getIsEqual(summary.start.date) && r.end.getIsEqual(summary.end.date));
720
+
721
+ if (!(index < 0)) {
722
+ map[key][index] = summary;
723
+ }
724
+ }
645
725
 
646
726
  return map;
647
727
  }, { });
@@ -650,7 +730,7 @@ module.exports = (() => {
650
730
  const portfolio = this._portfolios[position.portfolio];
651
731
 
652
732
  if (position) {
653
- const summaries = this._summaries[position.position] || [ ];
733
+ const summaries = this._summaries[position.position] || getSummaryArray(this._summaryRanges);
654
734
 
655
735
  items.push(new PositionItem(portfolio, position, summaries));
656
736
  }
@@ -706,13 +786,13 @@ module.exports = (() => {
706
786
  const populatedGroups = Object.keys(populatedObjects).map(key => populatedObjects[key]).map((items) => {
707
787
  const first = items[0];
708
788
 
709
- return new PositionGroup(parent, items, currentDefinition.currencySelector(first), currentDefinition.descriptionSelector(first), currentDefinition.single && items.length === 1);
789
+ return new PositionGroup(this, parent, items, currentDefinition.currencySelector(first), currentDefinition.descriptionSelector(first), currentDefinition.single && items.length === 1);
710
790
  });
711
791
 
712
792
  const missingGroups = array.difference(currentDefinition.requiredGroups.map(group => group.description), populatedGroups.map(group => group.description));
713
793
 
714
794
  const empty = missingGroups.map((description) => {
715
- return new PositionGroup(parent, [ ], currentDefinition.requiredGroups.find(group => group.description === description).currency, description);
795
+ return new PositionGroup(this, parent, [ ], currentDefinition.requiredGroups.find(group => group.description === description).currency, description);
716
796
  });
717
797
 
718
798
  const compositeGroups = populatedGroups.concat(empty);
@@ -783,8 +863,18 @@ module.exports = (() => {
783
863
  }, [ ]);
784
864
  }
785
865
 
786
- setExchangeRage(symbol, price) {
866
+ setExchangeRate(symbol, price) {
867
+
868
+ }
869
+
870
+ startTransaction(executor) {
871
+ assert.argumentIsRequired(executor, 'executor', Function);
787
872
 
873
+ this._tree.walk(group => group.setSuspended(true), false, false);
874
+
875
+ executor(this);
876
+
877
+ this._tree.walk(group => group.setSuspended(false), false, false);
788
878
  }
789
879
 
790
880
  getGroup(keys) {
@@ -812,10 +902,14 @@ module.exports = (() => {
812
902
  }
813
903
  }
814
904
 
905
+ function getSummaryArray(ranges) {
906
+ return ranges.map(range => null);
907
+ }
908
+
815
909
  return PositionContainer;
816
910
  })();
817
911
 
818
- },{"./../data/InstrumentType":1,"./PositionGroup":5,"./PositionItem":7,"@barchart/common-js/collections/Tree":8,"@barchart/common-js/collections/sorting/ComparatorBuilder":9,"@barchart/common-js/collections/sorting/comparators":10,"@barchart/common-js/lang/Currency":11,"@barchart/common-js/lang/array":16,"@barchart/common-js/lang/assert":17,"@barchart/common-js/lang/is":19}],5:[function(require,module,exports){
912
+ },{"./../data/PositionSummaryFrame":2,"./PositionGroup":5,"./PositionItem":7,"@barchart/common-js/collections/Tree":8,"@barchart/common-js/collections/sorting/ComparatorBuilder":9,"@barchart/common-js/collections/sorting/comparators":10,"@barchart/common-js/lang/Currency":11,"@barchart/common-js/lang/array":16,"@barchart/common-js/lang/assert":17,"@barchart/common-js/lang/is":19}],5:[function(require,module,exports){
819
913
  const assert = require('@barchart/common-js/lang/assert'),
820
914
  Currency = require('@barchart/common-js/lang/Currency'),
821
915
  Decimal = require('@barchart/common-js/lang/Decimal'),
@@ -829,7 +923,8 @@ module.exports = (() => {
829
923
  * @public
830
924
  */
831
925
  class PositionGroup {
832
- constructor(parent, items, currency, description, single) {
926
+ constructor(container, parent, items, currency, description, single) {
927
+ this._container = container;
833
928
  this._parent = parent || null;
834
929
 
835
930
  this._items = items;
@@ -839,24 +934,37 @@ module.exports = (() => {
839
934
 
840
935
  this._single = is.boolean(single) && single;
841
936
 
937
+ this._excluded = false;
938
+ this._suspended = false;
939
+
842
940
  this._dataFormat = { };
843
941
  this._dataActual = { };
844
942
 
845
943
  this._dataFormat.description = this._description;
846
-
944
+
847
945
  this._dataActual.currentPrice = null;
848
946
  this._dataActual.previousPrice = null;
849
947
  this._dataActual.basis = null;
948
+ this._dataActual.realized = null;
949
+ this._dataActual.income = null;
850
950
  this._dataActual.market = null;
851
951
  this._dataActual.marketPercent = null;
852
952
  this._dataActual.unrealizedToday = null;
953
+ this._dataActual.total = null;
954
+ this._dataActual.summaryOneTotal = null;
955
+ this._dataActual.summaryTwoTotal = null;
853
956
 
854
957
  this._dataFormat.currentPrice = null;
855
958
  this._dataFormat.previousPrice = null;
856
959
  this._dataFormat.basis = null;
960
+ this._dataFormat.realized = null;
961
+ this._dataFormat.income = null;
857
962
  this._dataFormat.market = null;
858
963
  this._dataFormat.marketPercent = null;
859
964
  this._dataFormat.unrealizedToday = null;
965
+ this._dataFormat.total = null;
966
+ this._dataFormat.summaryOneTotal = null;
967
+ this._dataFormat.summaryTwoTotal = null;
860
968
 
861
969
  this._dataFormat.unrealizedTodayNegative = false;
862
970
 
@@ -870,12 +978,11 @@ module.exports = (() => {
870
978
  this._dataFormat.currentPrice = null;
871
979
  }
872
980
 
873
- calculatePriceData(this, sender);
981
+ calculatePriceData(this, sender, false);
874
982
  });
875
983
  });
876
984
 
877
- calculateStaticData(this);
878
- calculatePriceData(this);
985
+ this.refresh();
879
986
  }
880
987
 
881
988
  get items() {
@@ -898,6 +1005,41 @@ module.exports = (() => {
898
1005
  return this._single;
899
1006
  }
900
1007
 
1008
+ get suspended() {
1009
+ return this._suspended;
1010
+ }
1011
+
1012
+ get excluded() {
1013
+ return this._excluded;
1014
+ }
1015
+
1016
+ setExcluded(value) {
1017
+ assert.argumentIsRequired(value, 'value', Boolean);
1018
+
1019
+ if (this._excluded !== value) {
1020
+ this._container.startTransaction(() => {
1021
+ this._items.forEach((item) => {
1022
+ item.setExcluded(value);
1023
+ });
1024
+ });
1025
+ }
1026
+ }
1027
+
1028
+ setSuspended(value) {
1029
+ assert.argumentIsRequired(value, 'value', Boolean);
1030
+
1031
+ if (this._suspended !== value) {
1032
+ if (this._suspended = value) {
1033
+ this.refresh();
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ refresh() {
1039
+ calculateStaticData(this);
1040
+ calculatePriceData(this, null, true);
1041
+ }
1042
+
901
1043
  toString() {
902
1044
  return '[PositionGroup]';
903
1045
  }
@@ -924,6 +1066,10 @@ module.exports = (() => {
924
1066
  }
925
1067
 
926
1068
  function calculateStaticData(group) {
1069
+ if (group.suspended) {
1070
+ return;
1071
+ }
1072
+
927
1073
  const actual = group._dataActual;
928
1074
  const format = group._dataFormat;
929
1075
 
@@ -933,18 +1079,38 @@ module.exports = (() => {
933
1079
 
934
1080
  let updates = items.reduce((updates, item) => {
935
1081
  updates.basis = updates.basis.add(item.data.basis);
1082
+ updates.realized = updates.realized.add(item.data.realized);
1083
+ updates.income = updates.income.add(item.data.income);
1084
+ updates.summaryOneTotal = updates.summaryOneTotal.add(item.data.summaryOneTotal);
1085
+ updates.summaryTwoTotal = updates.summaryTwoTotal.add(item.data.summaryTwoTotal);
936
1086
 
937
1087
  return updates;
938
1088
  }, {
939
- basis: Decimal.ZERO
1089
+ basis: Decimal.ZERO,
1090
+ realized: Decimal.ZERO,
1091
+ income: Decimal.ZERO,
1092
+ summaryOneTotal: Decimal.ZERO,
1093
+ summaryTwoTotal: Decimal.ZERO
940
1094
  });
941
1095
 
942
1096
  actual.basis = updates.basis;
943
-
944
- format.basis = formatCurrency(updates.basis, currency);
1097
+ actual.realized = updates.realized;
1098
+ actual.income = updates.income;
1099
+ actual.summaryOneTotal = updates.summaryOneTotal;
1100
+ actual.summaryTwoTotal = updates.summaryTwoTotal;
1101
+
1102
+ format.basis = formatCurrency(actual.basis, currency);
1103
+ format.realized = formatCurrency(actual.basis, currency);
1104
+ format.income = formatCurrency(actual.income, currency);
1105
+ format.summaryOneTotal = formatCurrency(updates.summaryOneTotal, currency);
1106
+ format.summaryTwoTotal = formatCurrency(updates.summaryTwoTotal, currency);
945
1107
  }
946
1108
 
947
- function calculatePriceData(group, item) {
1109
+ function calculatePriceData(group, item, forceRefresh) {
1110
+ if (group.suspended) {
1111
+ return;
1112
+ }
1113
+
948
1114
  const parent = group._parent;
949
1115
 
950
1116
  const actual = group._dataActual;
@@ -952,9 +1118,11 @@ module.exports = (() => {
952
1118
 
953
1119
  const currency = group.currency;
954
1120
 
1121
+ const refresh = (is.boolean(forceRefresh) && forceRefresh) || (actual.market === null || actual.unrealizedToday === null || actual.total === null);
1122
+
955
1123
  let updates;
956
1124
 
957
- if (actual.market === null || actual.unrealizedToday === null) {
1125
+ if (refresh) {
958
1126
  const items = group._items;
959
1127
 
960
1128
  updates = items.reduce((updates, item) => {
@@ -964,6 +1132,7 @@ module.exports = (() => {
964
1132
  return updates;
965
1133
  }, {
966
1134
  market: Decimal.ZERO,
1135
+
967
1136
  unrealizedToday: Decimal.ZERO
968
1137
  });
969
1138
  } else {
@@ -984,15 +1153,17 @@ module.exports = (() => {
984
1153
  } else {
985
1154
  updates.marketPercent = null;
986
1155
  }
987
-
1156
+
988
1157
  actual.market = updates.market;
989
1158
  actual.marketPercent = updates.marketPercent;
990
1159
  actual.unrealizedToday = updates.unrealizedToday;
1160
+ actual.total = updates.unrealizedToday.add(actual.realized).add(actual.income);
991
1161
 
992
- format.market = formatCurrency(updates.market, currency);
993
- format.marketPercent = formatPercent(updates.marketPercent, 2);
994
- format.unrealizedToday = formatCurrency(updates.unrealizedToday, currency);
1162
+ format.market = formatCurrency(actual.market, currency);
1163
+ format.marketPercent = formatPercent(actual.marketPercent, 2);
1164
+ format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
995
1165
  format.unrealizedTodayNegative = actual.unrealizedToday.getIsNegative();
1166
+ format.total = formatCurrency(actual.total, currency);
996
1167
  }
997
1168
 
998
1169
  return PositionGroup;
@@ -1081,10 +1252,16 @@ module.exports = (() => {
1081
1252
  this._data.unrealizedToday = null;
1082
1253
  this._data.unrealizedTodayChange = null;
1083
1254
 
1255
+ this._data.realized = null;
1256
+ this._data.income = null;
1257
+
1258
+ this._excluded = false;
1259
+
1084
1260
  calculateStaticData(this);
1085
1261
  calculatePriceData(this, null);
1086
1262
 
1087
1263
  this._priceChangeEvent = new Event(this);
1264
+ this._excludedChangeEvent = new Event(this);
1088
1265
  }
1089
1266
 
1090
1267
  get portfolio() {
@@ -1103,7 +1280,13 @@ module.exports = (() => {
1103
1280
  return this._data;
1104
1281
  }
1105
1282
 
1283
+ get excluded() {
1284
+ return this._excluded;
1285
+ }
1286
+
1106
1287
  setPrice(price) {
1288
+ assert.argumentIsRequired(price, 'price', Number);
1289
+
1107
1290
  if (this._data.price !== price) {
1108
1291
  calculatePriceData(this, this._data.currentPrice = price);
1109
1292
 
@@ -1111,12 +1294,22 @@ module.exports = (() => {
1111
1294
  }
1112
1295
  }
1113
1296
 
1114
- registerPriceChangeHandler(handler) {
1115
- assert.argumentIsRequired(handler, 'handler', Function);
1297
+ setExcluded(value) {
1298
+ assert.argumentIsRequired(value, 'value', Boolean);
1299
+
1300
+ if (this._excluded !== value) {
1301
+ this._excludedChangeEvent.fire(this, this._excluded = value);
1302
+ }
1303
+ }
1116
1304
 
1305
+ registerPriceChangeHandler(handler) {
1117
1306
  this._priceChangeEvent.register(handler);
1118
1307
  }
1119
1308
 
1309
+ registerExcludedChangeHandler(handler) {
1310
+ this._excludedChangeEvent.register(handler);
1311
+ }
1312
+
1120
1313
  toString() {
1121
1314
  return '[PositionItem]';
1122
1315
  }
@@ -1125,6 +1318,7 @@ module.exports = (() => {
1125
1318
  function calculateStaticData(item) {
1126
1319
  const position = item.position;
1127
1320
  const snapshot = item.position.snapshot;
1321
+ const summaries = item.summaries;
1128
1322
 
1129
1323
  const data = item._data;
1130
1324
 
@@ -1139,6 +1333,26 @@ module.exports = (() => {
1139
1333
  }
1140
1334
 
1141
1335
  data.basis = basis;
1336
+
1337
+ data.realized = snapshot.gain;
1338
+ data.income = snapshot.income;
1339
+
1340
+ const getSummaryTotal = (index) => {
1341
+ let summaryTotal;
1342
+
1343
+ if (summaries.length > index && summaries[index] !== null) {
1344
+ const period = summaries[index].period;
1345
+
1346
+ summaryTotal = period.realized.add(period.unrealized).add(period.income);
1347
+ } else {
1348
+ summaryTotal = Decimal.ZERO;
1349
+ }
1350
+
1351
+ return summaryTotal;
1352
+ };
1353
+
1354
+ data.summaryOneTotal = getSummaryTotal(0);
1355
+ data.summaryTwoTotal = getSummaryTotal(1);
1142
1356
  }
1143
1357
 
1144
1358
  function calculatePriceData(item, price) {
@@ -5605,7 +5819,9 @@ describe('When a position container data is gathered', () => {
5605
5819
  snapshot: {
5606
5820
  basis: new Decimal(123),
5607
5821
  value: new Decimal(456),
5608
- open: new Decimal(1)
5822
+ open: new Decimal(1),
5823
+ income: new Decimal(0),
5824
+ gain: new Decimal(0)
5609
5825
  }
5610
5826
  }
5611
5827
  }
@@ -25,7 +25,9 @@ describe('When a position container data is gathered', () => {
25
25
  snapshot: {
26
26
  basis: new Decimal(123),
27
27
  value: new Decimal(456),
28
- open: new Decimal(1)
28
+ open: new Decimal(1),
29
+ income: new Decimal(0),
30
+ gain: new Decimal(0)
29
31
  }
30
32
  }
31
33
  }