@barchart/portfolio-api-common 1.0.65 → 1.0.69
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 +22 -8
- package/lib/processing/PositionGroup.js +51 -19
- package/lib/processing/PositionItem.js +26 -2
- package/package.json +1 -1
- package/test/SpecRunner.js +180 -37
- 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,8 @@ 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 InstrumentType = require('./../data/InstrumentType'),
|
|
10
|
+
PositionSummaryFrame = require('./../data/PositionSummaryFrame');
|
|
10
11
|
|
|
11
12
|
const PositionGroup = require('./PositionGroup'),
|
|
12
13
|
PositionItem = require('./PositionItem');
|
|
@@ -18,10 +19,13 @@ module.exports = (() => {
|
|
|
18
19
|
* @public
|
|
19
20
|
*/
|
|
20
21
|
class PositionContainer {
|
|
21
|
-
constructor(portfolios, positions, summaries, definitions, defaultCurrency) {
|
|
22
|
+
constructor(portfolios, positions, summaries, definitions, defaultCurrency, summaryFrameType) {
|
|
22
23
|
this._definitions = definitions;
|
|
23
24
|
this._defaultCurrency = defaultCurrency || Currency.CAD;
|
|
24
25
|
|
|
26
|
+
this._summaryFrame = summaryFrameType || PositionSummaryFrame.YEARLY;
|
|
27
|
+
this._summaryRanges = this._summaryFrame.getRecentRanges(2);
|
|
28
|
+
|
|
25
29
|
this._portfolios = portfolios.reduce((map, portfolio) => {
|
|
26
30
|
map[portfolio.portfolio] = portfolio;
|
|
27
31
|
|
|
@@ -29,13 +33,19 @@ module.exports = (() => {
|
|
|
29
33
|
}, { });
|
|
30
34
|
|
|
31
35
|
this._summaries = summaries.reduce((map, summary) => {
|
|
32
|
-
|
|
36
|
+
if (this._summaryFrame === summary.frame) {
|
|
37
|
+
const key = summary.position;
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
if (!map.hasOwnProperty(key)) {
|
|
40
|
+
map[key] = getSummaryArray(this._summaryRanges);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const index = this._summaryRanges.findIndex(r => r.start === summary.start.date && r.end === summary.end.date);
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
if (!(index < 0)) {
|
|
46
|
+
map[key][index] = summary;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
39
49
|
|
|
40
50
|
return map;
|
|
41
51
|
}, { });
|
|
@@ -44,7 +54,7 @@ module.exports = (() => {
|
|
|
44
54
|
const portfolio = this._portfolios[position.portfolio];
|
|
45
55
|
|
|
46
56
|
if (position) {
|
|
47
|
-
const summaries = this._summaries[position.position] ||
|
|
57
|
+
const summaries = this._summaries[position.position] || getSummaryArray(this._summaryRanges);
|
|
48
58
|
|
|
49
59
|
items.push(new PositionItem(portfolio, position, summaries));
|
|
50
60
|
}
|
|
@@ -206,5 +216,9 @@ module.exports = (() => {
|
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
218
|
|
|
219
|
+
function getSummaryArray(ranges) {
|
|
220
|
+
return ranges.map(range => null);
|
|
221
|
+
}
|
|
222
|
+
|
|
209
223
|
return PositionContainer;
|
|
210
224
|
})();
|
|
@@ -25,22 +25,32 @@ 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
|
-
this._dataFormat.
|
|
53
|
+
this._dataFormat.unrealizedTodayNegative = false;
|
|
44
54
|
|
|
45
55
|
this._items.forEach((item) => {
|
|
46
56
|
item.registerPriceChangeHandler((data, sender) => {
|
|
@@ -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(
|
|
173
|
-
format.
|
|
201
|
+
format.market = formatCurrency(actual.market, currency);
|
|
202
|
+
format.marketPercent = formatPercent(actual.marketPercent, 2);
|
|
203
|
+
format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
|
|
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,8 @@ 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 InstrumentType = require('./../data/InstrumentType'),
|
|
687
|
+
PositionSummaryFrame = require('./../data/PositionSummaryFrame');
|
|
616
688
|
|
|
617
689
|
const PositionGroup = require('./PositionGroup'),
|
|
618
690
|
PositionItem = require('./PositionItem');
|
|
@@ -624,10 +696,13 @@ module.exports = (() => {
|
|
|
624
696
|
* @public
|
|
625
697
|
*/
|
|
626
698
|
class PositionContainer {
|
|
627
|
-
constructor(portfolios, positions, summaries, definitions, defaultCurrency) {
|
|
699
|
+
constructor(portfolios, positions, summaries, definitions, defaultCurrency, summaryFrameType) {
|
|
628
700
|
this._definitions = definitions;
|
|
629
701
|
this._defaultCurrency = defaultCurrency || Currency.CAD;
|
|
630
702
|
|
|
703
|
+
this._summaryFrame = summaryFrameType || PositionSummaryFrame.YEARLY;
|
|
704
|
+
this._summaryRanges = this._summaryFrame.getRecentRanges(2);
|
|
705
|
+
|
|
631
706
|
this._portfolios = portfolios.reduce((map, portfolio) => {
|
|
632
707
|
map[portfolio.portfolio] = portfolio;
|
|
633
708
|
|
|
@@ -635,13 +710,19 @@ module.exports = (() => {
|
|
|
635
710
|
}, { });
|
|
636
711
|
|
|
637
712
|
this._summaries = summaries.reduce((map, summary) => {
|
|
638
|
-
|
|
713
|
+
if (this._summaryFrame === summary.frame) {
|
|
714
|
+
const key = summary.position;
|
|
639
715
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
716
|
+
if (!map.hasOwnProperty(key)) {
|
|
717
|
+
map[key] = getSummaryArray(this._summaryRanges);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const index = this._summaryRanges.findIndex(r => r.start === summary.start.date && r.end === summary.end.date);
|
|
643
721
|
|
|
644
|
-
|
|
722
|
+
if (!(index < 0)) {
|
|
723
|
+
map[key][index] = summary;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
645
726
|
|
|
646
727
|
return map;
|
|
647
728
|
}, { });
|
|
@@ -650,7 +731,7 @@ module.exports = (() => {
|
|
|
650
731
|
const portfolio = this._portfolios[position.portfolio];
|
|
651
732
|
|
|
652
733
|
if (position) {
|
|
653
|
-
const summaries = this._summaries[position.position] ||
|
|
734
|
+
const summaries = this._summaries[position.position] || getSummaryArray(this._summaryRanges);
|
|
654
735
|
|
|
655
736
|
items.push(new PositionItem(portfolio, position, summaries));
|
|
656
737
|
}
|
|
@@ -812,10 +893,14 @@ module.exports = (() => {
|
|
|
812
893
|
}
|
|
813
894
|
}
|
|
814
895
|
|
|
896
|
+
function getSummaryArray(ranges) {
|
|
897
|
+
return ranges.map(range => null);
|
|
898
|
+
}
|
|
899
|
+
|
|
815
900
|
return PositionContainer;
|
|
816
901
|
})();
|
|
817
902
|
|
|
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){
|
|
903
|
+
},{"./../data/InstrumentType":1,"./../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
904
|
const assert = require('@barchart/common-js/lang/assert'),
|
|
820
905
|
Currency = require('@barchart/common-js/lang/Currency'),
|
|
821
906
|
Decimal = require('@barchart/common-js/lang/Decimal'),
|
|
@@ -843,22 +928,32 @@ module.exports = (() => {
|
|
|
843
928
|
this._dataActual = { };
|
|
844
929
|
|
|
845
930
|
this._dataFormat.description = this._description;
|
|
846
|
-
|
|
931
|
+
|
|
847
932
|
this._dataActual.currentPrice = null;
|
|
848
933
|
this._dataActual.previousPrice = null;
|
|
849
934
|
this._dataActual.basis = null;
|
|
935
|
+
this._dataActual.realized = null;
|
|
936
|
+
this._dataActual.income = null;
|
|
850
937
|
this._dataActual.market = null;
|
|
851
938
|
this._dataActual.marketPercent = null;
|
|
852
939
|
this._dataActual.unrealizedToday = null;
|
|
940
|
+
this._dataActual.total = null;
|
|
941
|
+
this._dataActual.summaryOneTotal = null;
|
|
942
|
+
this._dataActual.summaryTwoTotal = null;
|
|
853
943
|
|
|
854
944
|
this._dataFormat.currentPrice = null;
|
|
855
945
|
this._dataFormat.previousPrice = null;
|
|
856
946
|
this._dataFormat.basis = null;
|
|
947
|
+
this._dataFormat.realized = null;
|
|
948
|
+
this._dataFormat.income = null;
|
|
857
949
|
this._dataFormat.market = null;
|
|
858
950
|
this._dataFormat.marketPercent = null;
|
|
859
951
|
this._dataFormat.unrealizedToday = null;
|
|
952
|
+
this._dataFormat.total = null;
|
|
953
|
+
this._dataFormat.summaryOneTotal = null;
|
|
954
|
+
this._dataFormat.summaryTwoTotal = null;
|
|
860
955
|
|
|
861
|
-
this._dataFormat.
|
|
956
|
+
this._dataFormat.unrealizedTodayNegative = false;
|
|
862
957
|
|
|
863
958
|
this._items.forEach((item) => {
|
|
864
959
|
item.registerPriceChangeHandler((data, sender) => {
|
|
@@ -907,15 +1002,15 @@ module.exports = (() => {
|
|
|
907
1002
|
if (decimal !== null) {
|
|
908
1003
|
return formatter.numberToString(decimal.toFloat(), precision, ',', false);
|
|
909
1004
|
} else {
|
|
910
|
-
return '
|
|
1005
|
+
return '—';
|
|
911
1006
|
}
|
|
912
1007
|
}
|
|
913
1008
|
|
|
914
1009
|
function formatPercent(decimal, precision) {
|
|
915
1010
|
if (decimal !== null) {
|
|
916
|
-
return formatNumber(decimal.multiply(100));
|
|
1011
|
+
return formatNumber(decimal.multiply(100), precision);
|
|
917
1012
|
} else {
|
|
918
|
-
return '
|
|
1013
|
+
return '—';
|
|
919
1014
|
}
|
|
920
1015
|
}
|
|
921
1016
|
|
|
@@ -933,15 +1028,31 @@ module.exports = (() => {
|
|
|
933
1028
|
|
|
934
1029
|
let updates = items.reduce((updates, item) => {
|
|
935
1030
|
updates.basis = updates.basis.add(item.data.basis);
|
|
1031
|
+
updates.realized = updates.realized.add(item.data.realized);
|
|
1032
|
+
updates.income = updates.income.add(item.data.income);
|
|
1033
|
+
updates.summaryOneTotal = updates.summaryOneTotal.add(item.data.summaryOneTotal);
|
|
1034
|
+
updates.summaryTwoTotal = updates.summaryTwoTotal.add(item.data.summaryTwoTotal);
|
|
936
1035
|
|
|
937
1036
|
return updates;
|
|
938
1037
|
}, {
|
|
939
|
-
basis: Decimal.ZERO
|
|
1038
|
+
basis: Decimal.ZERO,
|
|
1039
|
+
realized: Decimal.ZERO,
|
|
1040
|
+
income: Decimal.ZERO,
|
|
1041
|
+
summaryOneTotal: Decimal.ZERO,
|
|
1042
|
+
summaryTwoTotal: Decimal.ZERO
|
|
940
1043
|
});
|
|
941
1044
|
|
|
942
1045
|
actual.basis = updates.basis;
|
|
943
|
-
|
|
944
|
-
|
|
1046
|
+
actual.realized = updates.realized;
|
|
1047
|
+
actual.income = updates.income;
|
|
1048
|
+
actual.summaryOneTotal = updates.summaryOneTotal;
|
|
1049
|
+
actual.summaryTwoTotal = updates.summaryTwoTotal;
|
|
1050
|
+
|
|
1051
|
+
format.basis = formatCurrency(actual.basis, currency);
|
|
1052
|
+
format.realized = formatCurrency(actual.basis, currency);
|
|
1053
|
+
format.income = formatCurrency(actual.income, currency);
|
|
1054
|
+
format.summaryOneTotal = formatCurrency(updates.summaryOneTotal, currency);
|
|
1055
|
+
format.summaryTwoTotal = formatCurrency(updates.summaryTwoTotal, currency);
|
|
945
1056
|
}
|
|
946
1057
|
|
|
947
1058
|
function calculatePriceData(group, item) {
|
|
@@ -954,7 +1065,12 @@ module.exports = (() => {
|
|
|
954
1065
|
|
|
955
1066
|
let updates;
|
|
956
1067
|
|
|
957
|
-
if (actual.market
|
|
1068
|
+
if (actual.market !== null && actual.unrealizedToday !== null && actual.total !== null) {
|
|
1069
|
+
updates = {
|
|
1070
|
+
market: actual.market.add(item.data.marketChange),
|
|
1071
|
+
unrealizedToday: actual.unrealizedToday.add(item.data.unrealizedTodayChange)
|
|
1072
|
+
};
|
|
1073
|
+
} else {
|
|
958
1074
|
const items = group._items;
|
|
959
1075
|
|
|
960
1076
|
updates = items.reduce((updates, item) => {
|
|
@@ -966,11 +1082,6 @@ module.exports = (() => {
|
|
|
966
1082
|
market: Decimal.ZERO,
|
|
967
1083
|
unrealizedToday: Decimal.ZERO
|
|
968
1084
|
});
|
|
969
|
-
} else {
|
|
970
|
-
updates = {
|
|
971
|
-
market: actual.market.add(item.data.marketChange),
|
|
972
|
-
unrealizedToday: actual.unrealizedToday.add(item.data.unrealizedTodayChange)
|
|
973
|
-
};
|
|
974
1085
|
}
|
|
975
1086
|
|
|
976
1087
|
if (parent !== null) {
|
|
@@ -978,17 +1089,23 @@ module.exports = (() => {
|
|
|
978
1089
|
|
|
979
1090
|
if (parentData.market !== null && !parentData.market.getIsZero()) {
|
|
980
1091
|
updates.marketPercent = updates.market.divide(parentData.market);
|
|
1092
|
+
} else {
|
|
1093
|
+
updates.marketPercent = null;
|
|
981
1094
|
}
|
|
1095
|
+
} else {
|
|
1096
|
+
updates.marketPercent = null;
|
|
982
1097
|
}
|
|
983
|
-
|
|
1098
|
+
|
|
984
1099
|
actual.market = updates.market;
|
|
985
1100
|
actual.marketPercent = updates.marketPercent;
|
|
986
1101
|
actual.unrealizedToday = updates.unrealizedToday;
|
|
1102
|
+
actual.total = updates.unrealizedToday.add(actual.realized).add(actual.income);
|
|
987
1103
|
|
|
988
|
-
format.market = formatCurrency(
|
|
989
|
-
format.marketPercent = formatPercent(
|
|
990
|
-
format.unrealizedToday = formatCurrency(
|
|
991
|
-
format.
|
|
1104
|
+
format.market = formatCurrency(actual.market, currency);
|
|
1105
|
+
format.marketPercent = formatPercent(actual.marketPercent, 2);
|
|
1106
|
+
format.unrealizedToday = formatCurrency(actual.unrealizedToday, currency);
|
|
1107
|
+
format.unrealizedTodayNegative = actual.unrealizedToday.getIsNegative();
|
|
1108
|
+
format.total = formatCurrency(actual.total, currency);
|
|
992
1109
|
}
|
|
993
1110
|
|
|
994
1111
|
return PositionGroup;
|
|
@@ -1073,10 +1190,13 @@ module.exports = (() => {
|
|
|
1073
1190
|
|
|
1074
1191
|
this._data.market = null;
|
|
1075
1192
|
this._data.marketChange = null;
|
|
1076
|
-
|
|
1193
|
+
|
|
1077
1194
|
this._data.unrealizedToday = null;
|
|
1078
1195
|
this._data.unrealizedTodayChange = null;
|
|
1079
1196
|
|
|
1197
|
+
this._data.realized = null;
|
|
1198
|
+
this._data.income = null;
|
|
1199
|
+
|
|
1080
1200
|
calculateStaticData(this);
|
|
1081
1201
|
calculatePriceData(this, null);
|
|
1082
1202
|
|
|
@@ -1121,10 +1241,11 @@ module.exports = (() => {
|
|
|
1121
1241
|
function calculateStaticData(item) {
|
|
1122
1242
|
const position = item.position;
|
|
1123
1243
|
const snapshot = item.position.snapshot;
|
|
1244
|
+
const summaries = item.summaries;
|
|
1124
1245
|
|
|
1125
1246
|
const data = item._data;
|
|
1126
1247
|
|
|
1127
|
-
data.previousPrice = position.
|
|
1248
|
+
data.previousPrice = position.previous || null;
|
|
1128
1249
|
|
|
1129
1250
|
let basis;
|
|
1130
1251
|
|
|
@@ -1135,6 +1256,26 @@ module.exports = (() => {
|
|
|
1135
1256
|
}
|
|
1136
1257
|
|
|
1137
1258
|
data.basis = basis;
|
|
1259
|
+
|
|
1260
|
+
data.realized = snapshot.gain;
|
|
1261
|
+
data.income = snapshot.income;
|
|
1262
|
+
|
|
1263
|
+
const getSummaryTotal = (index) => {
|
|
1264
|
+
let summaryTotal;
|
|
1265
|
+
|
|
1266
|
+
if (summaries.length > (index + 1) && summaries[index] !== null) {
|
|
1267
|
+
const period = summaries[index].period;
|
|
1268
|
+
|
|
1269
|
+
summaryTotal = period.realized.add(period.unrealized).add(period.income);
|
|
1270
|
+
} else {
|
|
1271
|
+
summaryTotal = Decimal.ZERO;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
return summaryTotal;
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
data.summaryOneTotal = getSummaryTotal(0);
|
|
1278
|
+
data.summaryTwoTotal = getSummaryTotal(1);
|
|
1138
1279
|
}
|
|
1139
1280
|
|
|
1140
1281
|
function calculatePriceData(item, price) {
|
|
@@ -5601,7 +5742,9 @@ describe('When a position container data is gathered', () => {
|
|
|
5601
5742
|
snapshot: {
|
|
5602
5743
|
basis: new Decimal(123),
|
|
5603
5744
|
value: new Decimal(456),
|
|
5604
|
-
open: new Decimal(1)
|
|
5745
|
+
open: new Decimal(1),
|
|
5746
|
+
income: new Decimal(0),
|
|
5747
|
+
gain: new Decimal(0)
|
|
5605
5748
|
}
|
|
5606
5749
|
}
|
|
5607
5750
|
}
|