@barchart/portfolio-api-common 1.0.66 → 1.0.70
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.
- package/lib/data/PositionSummaryFrame.js +76 -5
- package/lib/processing/PositionContainer.js +21 -8
- package/lib/processing/PositionGroup.js +49 -17
- package/lib/processing/PositionItem.js +26 -2
- package/package.json +1 -1
- package/test/SpecRunner.js +177 -35
- package/test/specs/processing/PositionContainerSpec.js +3 -1
|
@@ -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
|
|
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
|
-
|
|
35
|
+
if (this._summaryFrame === summary.frame) {
|
|
36
|
+
const key = summary.position;
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
if (!map.hasOwnProperty(key)) {
|
|
39
|
+
map[key] = getSummaryArray(this._summaryRanges);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const index = this._summaryRanges.findIndex(r => r.start.getIsEqual(summary.start.date) && r.end.getIsEqual(summary.end.date));
|
|
37
43
|
|
|
38
|
-
|
|
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
|
}
|
|
@@ -206,5 +215,9 @@ module.exports = (() => {
|
|
|
206
215
|
}
|
|
207
216
|
}
|
|
208
217
|
|
|
218
|
+
function getSummaryArray(ranges) {
|
|
219
|
+
return ranges.map(range => null);
|
|
220
|
+
}
|
|
221
|
+
|
|
209
222
|
return PositionContainer;
|
|
210
223
|
})();
|
|
@@ -25,20 +25,30 @@ module.exports = (() => {
|
|
|
25
25
|
this._dataActual = { };
|
|
26
26
|
|
|
27
27
|
this._dataFormat.description = this._description;
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
this._dataActual.currentPrice = null;
|
|
30
30
|
this._dataActual.previousPrice = null;
|
|
31
31
|
this._dataActual.basis = null;
|
|
32
|
+
this._dataActual.realized = null;
|
|
33
|
+
this._dataActual.income = null;
|
|
32
34
|
this._dataActual.market = null;
|
|
33
35
|
this._dataActual.marketPercent = null;
|
|
34
36
|
this._dataActual.unrealizedToday = null;
|
|
37
|
+
this._dataActual.total = null;
|
|
38
|
+
this._dataActual.summaryOneTotal = null;
|
|
39
|
+
this._dataActual.summaryTwoTotal = null;
|
|
35
40
|
|
|
36
41
|
this._dataFormat.currentPrice = null;
|
|
37
42
|
this._dataFormat.previousPrice = null;
|
|
38
43
|
this._dataFormat.basis = null;
|
|
44
|
+
this._dataFormat.realized = null;
|
|
45
|
+
this._dataFormat.income = null;
|
|
39
46
|
this._dataFormat.market = null;
|
|
40
47
|
this._dataFormat.marketPercent = null;
|
|
41
48
|
this._dataFormat.unrealizedToday = null;
|
|
49
|
+
this._dataFormat.total = null;
|
|
50
|
+
this._dataFormat.summaryOneTotal = null;
|
|
51
|
+
this._dataFormat.summaryTwoTotal = null;
|
|
42
52
|
|
|
43
53
|
this._dataFormat.unrealizedTodayNegative = false;
|
|
44
54
|
|
|
@@ -89,15 +99,15 @@ module.exports = (() => {
|
|
|
89
99
|
if (decimal !== null) {
|
|
90
100
|
return formatter.numberToString(decimal.toFloat(), precision, ',', false);
|
|
91
101
|
} else {
|
|
92
|
-
return '
|
|
102
|
+
return '—';
|
|
93
103
|
}
|
|
94
104
|
}
|
|
95
105
|
|
|
96
106
|
function formatPercent(decimal, precision) {
|
|
97
107
|
if (decimal !== null) {
|
|
98
|
-
return formatNumber(decimal.multiply(100));
|
|
108
|
+
return formatNumber(decimal.multiply(100), precision);
|
|
99
109
|
} else {
|
|
100
|
-
return '
|
|
110
|
+
return '—';
|
|
101
111
|
}
|
|
102
112
|
}
|
|
103
113
|
|
|
@@ -115,15 +125,31 @@ module.exports = (() => {
|
|
|
115
125
|
|
|
116
126
|
let updates = items.reduce((updates, item) => {
|
|
117
127
|
updates.basis = updates.basis.add(item.data.basis);
|
|
128
|
+
updates.realized = updates.realized.add(item.data.realized);
|
|
129
|
+
updates.income = updates.income.add(item.data.income);
|
|
130
|
+
updates.summaryOneTotal = updates.summaryOneTotal.add(item.data.summaryOneTotal);
|
|
131
|
+
updates.summaryTwoTotal = updates.summaryTwoTotal.add(item.data.summaryTwoTotal);
|
|
118
132
|
|
|
119
133
|
return updates;
|
|
120
134
|
}, {
|
|
121
|
-
basis: Decimal.ZERO
|
|
135
|
+
basis: Decimal.ZERO,
|
|
136
|
+
realized: Decimal.ZERO,
|
|
137
|
+
income: Decimal.ZERO,
|
|
138
|
+
summaryOneTotal: Decimal.ZERO,
|
|
139
|
+
summaryTwoTotal: Decimal.ZERO
|
|
122
140
|
});
|
|
123
141
|
|
|
124
142
|
actual.basis = updates.basis;
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
actual.realized = updates.realized;
|
|
144
|
+
actual.income = updates.income;
|
|
145
|
+
actual.summaryOneTotal = updates.summaryOneTotal;
|
|
146
|
+
actual.summaryTwoTotal = updates.summaryTwoTotal;
|
|
147
|
+
|
|
148
|
+
format.basis = formatCurrency(actual.basis, currency);
|
|
149
|
+
format.realized = formatCurrency(actual.basis, currency);
|
|
150
|
+
format.income = formatCurrency(actual.income, currency);
|
|
151
|
+
format.summaryOneTotal = formatCurrency(updates.summaryOneTotal, currency);
|
|
152
|
+
format.summaryTwoTotal = formatCurrency(updates.summaryTwoTotal, currency);
|
|
127
153
|
}
|
|
128
154
|
|
|
129
155
|
function calculatePriceData(group, item) {
|
|
@@ -136,7 +162,12 @@ module.exports = (() => {
|
|
|
136
162
|
|
|
137
163
|
let updates;
|
|
138
164
|
|
|
139
|
-
if (actual.market
|
|
165
|
+
if (actual.market !== null && actual.unrealizedToday !== null && actual.total !== null) {
|
|
166
|
+
updates = {
|
|
167
|
+
market: actual.market.add(item.data.marketChange),
|
|
168
|
+
unrealizedToday: actual.unrealizedToday.add(item.data.unrealizedTodayChange)
|
|
169
|
+
};
|
|
170
|
+
} else {
|
|
140
171
|
const items = group._items;
|
|
141
172
|
|
|
142
173
|
updates = items.reduce((updates, item) => {
|
|
@@ -148,11 +179,6 @@ module.exports = (() => {
|
|
|
148
179
|
market: Decimal.ZERO,
|
|
149
180
|
unrealizedToday: Decimal.ZERO
|
|
150
181
|
});
|
|
151
|
-
} else {
|
|
152
|
-
updates = {
|
|
153
|
-
market: actual.market.add(item.data.marketChange),
|
|
154
|
-
unrealizedToday: actual.unrealizedToday.add(item.data.unrealizedTodayChange)
|
|
155
|
-
};
|
|
156
182
|
}
|
|
157
183
|
|
|
158
184
|
if (parent !== null) {
|
|
@@ -160,17 +186,23 @@ module.exports = (() => {
|
|
|
160
186
|
|
|
161
187
|
if (parentData.market !== null && !parentData.market.getIsZero()) {
|
|
162
188
|
updates.marketPercent = updates.market.divide(parentData.market);
|
|
189
|
+
} else {
|
|
190
|
+
updates.marketPercent = null;
|
|
163
191
|
}
|
|
192
|
+
} else {
|
|
193
|
+
updates.marketPercent = null;
|
|
164
194
|
}
|
|
165
|
-
|
|
195
|
+
|
|
166
196
|
actual.market = updates.market;
|
|
167
197
|
actual.marketPercent = updates.marketPercent;
|
|
168
198
|
actual.unrealizedToday = updates.unrealizedToday;
|
|
199
|
+
actual.total = updates.unrealizedToday.add(actual.realized).add(actual.income);
|
|
169
200
|
|
|
170
|
-
format.market = formatCurrency(
|
|
171
|
-
format.marketPercent = formatPercent(
|
|
172
|
-
format.unrealizedToday = formatCurrency(
|
|
201
|
+
format.market = formatCurrency(actual.market, currency);
|
|
202
|
+
format.marketPercent = formatPercent(actual.marketPercent, 2);
|
|
203
|
+
format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
|
|
173
204
|
format.unrealizedTodayNegative = actual.unrealizedToday.getIsNegative();
|
|
205
|
+
format.total = formatCurrency(actual.total, currency);
|
|
174
206
|
}
|
|
175
207
|
|
|
176
208
|
return PositionGroup;
|
|
@@ -26,10 +26,13 @@ module.exports = (() => {
|
|
|
26
26
|
|
|
27
27
|
this._data.market = null;
|
|
28
28
|
this._data.marketChange = null;
|
|
29
|
-
|
|
29
|
+
|
|
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
|
+
|
|
33
36
|
calculateStaticData(this);
|
|
34
37
|
calculatePriceData(this, null);
|
|
35
38
|
|
|
@@ -74,10 +77,11 @@ module.exports = (() => {
|
|
|
74
77
|
function calculateStaticData(item) {
|
|
75
78
|
const position = item.position;
|
|
76
79
|
const snapshot = item.position.snapshot;
|
|
80
|
+
const summaries = item.summaries;
|
|
77
81
|
|
|
78
82
|
const data = item._data;
|
|
79
83
|
|
|
80
|
-
data.previousPrice = position.
|
|
84
|
+
data.previousPrice = position.previous || null;
|
|
81
85
|
|
|
82
86
|
let basis;
|
|
83
87
|
|
|
@@ -88,6 +92,26 @@ module.exports = (() => {
|
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
data.basis = basis;
|
|
95
|
+
|
|
96
|
+
data.realized = snapshot.gain;
|
|
97
|
+
data.income = snapshot.income;
|
|
98
|
+
|
|
99
|
+
const getSummaryTotal = (index) => {
|
|
100
|
+
let summaryTotal;
|
|
101
|
+
|
|
102
|
+
if (summaries.length > (index + 1) && summaries[index] !== null) {
|
|
103
|
+
const period = summaries[index].period;
|
|
104
|
+
|
|
105
|
+
summaryTotal = period.realized.add(period.unrealized).add(period.income);
|
|
106
|
+
} else {
|
|
107
|
+
summaryTotal = Decimal.ZERO;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return summaryTotal;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
data.summaryOneTotal = getSummaryTotal(0);
|
|
114
|
+
data.summaryTwoTotal = getSummaryTotal(1);
|
|
91
115
|
}
|
|
92
116
|
|
|
93
117
|
function calculatePriceData(item, price) {
|
package/package.json
CHANGED
package/test/SpecRunner.js
CHANGED
|
@@ -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);
|
|
138
|
+
}
|
|
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 ]);
|
|
120
152
|
}
|
|
121
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
|
|
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
|
-
|
|
712
|
+
if (this._summaryFrame === summary.frame) {
|
|
713
|
+
const key = summary.position;
|
|
639
714
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
715
|
+
if (!map.hasOwnProperty(key)) {
|
|
716
|
+
map[key] = getSummaryArray(this._summaryRanges);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const index = this._summaryRanges.findIndex(r => r.start.getIsEqual(summary.start.date) && r.end.getIsEqual(summary.end.date));
|
|
643
720
|
|
|
644
|
-
|
|
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
|
}
|
|
@@ -812,10 +892,14 @@ module.exports = (() => {
|
|
|
812
892
|
}
|
|
813
893
|
}
|
|
814
894
|
|
|
895
|
+
function getSummaryArray(ranges) {
|
|
896
|
+
return ranges.map(range => null);
|
|
897
|
+
}
|
|
898
|
+
|
|
815
899
|
return PositionContainer;
|
|
816
900
|
})();
|
|
817
901
|
|
|
818
|
-
},{"./../data/
|
|
902
|
+
},{"./../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
903
|
const assert = require('@barchart/common-js/lang/assert'),
|
|
820
904
|
Currency = require('@barchart/common-js/lang/Currency'),
|
|
821
905
|
Decimal = require('@barchart/common-js/lang/Decimal'),
|
|
@@ -843,20 +927,30 @@ module.exports = (() => {
|
|
|
843
927
|
this._dataActual = { };
|
|
844
928
|
|
|
845
929
|
this._dataFormat.description = this._description;
|
|
846
|
-
|
|
930
|
+
|
|
847
931
|
this._dataActual.currentPrice = null;
|
|
848
932
|
this._dataActual.previousPrice = null;
|
|
849
933
|
this._dataActual.basis = null;
|
|
934
|
+
this._dataActual.realized = null;
|
|
935
|
+
this._dataActual.income = null;
|
|
850
936
|
this._dataActual.market = null;
|
|
851
937
|
this._dataActual.marketPercent = null;
|
|
852
938
|
this._dataActual.unrealizedToday = null;
|
|
939
|
+
this._dataActual.total = null;
|
|
940
|
+
this._dataActual.summaryOneTotal = null;
|
|
941
|
+
this._dataActual.summaryTwoTotal = null;
|
|
853
942
|
|
|
854
943
|
this._dataFormat.currentPrice = null;
|
|
855
944
|
this._dataFormat.previousPrice = null;
|
|
856
945
|
this._dataFormat.basis = null;
|
|
946
|
+
this._dataFormat.realized = null;
|
|
947
|
+
this._dataFormat.income = null;
|
|
857
948
|
this._dataFormat.market = null;
|
|
858
949
|
this._dataFormat.marketPercent = null;
|
|
859
950
|
this._dataFormat.unrealizedToday = null;
|
|
951
|
+
this._dataFormat.total = null;
|
|
952
|
+
this._dataFormat.summaryOneTotal = null;
|
|
953
|
+
this._dataFormat.summaryTwoTotal = null;
|
|
860
954
|
|
|
861
955
|
this._dataFormat.unrealizedTodayNegative = false;
|
|
862
956
|
|
|
@@ -907,15 +1001,15 @@ module.exports = (() => {
|
|
|
907
1001
|
if (decimal !== null) {
|
|
908
1002
|
return formatter.numberToString(decimal.toFloat(), precision, ',', false);
|
|
909
1003
|
} else {
|
|
910
|
-
return '
|
|
1004
|
+
return '—';
|
|
911
1005
|
}
|
|
912
1006
|
}
|
|
913
1007
|
|
|
914
1008
|
function formatPercent(decimal, precision) {
|
|
915
1009
|
if (decimal !== null) {
|
|
916
|
-
return formatNumber(decimal.multiply(100));
|
|
1010
|
+
return formatNumber(decimal.multiply(100), precision);
|
|
917
1011
|
} else {
|
|
918
|
-
return '
|
|
1012
|
+
return '—';
|
|
919
1013
|
}
|
|
920
1014
|
}
|
|
921
1015
|
|
|
@@ -933,15 +1027,31 @@ module.exports = (() => {
|
|
|
933
1027
|
|
|
934
1028
|
let updates = items.reduce((updates, item) => {
|
|
935
1029
|
updates.basis = updates.basis.add(item.data.basis);
|
|
1030
|
+
updates.realized = updates.realized.add(item.data.realized);
|
|
1031
|
+
updates.income = updates.income.add(item.data.income);
|
|
1032
|
+
updates.summaryOneTotal = updates.summaryOneTotal.add(item.data.summaryOneTotal);
|
|
1033
|
+
updates.summaryTwoTotal = updates.summaryTwoTotal.add(item.data.summaryTwoTotal);
|
|
936
1034
|
|
|
937
1035
|
return updates;
|
|
938
1036
|
}, {
|
|
939
|
-
basis: Decimal.ZERO
|
|
1037
|
+
basis: Decimal.ZERO,
|
|
1038
|
+
realized: Decimal.ZERO,
|
|
1039
|
+
income: Decimal.ZERO,
|
|
1040
|
+
summaryOneTotal: Decimal.ZERO,
|
|
1041
|
+
summaryTwoTotal: Decimal.ZERO
|
|
940
1042
|
});
|
|
941
1043
|
|
|
942
1044
|
actual.basis = updates.basis;
|
|
943
|
-
|
|
944
|
-
|
|
1045
|
+
actual.realized = updates.realized;
|
|
1046
|
+
actual.income = updates.income;
|
|
1047
|
+
actual.summaryOneTotal = updates.summaryOneTotal;
|
|
1048
|
+
actual.summaryTwoTotal = updates.summaryTwoTotal;
|
|
1049
|
+
|
|
1050
|
+
format.basis = formatCurrency(actual.basis, currency);
|
|
1051
|
+
format.realized = formatCurrency(actual.basis, currency);
|
|
1052
|
+
format.income = formatCurrency(actual.income, currency);
|
|
1053
|
+
format.summaryOneTotal = formatCurrency(updates.summaryOneTotal, currency);
|
|
1054
|
+
format.summaryTwoTotal = formatCurrency(updates.summaryTwoTotal, currency);
|
|
945
1055
|
}
|
|
946
1056
|
|
|
947
1057
|
function calculatePriceData(group, item) {
|
|
@@ -954,7 +1064,12 @@ module.exports = (() => {
|
|
|
954
1064
|
|
|
955
1065
|
let updates;
|
|
956
1066
|
|
|
957
|
-
if (actual.market
|
|
1067
|
+
if (actual.market !== null && actual.unrealizedToday !== null && actual.total !== null) {
|
|
1068
|
+
updates = {
|
|
1069
|
+
market: actual.market.add(item.data.marketChange),
|
|
1070
|
+
unrealizedToday: actual.unrealizedToday.add(item.data.unrealizedTodayChange)
|
|
1071
|
+
};
|
|
1072
|
+
} else {
|
|
958
1073
|
const items = group._items;
|
|
959
1074
|
|
|
960
1075
|
updates = items.reduce((updates, item) => {
|
|
@@ -966,11 +1081,6 @@ module.exports = (() => {
|
|
|
966
1081
|
market: Decimal.ZERO,
|
|
967
1082
|
unrealizedToday: Decimal.ZERO
|
|
968
1083
|
});
|
|
969
|
-
} else {
|
|
970
|
-
updates = {
|
|
971
|
-
market: actual.market.add(item.data.marketChange),
|
|
972
|
-
unrealizedToday: actual.unrealizedToday.add(item.data.unrealizedTodayChange)
|
|
973
|
-
};
|
|
974
1084
|
}
|
|
975
1085
|
|
|
976
1086
|
if (parent !== null) {
|
|
@@ -978,17 +1088,23 @@ module.exports = (() => {
|
|
|
978
1088
|
|
|
979
1089
|
if (parentData.market !== null && !parentData.market.getIsZero()) {
|
|
980
1090
|
updates.marketPercent = updates.market.divide(parentData.market);
|
|
1091
|
+
} else {
|
|
1092
|
+
updates.marketPercent = null;
|
|
981
1093
|
}
|
|
1094
|
+
} else {
|
|
1095
|
+
updates.marketPercent = null;
|
|
982
1096
|
}
|
|
983
|
-
|
|
1097
|
+
|
|
984
1098
|
actual.market = updates.market;
|
|
985
1099
|
actual.marketPercent = updates.marketPercent;
|
|
986
1100
|
actual.unrealizedToday = updates.unrealizedToday;
|
|
1101
|
+
actual.total = updates.unrealizedToday.add(actual.realized).add(actual.income);
|
|
987
1102
|
|
|
988
|
-
format.market = formatCurrency(
|
|
989
|
-
format.marketPercent = formatPercent(
|
|
990
|
-
format.unrealizedToday = formatCurrency(
|
|
1103
|
+
format.market = formatCurrency(actual.market, currency);
|
|
1104
|
+
format.marketPercent = formatPercent(actual.marketPercent, 2);
|
|
1105
|
+
format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
|
|
991
1106
|
format.unrealizedTodayNegative = actual.unrealizedToday.getIsNegative();
|
|
1107
|
+
format.total = formatCurrency(actual.total, currency);
|
|
992
1108
|
}
|
|
993
1109
|
|
|
994
1110
|
return PositionGroup;
|
|
@@ -1073,10 +1189,13 @@ module.exports = (() => {
|
|
|
1073
1189
|
|
|
1074
1190
|
this._data.market = null;
|
|
1075
1191
|
this._data.marketChange = null;
|
|
1076
|
-
|
|
1192
|
+
|
|
1077
1193
|
this._data.unrealizedToday = null;
|
|
1078
1194
|
this._data.unrealizedTodayChange = null;
|
|
1079
1195
|
|
|
1196
|
+
this._data.realized = null;
|
|
1197
|
+
this._data.income = null;
|
|
1198
|
+
|
|
1080
1199
|
calculateStaticData(this);
|
|
1081
1200
|
calculatePriceData(this, null);
|
|
1082
1201
|
|
|
@@ -1121,10 +1240,11 @@ module.exports = (() => {
|
|
|
1121
1240
|
function calculateStaticData(item) {
|
|
1122
1241
|
const position = item.position;
|
|
1123
1242
|
const snapshot = item.position.snapshot;
|
|
1243
|
+
const summaries = item.summaries;
|
|
1124
1244
|
|
|
1125
1245
|
const data = item._data;
|
|
1126
1246
|
|
|
1127
|
-
data.previousPrice = position.
|
|
1247
|
+
data.previousPrice = position.previous || null;
|
|
1128
1248
|
|
|
1129
1249
|
let basis;
|
|
1130
1250
|
|
|
@@ -1135,6 +1255,26 @@ module.exports = (() => {
|
|
|
1135
1255
|
}
|
|
1136
1256
|
|
|
1137
1257
|
data.basis = basis;
|
|
1258
|
+
|
|
1259
|
+
data.realized = snapshot.gain;
|
|
1260
|
+
data.income = snapshot.income;
|
|
1261
|
+
|
|
1262
|
+
const getSummaryTotal = (index) => {
|
|
1263
|
+
let summaryTotal;
|
|
1264
|
+
|
|
1265
|
+
if (summaries.length > (index + 1) && summaries[index] !== null) {
|
|
1266
|
+
const period = summaries[index].period;
|
|
1267
|
+
|
|
1268
|
+
summaryTotal = period.realized.add(period.unrealized).add(period.income);
|
|
1269
|
+
} else {
|
|
1270
|
+
summaryTotal = Decimal.ZERO;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
return summaryTotal;
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
data.summaryOneTotal = getSummaryTotal(0);
|
|
1277
|
+
data.summaryTwoTotal = getSummaryTotal(1);
|
|
1138
1278
|
}
|
|
1139
1279
|
|
|
1140
1280
|
function calculatePriceData(item, price) {
|
|
@@ -5601,7 +5741,9 @@ describe('When a position container data is gathered', () => {
|
|
|
5601
5741
|
snapshot: {
|
|
5602
5742
|
basis: new Decimal(123),
|
|
5603
5743
|
value: new Decimal(456),
|
|
5604
|
-
open: new Decimal(1)
|
|
5744
|
+
open: new Decimal(1),
|
|
5745
|
+
income: new Decimal(0),
|
|
5746
|
+
gain: new Decimal(0)
|
|
5605
5747
|
}
|
|
5606
5748
|
}
|
|
5607
5749
|
}
|