@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.
- package/lib/data/PositionSummaryFrame.js +76 -5
- package/lib/processing/PositionContainer.js +34 -11
- package/lib/processing/PositionGroup.js +91 -14
- package/lib/processing/PositionItem.js +45 -2
- package/lib/serialization/PortfolioSchema.js +1 -0
- package/package.json +1 -1
- package/test/SpecRunner.js +251 -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
|
+
}
|
|
37
41
|
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
175
|
-
format.marketPercent = formatPercent(
|
|
176
|
-
format.unrealizedToday = formatCurrency(
|
|
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
|
-
|
|
64
|
-
assert.argumentIsRequired(
|
|
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
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);
|
|
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
|
|
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
|
+
}
|
|
643
718
|
|
|
644
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
993
|
-
format.marketPercent = formatPercent(
|
|
994
|
-
format.unrealizedToday = formatCurrency(
|
|
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
|
-
|
|
1115
|
-
assert.argumentIsRequired(
|
|
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
|
}
|