@barchart/portfolio-api-common 1.0.34 → 1.0.39
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 +34 -5
- package/lib/processing/PositionContainer.js +133 -0
- package/lib/processing/PositionGroup.js +66 -0
- package/lib/processing/PositionGroupDefinition.js +43 -0
- package/lib/processing/PositionItem.js +51 -0
- package/lib/serialization/PortfolioSchema.js +18 -0
- package/package.json +1 -1
- package/test/SpecRunner.js +1185 -64
- package/test/specs/data/PositionSummaryFrameSpec.js +42 -0
- package/test/specs/processing/PositionContainerSpec.js +97 -0
|
@@ -15,14 +15,16 @@ module.exports = (() => {
|
|
|
15
15
|
* @param {String} code
|
|
16
16
|
* @param {String} description
|
|
17
17
|
* @param {Function} rangeCalculator
|
|
18
|
+
* @param {Function} startDateCalculator
|
|
18
19
|
*/
|
|
19
20
|
class PositionSummaryFrame extends Enum {
|
|
20
|
-
constructor(code, description, rangeCalculator) {
|
|
21
|
+
constructor(code, description, rangeCalculator, startDateCalculator) {
|
|
21
22
|
super(code, description);
|
|
22
23
|
|
|
23
24
|
assert.argumentIsRequired(rangeCalculator, 'rangeCalculator', Function);
|
|
24
25
|
|
|
25
26
|
this._rangeCalculator = rangeCalculator;
|
|
27
|
+
this._startDateCalculator = startDateCalculator;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
getRanges(transactions) {
|
|
@@ -31,6 +33,12 @@ module.exports = (() => {
|
|
|
31
33
|
return this._rangeCalculator(getFilteredTransactions(transactions));
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
getStartDate(periods) {
|
|
37
|
+
assert.argumentIsRequired(periods, 'periods', Number);
|
|
38
|
+
|
|
39
|
+
return this._startDateCalculator(periods);
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
/**
|
|
35
43
|
* A summary for a calendar year.
|
|
36
44
|
*
|
|
@@ -76,10 +84,10 @@ module.exports = (() => {
|
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
const yearly = new PositionSummaryFrame('YEARLY', 'year', getYearlyRanges);
|
|
80
|
-
const quarterly = new PositionSummaryFrame('QUARTER', 'quarter', getQuarterlyRanges);
|
|
81
|
-
const monthly = new PositionSummaryFrame('MONTH', 'month', getMonthlyRanges);
|
|
82
|
-
const ytd = new PositionSummaryFrame('YTD', 'year-to-date', getYearToDateRanges);
|
|
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);
|
|
83
91
|
|
|
84
92
|
function getRange(start, end) {
|
|
85
93
|
return {
|
|
@@ -142,6 +150,27 @@ module.exports = (() => {
|
|
|
142
150
|
return ranges;
|
|
143
151
|
}
|
|
144
152
|
|
|
153
|
+
function getYearlyStartDate(periods) {
|
|
154
|
+
const today = Day.getToday();
|
|
155
|
+
|
|
156
|
+
return Day.getToday()
|
|
157
|
+
.subtractMonths(today.month - 1)
|
|
158
|
+
.subtractDays(today.day)
|
|
159
|
+
.subtractYears(periods);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getQuarterlyStartDate(periods) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getMonthlyStartDate(periods) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getYearToDateStartDate(periods) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
145
174
|
function getFilteredTransactions(transactions) {
|
|
146
175
|
return transactions.reduce((filtered, transaction) => {
|
|
147
176
|
if (!transaction.snapshot.open.getIsZero() || transaction.type.closing) {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const array = require('@barchart/common-js/lang/array'),
|
|
2
|
+
assert = require('@barchart/common-js/lang/assert'),
|
|
3
|
+
is = require('@barchart/common-js/lang/is'),
|
|
4
|
+
Tree = require('@barchart/common-js/collections/Tree');
|
|
5
|
+
|
|
6
|
+
const PositionGroup = require('./PositionGroup'),
|
|
7
|
+
PositionGroupDefinition = require('./PositionGroupDefinition'),
|
|
8
|
+
PositionItem = require('./PositionItem');
|
|
9
|
+
|
|
10
|
+
module.exports = (() => {
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
class PositionContainer {
|
|
17
|
+
constructor(portfolios, positions, summaries, definitions) {
|
|
18
|
+
this._portfolios = portfolios.reduce((map, portfolio) => {
|
|
19
|
+
map[portfolio.portfolio] = portfolio;
|
|
20
|
+
|
|
21
|
+
return map;
|
|
22
|
+
}, { });
|
|
23
|
+
|
|
24
|
+
this._summaries = summaries.reduce((map, summary) => {
|
|
25
|
+
const key = summary.position;
|
|
26
|
+
|
|
27
|
+
if (!map.hasOwnProperty(key)) {
|
|
28
|
+
map[key] = [ ];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
map[key].push(summary);
|
|
32
|
+
|
|
33
|
+
return map;
|
|
34
|
+
}, { });
|
|
35
|
+
|
|
36
|
+
this._items = positions.reduce((items, position) => {
|
|
37
|
+
const portfolio = this._portfolios[position.portfolio];
|
|
38
|
+
|
|
39
|
+
if (position) {
|
|
40
|
+
const summaries = this._summaries[position.position] || [ ];
|
|
41
|
+
|
|
42
|
+
items.push(new PositionItem(portfolio, position, summaries));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return items;
|
|
46
|
+
}, [ ]);
|
|
47
|
+
|
|
48
|
+
this._symbols = this._items.reduce((map, item) => {
|
|
49
|
+
let position = item.position;
|
|
50
|
+
let symbol = null;
|
|
51
|
+
|
|
52
|
+
if (position.instrument && position.instrument.symbol && position.instrument.barchart) {
|
|
53
|
+
symbol = position.instrument.barchart;
|
|
54
|
+
|
|
55
|
+
if (!map.hasOwnProperty(symbol)) {
|
|
56
|
+
map[symbol] = [ ];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
map[symbol].push(item);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return map;
|
|
63
|
+
}, { });
|
|
64
|
+
|
|
65
|
+
this._definitions = definitions || [ new PositionGroupDefinition('Totals', i => true, i => 'Totals', [ 'Totals' ]) ];
|
|
66
|
+
|
|
67
|
+
this._tree = new Tree();
|
|
68
|
+
|
|
69
|
+
const createGroups = (tree, items, definitions) => {
|
|
70
|
+
if (definitions.length === 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const currentDefinition = definitions[0];
|
|
75
|
+
const additionalDefinitions = array.dropLeft(definitions);
|
|
76
|
+
|
|
77
|
+
const populatedGroups = array.batchBy(items, currentDefinition.keySelector).map((items) => {
|
|
78
|
+
const first = items[0];
|
|
79
|
+
|
|
80
|
+
return new PositionGroup(items, currentDefinition.descriptionSelector(first), currentDefinition.single && items.length === 1);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const missingGroups = array.difference(currentDefinition.requiredGroups, populatedGroups.map(group => group.description));
|
|
84
|
+
|
|
85
|
+
const empty = missingGroups.map((description) => {
|
|
86
|
+
return new PositionGroup(description, [ ]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const compositeGroups = populatedGroups.concat(empty);
|
|
90
|
+
|
|
91
|
+
compositeGroups.forEach((group) => {
|
|
92
|
+
const child = tree.addChild(group);
|
|
93
|
+
|
|
94
|
+
createGroups(child, group.items, additionalDefinitions);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
createGroups(this._tree, this._items, this._definitions);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
setPrice(symbol, price) {
|
|
102
|
+
if (this._symbols.hasOwnProperty(symbol)) {
|
|
103
|
+
this._symbols.forEach(item.setPrice(price));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getGroup(keys) {
|
|
108
|
+
const node = keys.reduce((tree, key) => {
|
|
109
|
+
tree = tree.findChild((group) => group.description === key);
|
|
110
|
+
|
|
111
|
+
return tree;
|
|
112
|
+
}, this._tree);
|
|
113
|
+
|
|
114
|
+
return node.getValue();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getGroups(keys) {
|
|
118
|
+
const node = keys.reduce((tree, key) => {
|
|
119
|
+
tree = tree.findChild((group) => group.description === key);
|
|
120
|
+
|
|
121
|
+
return tree;
|
|
122
|
+
}, this._tree);
|
|
123
|
+
|
|
124
|
+
return node.getChildren().map((node) => node.getValue());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
toString() {
|
|
128
|
+
return '[PositionContainer]';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return PositionContainer;
|
|
133
|
+
})();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const assert = require('@barchart/common-js/lang/assert'),
|
|
2
|
+
is = require('@barchart/common-js/lang/is');
|
|
3
|
+
|
|
4
|
+
module.exports = (() => {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
class PositionGroup {
|
|
11
|
+
constructor(items, description, single) {
|
|
12
|
+
this._description = description;
|
|
13
|
+
this._items = items;
|
|
14
|
+
|
|
15
|
+
this._single = is.boolean(single) && single;
|
|
16
|
+
|
|
17
|
+
this._data = { };
|
|
18
|
+
|
|
19
|
+
this._data.description = this._description;
|
|
20
|
+
|
|
21
|
+
this._data.previous = null;
|
|
22
|
+
this._data.current = null;
|
|
23
|
+
|
|
24
|
+
this._items.forEach((item) => {
|
|
25
|
+
item.registerPriceChangeHandler((price, sender) => {
|
|
26
|
+
if (this._single) {
|
|
27
|
+
data.current = price;
|
|
28
|
+
} else {
|
|
29
|
+
data.current = null;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
calculateStaticData(this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get description() {
|
|
38
|
+
return this._description;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get data() {
|
|
42
|
+
return this._data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get items() {
|
|
46
|
+
return this._items;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
toString() {
|
|
50
|
+
return '[PositionGroup]';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function calculateStaticData(group) {
|
|
55
|
+
const items = group._items;
|
|
56
|
+
const data = group._data;
|
|
57
|
+
|
|
58
|
+
const updates = items.reduce(function(updates, item) {
|
|
59
|
+
|
|
60
|
+
}, { });
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return PositionGroup;
|
|
66
|
+
})();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const assert = require('@barchart/common-js/lang/assert'),
|
|
2
|
+
is = require('@barchart/common-js/lang/is');
|
|
3
|
+
|
|
4
|
+
module.exports = (() => {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
class PositionGroupDefinition {
|
|
11
|
+
constructor(name, keySelector, descriptionSelector, requiredGroups, single) {
|
|
12
|
+
this._name = name;
|
|
13
|
+
|
|
14
|
+
this._keySelector = keySelector;
|
|
15
|
+
this._descriptionSelector = descriptionSelector;
|
|
16
|
+
|
|
17
|
+
this._requiredGroups = requiredGroups || [ ];
|
|
18
|
+
this._single = is.boolean(single) && single;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get name() {
|
|
22
|
+
return this._name;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get keySelector() {
|
|
26
|
+
return this._keySelector;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get descriptionSelector() {
|
|
30
|
+
return this._descriptionSelector;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get requiredGroups() {
|
|
34
|
+
return this._requiredGroups;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
toString() {
|
|
38
|
+
return '[PositionGroupDefinition]';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return PositionGroupDefinition;
|
|
43
|
+
})();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const assert = require('@barchart/common-js/lang/assert'),
|
|
2
|
+
Event = require('@barchart/common-js/messaging/Event'),
|
|
3
|
+
is = require('@barchart/common-js/lang/is');
|
|
4
|
+
|
|
5
|
+
module.exports = (() => {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
class PositionItem {
|
|
12
|
+
constructor(portfolio, position, summaries) {
|
|
13
|
+
this._portfolio = portfolio;
|
|
14
|
+
this._position = position;
|
|
15
|
+
this._summaries = summaries || [ ];
|
|
16
|
+
|
|
17
|
+
this._price = null;
|
|
18
|
+
this._priceChangeEvent = new Event(this);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get portfolio() {
|
|
22
|
+
return this._portfolio;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get position() {
|
|
26
|
+
return this._position;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get summaries() {
|
|
30
|
+
return this._summaries;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setPrice(price) {
|
|
34
|
+
if (this._price !== price) {
|
|
35
|
+
this._priceChangeEvent.fire(this._price = price);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
registerPriceChangeHandler(handler) {
|
|
40
|
+
assert.argumentIsRequired(handler, 'handler', Function);
|
|
41
|
+
|
|
42
|
+
this._priceChangeEvent.register(handler);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
toString() {
|
|
46
|
+
return '[PositionItem]';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return PositionItem;
|
|
51
|
+
})();
|
|
@@ -57,6 +57,17 @@ module.exports = (() => {
|
|
|
57
57
|
return client;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Only returns identifiers and portfolio name.
|
|
62
|
+
*
|
|
63
|
+
* @static
|
|
64
|
+
* @public
|
|
65
|
+
* @returns {PortfolioSchema}
|
|
66
|
+
*/
|
|
67
|
+
static get NAME() {
|
|
68
|
+
return name;
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
/**
|
|
61
72
|
* Data required to create a portfolio.
|
|
62
73
|
*
|
|
@@ -125,6 +136,13 @@ module.exports = (() => {
|
|
|
125
136
|
.schema
|
|
126
137
|
);
|
|
127
138
|
|
|
139
|
+
const name = new PortfolioSchema(SchemaBuilder.withName('name')
|
|
140
|
+
.withField('user', DataType.STRING)
|
|
141
|
+
.withField('portfolio', DataType.STRING)
|
|
142
|
+
.withField('name', DataType.STRING)
|
|
143
|
+
.schema
|
|
144
|
+
);
|
|
145
|
+
|
|
128
146
|
const create = new PortfolioSchema(SchemaBuilder.withName('create')
|
|
129
147
|
.withField('name', DataType.STRING)
|
|
130
148
|
.withField('timezone', DataType.forEnum(Timezones, 'Timezone'))
|