@barchart/portfolio-api-common 1.0.265 → 1.0.269
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/processing/PositionContainer.js +109 -124
- package/lib/processing/PositionGroup.js +62 -80
- package/package.json +1 -1
- package/test/SpecRunner.js +171 -204
|
@@ -146,6 +146,8 @@ module.exports = (() => {
|
|
|
146
146
|
}, { });
|
|
147
147
|
|
|
148
148
|
Object.keys(this._portfolios).forEach(key => updateEmptyPortfolioGroups.call(this, this._portfolios[key]));
|
|
149
|
+
|
|
150
|
+
recalculatePercentages.call(this);
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
/**
|
|
@@ -222,11 +224,9 @@ module.exports = (() => {
|
|
|
222
224
|
assert.argumentIsRequired(portfolio, 'portfolio', Object);
|
|
223
225
|
assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
|
|
224
226
|
|
|
225
|
-
this.
|
|
226
|
-
getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => item.updatePortfolio(portfolio));
|
|
227
|
+
getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => item.updatePortfolio(portfolio));
|
|
227
228
|
|
|
228
|
-
|
|
229
|
-
});
|
|
229
|
+
updateEmptyPortfolioGroups.call(this, portfolio);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
/**
|
|
@@ -241,19 +241,19 @@ module.exports = (() => {
|
|
|
241
241
|
assert.argumentIsRequired(portfolio, 'portfolio', Object);
|
|
242
242
|
assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
|
|
243
243
|
|
|
244
|
-
this.
|
|
245
|
-
getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => removePositionItem.call(this, item));
|
|
244
|
+
getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => removePositionItem.call(this, item));
|
|
246
245
|
|
|
247
|
-
|
|
246
|
+
delete this._portfolios[portfolio.portfolio];
|
|
248
247
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
});
|
|
248
|
+
Object.keys(this._trees).forEach((key) => {
|
|
249
|
+
this._trees[key].walk((group, groupNode) => {
|
|
250
|
+
if (group.definition.type === PositionLevelType.PORTFOLIO && group.key === PositionLevelDefinition.getKeyForPortfolioGroup(portfolio)) {
|
|
251
|
+
severGroupNode.call(this, groupNode);
|
|
252
|
+
}
|
|
253
|
+
}, true, false);
|
|
256
254
|
});
|
|
255
|
+
|
|
256
|
+
recalculatePercentages.call(this);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
/**
|
|
@@ -274,56 +274,56 @@ module.exports = (() => {
|
|
|
274
274
|
return;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
this.
|
|
278
|
-
const existingBarchartSymbols = this.getPositionSymbols(false);
|
|
277
|
+
const existingBarchartSymbols = this.getPositionSymbols(false);
|
|
279
278
|
|
|
280
|
-
|
|
279
|
+
removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
|
|
281
280
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
summaries.forEach((summary) => {
|
|
282
|
+
addSummaryCurrent(this._summariesCurrent, summary, this._currentSummaryFrame, this._currentSummaryRange);
|
|
283
|
+
addSummaryPrevious(this._summariesPrevious, summary, this._previousSummaryFrame, this._previousSummaryRanges);
|
|
284
|
+
});
|
|
286
285
|
|
|
287
|
-
|
|
286
|
+
const item = createPositionItem.call(this, position);
|
|
288
287
|
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
addBarchartSymbol(this._symbols, item);
|
|
289
|
+
addDisplaySymbol(this._symbolsDisplay, item);
|
|
291
290
|
|
|
292
|
-
|
|
291
|
+
this._items.push(item);
|
|
293
292
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
293
|
+
const createGroupOrInjectItem = (parentTree, treeDefinition, levelDefinitions) => {
|
|
294
|
+
if (levelDefinitions.length === 0) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
298
297
|
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
const levelDefinition = levelDefinitions[0];
|
|
299
|
+
const levelKey = levelDefinition.keySelector(item);
|
|
301
300
|
|
|
302
|
-
|
|
301
|
+
let groupTree;
|
|
303
302
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
303
|
+
if (parentTree.getChildren().length > 0) {
|
|
304
|
+
groupTree = parentTree.findChild(childGroup => childGroup.key === levelKey) || null;
|
|
305
|
+
} else {
|
|
306
|
+
groupTree = null;
|
|
307
|
+
}
|
|
309
308
|
|
|
310
|
-
|
|
311
|
-
|
|
309
|
+
if (groupTree !== null) {
|
|
310
|
+
groupTree.getValue().addItem(item);
|
|
312
311
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
312
|
+
createGroupOrInjectItem(groupTree, treeDefinition, array.dropLeft(levelDefinitions));
|
|
313
|
+
} else {
|
|
314
|
+
createGroups.call(this, parentTree, [ item ], treeDefinition, levelDefinitions, [ ]);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
318
317
|
|
|
319
|
-
|
|
318
|
+
this._definitions.forEach(definition => createGroupOrInjectItem(this._trees[definition.name], definition, definition.definitions));
|
|
320
319
|
|
|
321
|
-
|
|
320
|
+
const addedBarchartSymbol = extractSymbolForBarchart(item.position);
|
|
322
321
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
322
|
+
if (addedBarchartSymbol !== null && !existingBarchartSymbols.some(existingBarchartSymbol => existingBarchartSymbol === addedBarchartSymbol)) {
|
|
323
|
+
this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
recalculatePercentages.call(this);
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
/**
|
|
@@ -337,6 +337,8 @@ module.exports = (() => {
|
|
|
337
337
|
assert.argumentIsRequired(position.position, 'position.position', String);
|
|
338
338
|
|
|
339
339
|
removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
|
|
340
|
+
|
|
341
|
+
recalculatePercentages.call(this);
|
|
340
342
|
}
|
|
341
343
|
|
|
342
344
|
/**
|
|
@@ -370,19 +372,54 @@ module.exports = (() => {
|
|
|
370
372
|
}
|
|
371
373
|
|
|
372
374
|
/**
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
+
* Performs a batch update of both position quotes and forex quotes,
|
|
376
|
+
* triggering updates to position(s) and data aggregation(s).
|
|
375
377
|
*
|
|
376
378
|
* @public
|
|
377
|
-
* @param {
|
|
378
|
-
* @param {Object}
|
|
379
|
+
* @param {Array.<Object>} positionQuotes
|
|
380
|
+
* @param {Array.<Object>} forexQuotes
|
|
379
381
|
*/
|
|
380
|
-
|
|
381
|
-
assert.
|
|
382
|
-
assert.
|
|
382
|
+
setQuotes(positionQuotes, forexQuotes) {
|
|
383
|
+
assert.argumentIsArray(positionQuotes, 'positionQuotes');
|
|
384
|
+
assert.argumentIsArray(forexQuotes, 'forexQuotes');
|
|
383
385
|
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
+
if (positionQuotes.length !== 0) {
|
|
387
|
+
positionQuotes.forEach((quote) => {
|
|
388
|
+
const symbol = quote.symbol;
|
|
389
|
+
|
|
390
|
+
if (symbol) {
|
|
391
|
+
if (this._symbols.hasOwnProperty(symbol)) {
|
|
392
|
+
this._symbols[ symbol ].forEach(item => item.setQuote(quote));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (forexQuotes.length !== 0) {
|
|
399
|
+
forexQuotes.forEach((quote) => {
|
|
400
|
+
const symbol = quote.symbol;
|
|
401
|
+
|
|
402
|
+
if (symbol) {
|
|
403
|
+
const rate = Rate.fromPair(quote.lastPrice, symbol);
|
|
404
|
+
const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
405
|
+
|
|
406
|
+
if (index < 0) {
|
|
407
|
+
this._forexQuotes.push(rate);
|
|
408
|
+
} else {
|
|
409
|
+
this._forexQuotes[index] = rate;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
Object.keys(this._trees).forEach((key) => {
|
|
413
|
+
this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false)
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
recalculatePercentages.call(this);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (positionQuotes.length !== 0 || forexQuotes.length !== 0) {
|
|
422
|
+
recalculatePercentages.call(this);
|
|
386
423
|
}
|
|
387
424
|
}
|
|
388
425
|
|
|
@@ -406,30 +443,6 @@ module.exports = (() => {
|
|
|
406
443
|
return this._forexQuotes;
|
|
407
444
|
}
|
|
408
445
|
|
|
409
|
-
/**
|
|
410
|
-
* Updates the forex quote for a single currency pair; causing updates to
|
|
411
|
-
* any grouping level that contains that requires translation.
|
|
412
|
-
*
|
|
413
|
-
* @public
|
|
414
|
-
* @param {String} symbol
|
|
415
|
-
* @param {Object} quote
|
|
416
|
-
*/
|
|
417
|
-
setForexQuote(symbol, quote) {
|
|
418
|
-
assert.argumentIsRequired(symbol, 'symbol', String);
|
|
419
|
-
assert.argumentIsRequired(quote, 'quote', Object);
|
|
420
|
-
|
|
421
|
-
const rate = Rate.fromPair(quote.lastPrice, symbol);
|
|
422
|
-
const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
423
|
-
|
|
424
|
-
if (index < 0) {
|
|
425
|
-
this._forexQuotes.push(rate);
|
|
426
|
-
} else {
|
|
427
|
-
this._forexQuotes[index] = rate;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
|
|
431
|
-
}
|
|
432
|
-
|
|
433
446
|
/**
|
|
434
447
|
* Updates fundamental data for a single symbol.
|
|
435
448
|
*
|
|
@@ -578,39 +591,6 @@ module.exports = (() => {
|
|
|
578
591
|
return this.getPositions(portfolio).find(p => p.position === position) || null;
|
|
579
592
|
}
|
|
580
593
|
|
|
581
|
-
/**
|
|
582
|
-
* Pauses aggregation calculations during the processing of an action.
|
|
583
|
-
*
|
|
584
|
-
* @public
|
|
585
|
-
* @param {Function} executor
|
|
586
|
-
* @param {String=|Array.<String>=} names
|
|
587
|
-
*/
|
|
588
|
-
startTransaction(executor, names) {
|
|
589
|
-
let namesToUse;
|
|
590
|
-
|
|
591
|
-
if (is.array(names)) {
|
|
592
|
-
assert.argumentIsArray(names, 'names', String);
|
|
593
|
-
|
|
594
|
-
namesToUse = names;
|
|
595
|
-
} else {
|
|
596
|
-
assert.argumentIsOptional(names, 'names', String);
|
|
597
|
-
|
|
598
|
-
if (names) {
|
|
599
|
-
namesToUse = [ names ];
|
|
600
|
-
} else {
|
|
601
|
-
namesToUse = Object.keys(this._trees);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
assert.argumentIsRequired(executor, 'executor', Function);
|
|
606
|
-
|
|
607
|
-
namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(true), false, false));
|
|
608
|
-
|
|
609
|
-
executor(this);
|
|
610
|
-
|
|
611
|
-
namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(false), false, false));
|
|
612
|
-
}
|
|
613
|
-
|
|
614
594
|
/**
|
|
615
595
|
* Registers an observer for symbol addition (this occurs when a new position is added
|
|
616
596
|
* for a symbol that does not already exist in the container). This event only fires
|
|
@@ -739,12 +719,6 @@ module.exports = (() => {
|
|
|
739
719
|
}
|
|
740
720
|
}
|
|
741
721
|
}));
|
|
742
|
-
|
|
743
|
-
addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
|
|
744
|
-
if (!groupTree.getIsRoot()) {
|
|
745
|
-
groupTree.getParent().walk((childGroup) => childGroup.refreshMarketPercent());
|
|
746
|
-
}
|
|
747
|
-
}));
|
|
748
722
|
}
|
|
749
723
|
|
|
750
724
|
function createGroups(parentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
|
|
@@ -752,6 +726,8 @@ module.exports = (() => {
|
|
|
752
726
|
return;
|
|
753
727
|
}
|
|
754
728
|
|
|
729
|
+
const rates = this._forexQuotes;
|
|
730
|
+
|
|
755
731
|
const levelDefinition = levelDefinitions[0];
|
|
756
732
|
|
|
757
733
|
const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
|
|
@@ -759,7 +735,7 @@ module.exports = (() => {
|
|
|
759
735
|
const items = populatedObjects[key];
|
|
760
736
|
const first = items[0];
|
|
761
737
|
|
|
762
|
-
list.push(new PositionGroup(
|
|
738
|
+
list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
|
|
763
739
|
|
|
764
740
|
return list;
|
|
765
741
|
}, [ ]);
|
|
@@ -772,7 +748,7 @@ module.exports = (() => {
|
|
|
772
748
|
});
|
|
773
749
|
|
|
774
750
|
const empty = missingGroups.map((group) => {
|
|
775
|
-
return new PositionGroup(
|
|
751
|
+
return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
|
|
776
752
|
});
|
|
777
753
|
|
|
778
754
|
const compositeGroups = populatedGroups.concat(empty);
|
|
@@ -812,6 +788,9 @@ module.exports = (() => {
|
|
|
812
788
|
|
|
813
789
|
this._nodes[group.id] = childTree;
|
|
814
790
|
|
|
791
|
+
group.setParentGroup(this.getParentGroup(group));
|
|
792
|
+
group.setPortfolioGroup(this.getParentGroupForPortfolio(group));
|
|
793
|
+
|
|
815
794
|
initializeGroupObservers.call(this, childTree, treeDefinition);
|
|
816
795
|
|
|
817
796
|
createGroups.call(this, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
|
|
@@ -952,5 +931,11 @@ module.exports = (() => {
|
|
|
952
931
|
groupNodeToSever.walk(group => delete this._nodes[group.id], false, true);
|
|
953
932
|
}
|
|
954
933
|
|
|
934
|
+
function recalculatePercentages() {
|
|
935
|
+
Object.keys(this._trees).forEach((key) => {
|
|
936
|
+
this._trees[key].walk(group => group.refreshMarketPercent(), false, false);
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
|
|
955
940
|
return PositionContainer;
|
|
956
941
|
})();
|
|
@@ -33,13 +33,17 @@ module.exports = (() => {
|
|
|
33
33
|
* @param {Boolean=} aggregateCash
|
|
34
34
|
*/
|
|
35
35
|
class PositionGroup {
|
|
36
|
-
constructor(
|
|
36
|
+
constructor(definition, items, rates, currency, key, description, aggregateCash) {
|
|
37
37
|
this._id = counter++;
|
|
38
38
|
|
|
39
39
|
this._definition = definition;
|
|
40
|
-
this._container = container;
|
|
41
40
|
|
|
42
41
|
this._items = items;
|
|
42
|
+
this._rates = rates;
|
|
43
|
+
|
|
44
|
+
this._parentGroup = null;
|
|
45
|
+
this._portfolioGroup = null;
|
|
46
|
+
|
|
43
47
|
this._currency = currency || Currency.CAD;
|
|
44
48
|
this._bypassCurrencyTranslation = false;
|
|
45
49
|
|
|
@@ -50,10 +54,8 @@ module.exports = (() => {
|
|
|
50
54
|
this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
|
|
51
55
|
|
|
52
56
|
this._excluded = false;
|
|
53
|
-
this._suspended = false;
|
|
54
57
|
this._showClosedPositions = false;
|
|
55
58
|
|
|
56
|
-
this._marketPercentChangeEvent = new Event(this);
|
|
57
59
|
this._groupExcludedChangeEvent = new Event(this);
|
|
58
60
|
this._showClosedPositionsChangeEvent = new Event(this);
|
|
59
61
|
|
|
@@ -252,10 +254,6 @@ module.exports = (() => {
|
|
|
252
254
|
return this._single;
|
|
253
255
|
}
|
|
254
256
|
|
|
255
|
-
get suspended() {
|
|
256
|
-
return this._suspended;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
257
|
/**
|
|
260
258
|
* Indicates if the group should be excluded from higher-level aggregations.
|
|
261
259
|
*
|
|
@@ -266,6 +264,40 @@ module.exports = (() => {
|
|
|
266
264
|
return this._excluded;
|
|
267
265
|
}
|
|
268
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Sets the immediate parent group (allowing for calculation of relative
|
|
269
|
+
* percentages).
|
|
270
|
+
*
|
|
271
|
+
* @public
|
|
272
|
+
* @param {PortfolioGroup} group
|
|
273
|
+
*/
|
|
274
|
+
setParentGroup(group) {
|
|
275
|
+
assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
|
|
276
|
+
|
|
277
|
+
if (this._parentGroup !== null) {
|
|
278
|
+
throw new Error('The parent group has already been set.');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this._parentGroup = group;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Sets the nearest parent group for a portfolio (allowing for calculation
|
|
286
|
+
* of relative percentages).
|
|
287
|
+
*
|
|
288
|
+
* @public
|
|
289
|
+
* @param {PortfolioGroup} group
|
|
290
|
+
*/
|
|
291
|
+
setPortfolioGroup(group) {
|
|
292
|
+
assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
|
|
293
|
+
|
|
294
|
+
if (this._portfolioGroup !== null) {
|
|
295
|
+
throw new Error('The portfolio group has already been set.');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this._portfolioGroup = group;
|
|
299
|
+
}
|
|
300
|
+
|
|
269
301
|
/**
|
|
270
302
|
* Adds a new {@link PositionItem} to the group.
|
|
271
303
|
*
|
|
@@ -311,9 +343,11 @@ module.exports = (() => {
|
|
|
311
343
|
* Causes aggregated data to be recalculated using a new exchange rate.
|
|
312
344
|
*
|
|
313
345
|
* @public
|
|
314
|
-
* @param {Rate} rate
|
|
346
|
+
* @param {Array.<Rate>} rate
|
|
315
347
|
*/
|
|
316
|
-
|
|
348
|
+
setForexRates(rates) {
|
|
349
|
+
this._rates = rates;
|
|
350
|
+
|
|
317
351
|
if (!this._bypassCurrencyTranslation) {
|
|
318
352
|
this.refresh();
|
|
319
353
|
}
|
|
@@ -342,24 +376,6 @@ module.exports = (() => {
|
|
|
342
376
|
}
|
|
343
377
|
}
|
|
344
378
|
|
|
345
|
-
/**
|
|
346
|
-
* Stops (or starts) group-level aggregation calculations.
|
|
347
|
-
*
|
|
348
|
-
* @public
|
|
349
|
-
* @param {Boolean} value
|
|
350
|
-
*/
|
|
351
|
-
setSuspended(value) {
|
|
352
|
-
assert.argumentIsRequired(value, 'value', Boolean);
|
|
353
|
-
|
|
354
|
-
if (this._suspended !== value) {
|
|
355
|
-
this._suspended = value;
|
|
356
|
-
|
|
357
|
-
if (!this._suspended) {
|
|
358
|
-
this.refresh();
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
379
|
/**
|
|
364
380
|
* Updates the portfolio data. For example, a portfolio's name might change. This
|
|
365
381
|
* function only affects {@link PositionLevelType.PORTFOLIO} groups.
|
|
@@ -391,20 +407,13 @@ module.exports = (() => {
|
|
|
391
407
|
}
|
|
392
408
|
|
|
393
409
|
/**
|
|
394
|
-
* Causes all aggregated data to be recalculated
|
|
395
|
-
* been suspended).
|
|
410
|
+
* Causes all aggregated data to be recalculated.
|
|
396
411
|
*
|
|
397
412
|
* @public
|
|
398
413
|
*/
|
|
399
414
|
refresh() {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const rates = this._container.getForexQuotes();
|
|
405
|
-
|
|
406
|
-
calculateStaticData(this, rates);
|
|
407
|
-
calculatePriceData(this, rates, null, true);
|
|
415
|
+
calculateStaticData(this, this._rates);
|
|
416
|
+
calculatePriceData(this, this._rates, null, true);
|
|
408
417
|
}
|
|
409
418
|
|
|
410
419
|
/**
|
|
@@ -414,7 +423,7 @@ module.exports = (() => {
|
|
|
414
423
|
* @public
|
|
415
424
|
*/
|
|
416
425
|
refreshMarketPercent() {
|
|
417
|
-
calculateMarketPercent(this, this.
|
|
426
|
+
calculateMarketPercent(this, this._rates, this._parentGroup, this._portfolioGroup);
|
|
418
427
|
}
|
|
419
428
|
|
|
420
429
|
/**
|
|
@@ -427,17 +436,6 @@ module.exports = (() => {
|
|
|
427
436
|
return this._items.length === 0;
|
|
428
437
|
}
|
|
429
438
|
|
|
430
|
-
/**
|
|
431
|
-
* Adds an observer for change in the market percentage of the group.
|
|
432
|
-
*
|
|
433
|
-
* @public
|
|
434
|
-
* @param {Function} handler
|
|
435
|
-
* @return {Disposable}
|
|
436
|
-
*/
|
|
437
|
-
registerMarketPercentChangeHandler(handler) {
|
|
438
|
-
return this._marketPercentChangeEvent.register(handler);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
439
|
/**
|
|
442
440
|
* Adds an observer for changes to the exclusion of the group
|
|
443
441
|
* from higher level aggregations.
|
|
@@ -492,10 +490,10 @@ module.exports = (() => {
|
|
|
492
490
|
this._dataFormat.currentPrice = null;
|
|
493
491
|
}
|
|
494
492
|
|
|
495
|
-
calculatePriceData(this, this.
|
|
493
|
+
calculatePriceData(this, this._rates, sender, false);
|
|
496
494
|
});
|
|
497
495
|
|
|
498
|
-
let fundamentalBinding = item.registerFundamentalDataChangeHandler((data
|
|
496
|
+
let fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
|
|
499
497
|
if (this._single) {
|
|
500
498
|
this._dataFormat.fundamental = data;
|
|
501
499
|
} else {
|
|
@@ -546,13 +544,13 @@ module.exports = (() => {
|
|
|
546
544
|
let newsBinding = Disposable.getEmpty();
|
|
547
545
|
|
|
548
546
|
if (this._single) {
|
|
549
|
-
newsBinding = item.registerNewsExistsChangeHandler((exists
|
|
547
|
+
newsBinding = item.registerNewsExistsChangeHandler((exists) => {
|
|
550
548
|
this._dataActual.newsExists = exists;
|
|
551
549
|
this._dataFormat.newsExists = exists;
|
|
552
550
|
});
|
|
553
551
|
}
|
|
554
552
|
|
|
555
|
-
this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio
|
|
553
|
+
this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio) => {
|
|
556
554
|
const descriptionSelector = this._definition.descriptionSelector;
|
|
557
555
|
|
|
558
556
|
this._description = descriptionSelector(this._items[0]);
|
|
@@ -617,10 +615,6 @@ module.exports = (() => {
|
|
|
617
615
|
}
|
|
618
616
|
|
|
619
617
|
function calculateStaticData(group, rates) {
|
|
620
|
-
if (group.suspended) {
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
618
|
const actual = group._dataActual;
|
|
625
619
|
const format = group._dataFormat;
|
|
626
620
|
|
|
@@ -717,10 +711,6 @@ module.exports = (() => {
|
|
|
717
711
|
}
|
|
718
712
|
|
|
719
713
|
function calculatePriceData(group, rates, item, forceRefresh) {
|
|
720
|
-
if (group.suspended) {
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
714
|
const currency = group.currency;
|
|
725
715
|
|
|
726
716
|
const actual = group._dataActual;
|
|
@@ -801,28 +791,19 @@ module.exports = (() => {
|
|
|
801
791
|
|
|
802
792
|
format.total = formatCurrency(actual.total, currency);
|
|
803
793
|
format.totalNegative = actual.total.getIsNegative();
|
|
804
|
-
|
|
805
|
-
calculateMarketPercent(group, rates, false);
|
|
794
|
+
|
|
806
795
|
calculateUnrealizedPercent(group);
|
|
807
796
|
}
|
|
808
797
|
|
|
809
|
-
function calculateMarketPercent(group, rates,
|
|
810
|
-
return;
|
|
811
|
-
|
|
812
|
-
if (group.suspended) {
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
|
|
798
|
+
function calculateMarketPercent(group, rates, parentGroup, portfolioGroup) {
|
|
816
799
|
const actual = group._dataActual;
|
|
817
800
|
const format = group._dataFormat;
|
|
818
801
|
const excluded = group._excluded;
|
|
819
802
|
|
|
820
|
-
const portfolioParent = group._container.getParentGroupForPortfolio(group);
|
|
821
|
-
|
|
822
803
|
const calculatePercent = (parent) => {
|
|
823
804
|
let marketPercent;
|
|
824
805
|
|
|
825
|
-
if (parent
|
|
806
|
+
if (parent && !excluded) {
|
|
826
807
|
const parentData = parent._dataActual;
|
|
827
808
|
|
|
828
809
|
if (parentData.marketAbsolute !== null && !parentData.marketAbsolute.getIsZero()) {
|
|
@@ -845,14 +826,15 @@ module.exports = (() => {
|
|
|
845
826
|
return marketPercent;
|
|
846
827
|
};
|
|
847
828
|
|
|
848
|
-
actual.marketPercent = calculatePercent(
|
|
829
|
+
actual.marketPercent = calculatePercent(parentGroup);
|
|
849
830
|
format.marketPercent = formatPercent(actual.marketPercent, 2);
|
|
850
831
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
832
|
+
if (parentGroup === portfolioGroup) {
|
|
833
|
+
actual.marketPercentPortfolio = actual.marketPercent;
|
|
834
|
+
format.marketPercentPortfolio = format.marketPercent;
|
|
835
|
+
} else {
|
|
836
|
+
actual.marketPercentPortfolio = calculatePercent(portfolioGroup);
|
|
837
|
+
format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
|
|
856
838
|
}
|
|
857
839
|
}
|
|
858
840
|
|
package/package.json
CHANGED
package/test/SpecRunner.js
CHANGED
|
@@ -1302,6 +1302,8 @@ module.exports = (() => {
|
|
|
1302
1302
|
}, { });
|
|
1303
1303
|
|
|
1304
1304
|
Object.keys(this._portfolios).forEach(key => updateEmptyPortfolioGroups.call(this, this._portfolios[key]));
|
|
1305
|
+
|
|
1306
|
+
recalculatePercentages.call(this);
|
|
1305
1307
|
}
|
|
1306
1308
|
|
|
1307
1309
|
/**
|
|
@@ -1378,11 +1380,9 @@ module.exports = (() => {
|
|
|
1378
1380
|
assert.argumentIsRequired(portfolio, 'portfolio', Object);
|
|
1379
1381
|
assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
|
|
1380
1382
|
|
|
1381
|
-
this.
|
|
1382
|
-
getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => item.updatePortfolio(portfolio));
|
|
1383
|
+
getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => item.updatePortfolio(portfolio));
|
|
1383
1384
|
|
|
1384
|
-
|
|
1385
|
-
});
|
|
1385
|
+
updateEmptyPortfolioGroups.call(this, portfolio);
|
|
1386
1386
|
}
|
|
1387
1387
|
|
|
1388
1388
|
/**
|
|
@@ -1397,19 +1397,19 @@ module.exports = (() => {
|
|
|
1397
1397
|
assert.argumentIsRequired(portfolio, 'portfolio', Object);
|
|
1398
1398
|
assert.argumentIsRequired(portfolio.portfolio, 'portfolio.portfolio', String);
|
|
1399
1399
|
|
|
1400
|
-
this.
|
|
1401
|
-
getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => removePositionItem.call(this, item));
|
|
1400
|
+
getPositionItemsForPortfolio(this._items, portfolio.portfolio).forEach(item => removePositionItem.call(this, item));
|
|
1402
1401
|
|
|
1403
|
-
|
|
1402
|
+
delete this._portfolios[portfolio.portfolio];
|
|
1404
1403
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
});
|
|
1404
|
+
Object.keys(this._trees).forEach((key) => {
|
|
1405
|
+
this._trees[key].walk((group, groupNode) => {
|
|
1406
|
+
if (group.definition.type === PositionLevelType.PORTFOLIO && group.key === PositionLevelDefinition.getKeyForPortfolioGroup(portfolio)) {
|
|
1407
|
+
severGroupNode.call(this, groupNode);
|
|
1408
|
+
}
|
|
1409
|
+
}, true, false);
|
|
1412
1410
|
});
|
|
1411
|
+
|
|
1412
|
+
recalculatePercentages.call(this);
|
|
1413
1413
|
}
|
|
1414
1414
|
|
|
1415
1415
|
/**
|
|
@@ -1430,56 +1430,56 @@ module.exports = (() => {
|
|
|
1430
1430
|
return;
|
|
1431
1431
|
}
|
|
1432
1432
|
|
|
1433
|
-
this.
|
|
1434
|
-
const existingBarchartSymbols = this.getPositionSymbols(false);
|
|
1433
|
+
const existingBarchartSymbols = this.getPositionSymbols(false);
|
|
1435
1434
|
|
|
1436
|
-
|
|
1435
|
+
removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
|
|
1437
1436
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1437
|
+
summaries.forEach((summary) => {
|
|
1438
|
+
addSummaryCurrent(this._summariesCurrent, summary, this._currentSummaryFrame, this._currentSummaryRange);
|
|
1439
|
+
addSummaryPrevious(this._summariesPrevious, summary, this._previousSummaryFrame, this._previousSummaryRanges);
|
|
1440
|
+
});
|
|
1442
1441
|
|
|
1443
|
-
|
|
1442
|
+
const item = createPositionItem.call(this, position);
|
|
1444
1443
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1444
|
+
addBarchartSymbol(this._symbols, item);
|
|
1445
|
+
addDisplaySymbol(this._symbolsDisplay, item);
|
|
1447
1446
|
|
|
1448
|
-
|
|
1447
|
+
this._items.push(item);
|
|
1449
1448
|
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1449
|
+
const createGroupOrInjectItem = (parentTree, treeDefinition, levelDefinitions) => {
|
|
1450
|
+
if (levelDefinitions.length === 0) {
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1454
1453
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1454
|
+
const levelDefinition = levelDefinitions[0];
|
|
1455
|
+
const levelKey = levelDefinition.keySelector(item);
|
|
1457
1456
|
|
|
1458
|
-
|
|
1457
|
+
let groupTree;
|
|
1459
1458
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1459
|
+
if (parentTree.getChildren().length > 0) {
|
|
1460
|
+
groupTree = parentTree.findChild(childGroup => childGroup.key === levelKey) || null;
|
|
1461
|
+
} else {
|
|
1462
|
+
groupTree = null;
|
|
1463
|
+
}
|
|
1465
1464
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1465
|
+
if (groupTree !== null) {
|
|
1466
|
+
groupTree.getValue().addItem(item);
|
|
1468
1467
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1468
|
+
createGroupOrInjectItem(groupTree, treeDefinition, array.dropLeft(levelDefinitions));
|
|
1469
|
+
} else {
|
|
1470
|
+
createGroups.call(this, parentTree, [ item ], treeDefinition, levelDefinitions, [ ]);
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1474
1473
|
|
|
1475
|
-
|
|
1474
|
+
this._definitions.forEach(definition => createGroupOrInjectItem(this._trees[definition.name], definition, definition.definitions));
|
|
1476
1475
|
|
|
1477
|
-
|
|
1476
|
+
const addedBarchartSymbol = extractSymbolForBarchart(item.position);
|
|
1478
1477
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1478
|
+
if (addedBarchartSymbol !== null && !existingBarchartSymbols.some(existingBarchartSymbol => existingBarchartSymbol === addedBarchartSymbol)) {
|
|
1479
|
+
this._positionSymbolAddedEvent.fire(addedBarchartSymbol);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
recalculatePercentages.call(this);
|
|
1483
1483
|
}
|
|
1484
1484
|
|
|
1485
1485
|
/**
|
|
@@ -1493,6 +1493,8 @@ module.exports = (() => {
|
|
|
1493
1493
|
assert.argumentIsRequired(position.position, 'position.position', String);
|
|
1494
1494
|
|
|
1495
1495
|
removePositionItem.call(this, this._items.find((item) => item.position.position === position.position));
|
|
1496
|
+
|
|
1497
|
+
recalculatePercentages.call(this);
|
|
1496
1498
|
}
|
|
1497
1499
|
|
|
1498
1500
|
/**
|
|
@@ -1526,19 +1528,54 @@ module.exports = (() => {
|
|
|
1526
1528
|
}
|
|
1527
1529
|
|
|
1528
1530
|
/**
|
|
1529
|
-
*
|
|
1530
|
-
*
|
|
1531
|
+
* Performs a batch update of both position quotes and forex quotes,
|
|
1532
|
+
* triggering updates to position(s) and data aggregation(s).
|
|
1531
1533
|
*
|
|
1532
1534
|
* @public
|
|
1533
|
-
* @param {
|
|
1534
|
-
* @param {Object}
|
|
1535
|
+
* @param {Array.<Object>} positionQuotes
|
|
1536
|
+
* @param {Array.<Object>} forexQuotes
|
|
1535
1537
|
*/
|
|
1536
|
-
|
|
1537
|
-
assert.
|
|
1538
|
-
assert.
|
|
1538
|
+
setQuotes(positionQuotes, forexQuotes) {
|
|
1539
|
+
assert.argumentIsArray(positionQuotes, 'positionQuotes');
|
|
1540
|
+
assert.argumentIsArray(forexQuotes, 'forexQuotes');
|
|
1541
|
+
|
|
1542
|
+
if (positionQuotes.length !== 0) {
|
|
1543
|
+
positionQuotes.forEach((quote) => {
|
|
1544
|
+
const symbol = quote.symbol;
|
|
1545
|
+
|
|
1546
|
+
if (symbol) {
|
|
1547
|
+
if (this._symbols.hasOwnProperty(symbol)) {
|
|
1548
|
+
this._symbols[ symbol ].forEach(item => item.setQuote(quote));
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
if (forexQuotes.length !== 0) {
|
|
1555
|
+
forexQuotes.forEach((quote) => {
|
|
1556
|
+
const symbol = quote.symbol;
|
|
1557
|
+
|
|
1558
|
+
if (symbol) {
|
|
1559
|
+
const rate = Rate.fromPair(quote.lastPrice, symbol);
|
|
1560
|
+
const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
1561
|
+
|
|
1562
|
+
if (index < 0) {
|
|
1563
|
+
this._forexQuotes.push(rate);
|
|
1564
|
+
} else {
|
|
1565
|
+
this._forexQuotes[index] = rate;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
Object.keys(this._trees).forEach((key) => {
|
|
1569
|
+
this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false)
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
recalculatePercentages.call(this);
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1539
1576
|
|
|
1540
|
-
if (
|
|
1541
|
-
|
|
1577
|
+
if (positionQuotes.length !== 0 || forexQuotes.length !== 0) {
|
|
1578
|
+
recalculatePercentages.call(this);
|
|
1542
1579
|
}
|
|
1543
1580
|
}
|
|
1544
1581
|
|
|
@@ -1562,30 +1599,6 @@ module.exports = (() => {
|
|
|
1562
1599
|
return this._forexQuotes;
|
|
1563
1600
|
}
|
|
1564
1601
|
|
|
1565
|
-
/**
|
|
1566
|
-
* Updates the forex quote for a single currency pair; causing updates to
|
|
1567
|
-
* any grouping level that contains that requires translation.
|
|
1568
|
-
*
|
|
1569
|
-
* @public
|
|
1570
|
-
* @param {String} symbol
|
|
1571
|
-
* @param {Object} quote
|
|
1572
|
-
*/
|
|
1573
|
-
setForexQuote(symbol, quote) {
|
|
1574
|
-
assert.argumentIsRequired(symbol, 'symbol', String);
|
|
1575
|
-
assert.argumentIsRequired(quote, 'quote', Object);
|
|
1576
|
-
|
|
1577
|
-
const rate = Rate.fromPair(quote.lastPrice, symbol);
|
|
1578
|
-
const index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
1579
|
-
|
|
1580
|
-
if (index < 0) {
|
|
1581
|
-
this._forexQuotes.push(rate);
|
|
1582
|
-
} else {
|
|
1583
|
-
this._forexQuotes[index] = rate;
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
Object.keys(this._trees).forEach(key => this._trees[key].walk(group => group.setForexRate(rate), true, false));
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
1602
|
/**
|
|
1590
1603
|
* Updates fundamental data for a single symbol.
|
|
1591
1604
|
*
|
|
@@ -1734,39 +1747,6 @@ module.exports = (() => {
|
|
|
1734
1747
|
return this.getPositions(portfolio).find(p => p.position === position) || null;
|
|
1735
1748
|
}
|
|
1736
1749
|
|
|
1737
|
-
/**
|
|
1738
|
-
* Pauses aggregation calculations during the processing of an action.
|
|
1739
|
-
*
|
|
1740
|
-
* @public
|
|
1741
|
-
* @param {Function} executor
|
|
1742
|
-
* @param {String=|Array.<String>=} names
|
|
1743
|
-
*/
|
|
1744
|
-
startTransaction(executor, names) {
|
|
1745
|
-
let namesToUse;
|
|
1746
|
-
|
|
1747
|
-
if (is.array(names)) {
|
|
1748
|
-
assert.argumentIsArray(names, 'names', String);
|
|
1749
|
-
|
|
1750
|
-
namesToUse = names;
|
|
1751
|
-
} else {
|
|
1752
|
-
assert.argumentIsOptional(names, 'names', String);
|
|
1753
|
-
|
|
1754
|
-
if (names) {
|
|
1755
|
-
namesToUse = [ names ];
|
|
1756
|
-
} else {
|
|
1757
|
-
namesToUse = Object.keys(this._trees);
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
assert.argumentIsRequired(executor, 'executor', Function);
|
|
1762
|
-
|
|
1763
|
-
namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(true), false, false));
|
|
1764
|
-
|
|
1765
|
-
executor(this);
|
|
1766
|
-
|
|
1767
|
-
namesToUse.forEach((name) => this._trees[name].walk(group => group.setSuspended(false), false, false));
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
1750
|
/**
|
|
1771
1751
|
* Registers an observer for symbol addition (this occurs when a new position is added
|
|
1772
1752
|
* for a symbol that does not already exist in the container). This event only fires
|
|
@@ -1895,12 +1875,6 @@ module.exports = (() => {
|
|
|
1895
1875
|
}
|
|
1896
1876
|
}
|
|
1897
1877
|
}));
|
|
1898
|
-
|
|
1899
|
-
addGroupBinding.call(this, group, group.registerMarketPercentChangeHandler(() => {
|
|
1900
|
-
if (!groupTree.getIsRoot()) {
|
|
1901
|
-
groupTree.getParent().walk((childGroup) => childGroup.refreshMarketPercent());
|
|
1902
|
-
}
|
|
1903
|
-
}));
|
|
1904
1878
|
}
|
|
1905
1879
|
|
|
1906
1880
|
function createGroups(parentTree, items, treeDefinition, levelDefinitions, overrideRequiredGroups) {
|
|
@@ -1908,6 +1882,8 @@ module.exports = (() => {
|
|
|
1908
1882
|
return;
|
|
1909
1883
|
}
|
|
1910
1884
|
|
|
1885
|
+
const rates = this._forexQuotes;
|
|
1886
|
+
|
|
1911
1887
|
const levelDefinition = levelDefinitions[0];
|
|
1912
1888
|
|
|
1913
1889
|
const populatedObjects = array.groupBy(items, levelDefinition.keySelector);
|
|
@@ -1915,7 +1891,7 @@ module.exports = (() => {
|
|
|
1915
1891
|
const items = populatedObjects[key];
|
|
1916
1892
|
const first = items[0];
|
|
1917
1893
|
|
|
1918
|
-
list.push(new PositionGroup(
|
|
1894
|
+
list.push(new PositionGroup(levelDefinition, items, rates, levelDefinition.currencySelector(first), key, levelDefinition.descriptionSelector(first), levelDefinition.aggregateCash));
|
|
1919
1895
|
|
|
1920
1896
|
return list;
|
|
1921
1897
|
}, [ ]);
|
|
@@ -1928,7 +1904,7 @@ module.exports = (() => {
|
|
|
1928
1904
|
});
|
|
1929
1905
|
|
|
1930
1906
|
const empty = missingGroups.map((group) => {
|
|
1931
|
-
return new PositionGroup(
|
|
1907
|
+
return new PositionGroup(levelDefinition, [ ], rates, group.currency, group.key, group.description);
|
|
1932
1908
|
});
|
|
1933
1909
|
|
|
1934
1910
|
const compositeGroups = populatedGroups.concat(empty);
|
|
@@ -1968,6 +1944,9 @@ module.exports = (() => {
|
|
|
1968
1944
|
|
|
1969
1945
|
this._nodes[group.id] = childTree;
|
|
1970
1946
|
|
|
1947
|
+
group.setParentGroup(this.getParentGroup(group));
|
|
1948
|
+
group.setPortfolioGroup(this.getParentGroupForPortfolio(group));
|
|
1949
|
+
|
|
1971
1950
|
initializeGroupObservers.call(this, childTree, treeDefinition);
|
|
1972
1951
|
|
|
1973
1952
|
createGroups.call(this, childTree, group.items, treeDefinition, array.dropLeft(levelDefinitions));
|
|
@@ -2108,6 +2087,12 @@ module.exports = (() => {
|
|
|
2108
2087
|
groupNodeToSever.walk(group => delete this._nodes[group.id], false, true);
|
|
2109
2088
|
}
|
|
2110
2089
|
|
|
2090
|
+
function recalculatePercentages() {
|
|
2091
|
+
Object.keys(this._trees).forEach((key) => {
|
|
2092
|
+
this._trees[key].walk(group => group.refreshMarketPercent(), false, false);
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2111
2096
|
return PositionContainer;
|
|
2112
2097
|
})();
|
|
2113
2098
|
|
|
@@ -2147,13 +2132,17 @@ module.exports = (() => {
|
|
|
2147
2132
|
* @param {Boolean=} aggregateCash
|
|
2148
2133
|
*/
|
|
2149
2134
|
class PositionGroup {
|
|
2150
|
-
constructor(
|
|
2135
|
+
constructor(definition, items, rates, currency, key, description, aggregateCash) {
|
|
2151
2136
|
this._id = counter++;
|
|
2152
2137
|
|
|
2153
2138
|
this._definition = definition;
|
|
2154
|
-
this._container = container;
|
|
2155
2139
|
|
|
2156
2140
|
this._items = items;
|
|
2141
|
+
this._rates = rates;
|
|
2142
|
+
|
|
2143
|
+
this._parentGroup = null;
|
|
2144
|
+
this._portfolioGroup = null;
|
|
2145
|
+
|
|
2157
2146
|
this._currency = currency || Currency.CAD;
|
|
2158
2147
|
this._bypassCurrencyTranslation = false;
|
|
2159
2148
|
|
|
@@ -2164,10 +2153,8 @@ module.exports = (() => {
|
|
|
2164
2153
|
this._aggregateCash = is.boolean(aggregateCash) && aggregateCash;
|
|
2165
2154
|
|
|
2166
2155
|
this._excluded = false;
|
|
2167
|
-
this._suspended = false;
|
|
2168
2156
|
this._showClosedPositions = false;
|
|
2169
2157
|
|
|
2170
|
-
this._marketPercentChangeEvent = new Event(this);
|
|
2171
2158
|
this._groupExcludedChangeEvent = new Event(this);
|
|
2172
2159
|
this._showClosedPositionsChangeEvent = new Event(this);
|
|
2173
2160
|
|
|
@@ -2366,10 +2353,6 @@ module.exports = (() => {
|
|
|
2366
2353
|
return this._single;
|
|
2367
2354
|
}
|
|
2368
2355
|
|
|
2369
|
-
get suspended() {
|
|
2370
|
-
return this._suspended;
|
|
2371
|
-
}
|
|
2372
|
-
|
|
2373
2356
|
/**
|
|
2374
2357
|
* Indicates if the group should be excluded from higher-level aggregations.
|
|
2375
2358
|
*
|
|
@@ -2380,6 +2363,40 @@ module.exports = (() => {
|
|
|
2380
2363
|
return this._excluded;
|
|
2381
2364
|
}
|
|
2382
2365
|
|
|
2366
|
+
/**
|
|
2367
|
+
* Sets the immediate parent group (allowing for calculation of relative
|
|
2368
|
+
* percentages).
|
|
2369
|
+
*
|
|
2370
|
+
* @public
|
|
2371
|
+
* @param {PortfolioGroup} group
|
|
2372
|
+
*/
|
|
2373
|
+
setParentGroup(group) {
|
|
2374
|
+
assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
|
|
2375
|
+
|
|
2376
|
+
if (this._parentGroup !== null) {
|
|
2377
|
+
throw new Error('The parent group has already been set.');
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
this._parentGroup = group;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
/**
|
|
2384
|
+
* Sets the nearest parent group for a portfolio (allowing for calculation
|
|
2385
|
+
* of relative percentages).
|
|
2386
|
+
*
|
|
2387
|
+
* @public
|
|
2388
|
+
* @param {PortfolioGroup} group
|
|
2389
|
+
*/
|
|
2390
|
+
setPortfolioGroup(group) {
|
|
2391
|
+
assert.argumentIsOptional(group, 'group', PositionGroup, 'PositionGroup');
|
|
2392
|
+
|
|
2393
|
+
if (this._portfolioGroup !== null) {
|
|
2394
|
+
throw new Error('The portfolio group has already been set.');
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
this._portfolioGroup = group;
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2383
2400
|
/**
|
|
2384
2401
|
* Adds a new {@link PositionItem} to the group.
|
|
2385
2402
|
*
|
|
@@ -2425,9 +2442,11 @@ module.exports = (() => {
|
|
|
2425
2442
|
* Causes aggregated data to be recalculated using a new exchange rate.
|
|
2426
2443
|
*
|
|
2427
2444
|
* @public
|
|
2428
|
-
* @param {Rate} rate
|
|
2445
|
+
* @param {Array.<Rate>} rate
|
|
2429
2446
|
*/
|
|
2430
|
-
|
|
2447
|
+
setForexRates(rates) {
|
|
2448
|
+
this._rates = rates;
|
|
2449
|
+
|
|
2431
2450
|
if (!this._bypassCurrencyTranslation) {
|
|
2432
2451
|
this.refresh();
|
|
2433
2452
|
}
|
|
@@ -2456,24 +2475,6 @@ module.exports = (() => {
|
|
|
2456
2475
|
}
|
|
2457
2476
|
}
|
|
2458
2477
|
|
|
2459
|
-
/**
|
|
2460
|
-
* Stops (or starts) group-level aggregation calculations.
|
|
2461
|
-
*
|
|
2462
|
-
* @public
|
|
2463
|
-
* @param {Boolean} value
|
|
2464
|
-
*/
|
|
2465
|
-
setSuspended(value) {
|
|
2466
|
-
assert.argumentIsRequired(value, 'value', Boolean);
|
|
2467
|
-
|
|
2468
|
-
if (this._suspended !== value) {
|
|
2469
|
-
this._suspended = value;
|
|
2470
|
-
|
|
2471
|
-
if (!this._suspended) {
|
|
2472
|
-
this.refresh();
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
2478
|
/**
|
|
2478
2479
|
* Updates the portfolio data. For example, a portfolio's name might change. This
|
|
2479
2480
|
* function only affects {@link PositionLevelType.PORTFOLIO} groups.
|
|
@@ -2505,20 +2506,13 @@ module.exports = (() => {
|
|
|
2505
2506
|
}
|
|
2506
2507
|
|
|
2507
2508
|
/**
|
|
2508
|
-
* Causes all aggregated data to be recalculated
|
|
2509
|
-
* been suspended).
|
|
2509
|
+
* Causes all aggregated data to be recalculated.
|
|
2510
2510
|
*
|
|
2511
2511
|
* @public
|
|
2512
2512
|
*/
|
|
2513
2513
|
refresh() {
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2518
|
-
const rates = this._container.getForexQuotes();
|
|
2519
|
-
|
|
2520
|
-
calculateStaticData(this, rates);
|
|
2521
|
-
calculatePriceData(this, rates, null, true);
|
|
2514
|
+
calculateStaticData(this, this._rates);
|
|
2515
|
+
calculatePriceData(this, this._rates, null, true);
|
|
2522
2516
|
}
|
|
2523
2517
|
|
|
2524
2518
|
/**
|
|
@@ -2528,7 +2522,7 @@ module.exports = (() => {
|
|
|
2528
2522
|
* @public
|
|
2529
2523
|
*/
|
|
2530
2524
|
refreshMarketPercent() {
|
|
2531
|
-
calculateMarketPercent(this, this.
|
|
2525
|
+
calculateMarketPercent(this, this._rates, this._parentGroup, this._portfolioGroup);
|
|
2532
2526
|
}
|
|
2533
2527
|
|
|
2534
2528
|
/**
|
|
@@ -2541,17 +2535,6 @@ module.exports = (() => {
|
|
|
2541
2535
|
return this._items.length === 0;
|
|
2542
2536
|
}
|
|
2543
2537
|
|
|
2544
|
-
/**
|
|
2545
|
-
* Adds an observer for change in the market percentage of the group.
|
|
2546
|
-
*
|
|
2547
|
-
* @public
|
|
2548
|
-
* @param {Function} handler
|
|
2549
|
-
* @return {Disposable}
|
|
2550
|
-
*/
|
|
2551
|
-
registerMarketPercentChangeHandler(handler) {
|
|
2552
|
-
return this._marketPercentChangeEvent.register(handler);
|
|
2553
|
-
}
|
|
2554
|
-
|
|
2555
2538
|
/**
|
|
2556
2539
|
* Adds an observer for changes to the exclusion of the group
|
|
2557
2540
|
* from higher level aggregations.
|
|
@@ -2606,10 +2589,10 @@ module.exports = (() => {
|
|
|
2606
2589
|
this._dataFormat.currentPrice = null;
|
|
2607
2590
|
}
|
|
2608
2591
|
|
|
2609
|
-
calculatePriceData(this, this.
|
|
2592
|
+
calculatePriceData(this, this._rates, sender, false);
|
|
2610
2593
|
});
|
|
2611
2594
|
|
|
2612
|
-
let fundamentalBinding = item.registerFundamentalDataChangeHandler((data
|
|
2595
|
+
let fundamentalBinding = item.registerFundamentalDataChangeHandler((data) => {
|
|
2613
2596
|
if (this._single) {
|
|
2614
2597
|
this._dataFormat.fundamental = data;
|
|
2615
2598
|
} else {
|
|
@@ -2660,13 +2643,13 @@ module.exports = (() => {
|
|
|
2660
2643
|
let newsBinding = Disposable.getEmpty();
|
|
2661
2644
|
|
|
2662
2645
|
if (this._single) {
|
|
2663
|
-
newsBinding = item.registerNewsExistsChangeHandler((exists
|
|
2646
|
+
newsBinding = item.registerNewsExistsChangeHandler((exists) => {
|
|
2664
2647
|
this._dataActual.newsExists = exists;
|
|
2665
2648
|
this._dataFormat.newsExists = exists;
|
|
2666
2649
|
});
|
|
2667
2650
|
}
|
|
2668
2651
|
|
|
2669
|
-
this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio
|
|
2652
|
+
this._disposeStack.push(item.registerPortfolioChangeHandler((portfolio) => {
|
|
2670
2653
|
const descriptionSelector = this._definition.descriptionSelector;
|
|
2671
2654
|
|
|
2672
2655
|
this._description = descriptionSelector(this._items[0]);
|
|
@@ -2731,10 +2714,6 @@ module.exports = (() => {
|
|
|
2731
2714
|
}
|
|
2732
2715
|
|
|
2733
2716
|
function calculateStaticData(group, rates) {
|
|
2734
|
-
if (group.suspended) {
|
|
2735
|
-
return;
|
|
2736
|
-
}
|
|
2737
|
-
|
|
2738
2717
|
const actual = group._dataActual;
|
|
2739
2718
|
const format = group._dataFormat;
|
|
2740
2719
|
|
|
@@ -2831,10 +2810,6 @@ module.exports = (() => {
|
|
|
2831
2810
|
}
|
|
2832
2811
|
|
|
2833
2812
|
function calculatePriceData(group, rates, item, forceRefresh) {
|
|
2834
|
-
if (group.suspended) {
|
|
2835
|
-
return;
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
2813
|
const currency = group.currency;
|
|
2839
2814
|
|
|
2840
2815
|
const actual = group._dataActual;
|
|
@@ -2915,28 +2890,19 @@ module.exports = (() => {
|
|
|
2915
2890
|
|
|
2916
2891
|
format.total = formatCurrency(actual.total, currency);
|
|
2917
2892
|
format.totalNegative = actual.total.getIsNegative();
|
|
2918
|
-
|
|
2919
|
-
calculateMarketPercent(group, rates, false);
|
|
2893
|
+
|
|
2920
2894
|
calculateUnrealizedPercent(group);
|
|
2921
2895
|
}
|
|
2922
2896
|
|
|
2923
|
-
function calculateMarketPercent(group, rates,
|
|
2924
|
-
return;
|
|
2925
|
-
|
|
2926
|
-
if (group.suspended) {
|
|
2927
|
-
return;
|
|
2928
|
-
}
|
|
2929
|
-
|
|
2897
|
+
function calculateMarketPercent(group, rates, parentGroup, portfolioGroup) {
|
|
2930
2898
|
const actual = group._dataActual;
|
|
2931
2899
|
const format = group._dataFormat;
|
|
2932
2900
|
const excluded = group._excluded;
|
|
2933
2901
|
|
|
2934
|
-
const portfolioParent = group._container.getParentGroupForPortfolio(group);
|
|
2935
|
-
|
|
2936
2902
|
const calculatePercent = (parent) => {
|
|
2937
2903
|
let marketPercent;
|
|
2938
2904
|
|
|
2939
|
-
if (parent
|
|
2905
|
+
if (parent && !excluded) {
|
|
2940
2906
|
const parentData = parent._dataActual;
|
|
2941
2907
|
|
|
2942
2908
|
if (parentData.marketAbsolute !== null && !parentData.marketAbsolute.getIsZero()) {
|
|
@@ -2959,14 +2925,15 @@ module.exports = (() => {
|
|
|
2959
2925
|
return marketPercent;
|
|
2960
2926
|
};
|
|
2961
2927
|
|
|
2962
|
-
actual.marketPercent = calculatePercent(
|
|
2928
|
+
actual.marketPercent = calculatePercent(parentGroup);
|
|
2963
2929
|
format.marketPercent = formatPercent(actual.marketPercent, 2);
|
|
2964
2930
|
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2931
|
+
if (parentGroup === portfolioGroup) {
|
|
2932
|
+
actual.marketPercentPortfolio = actual.marketPercent;
|
|
2933
|
+
format.marketPercentPortfolio = format.marketPercent;
|
|
2934
|
+
} else {
|
|
2935
|
+
actual.marketPercentPortfolio = calculatePercent(portfolioGroup);
|
|
2936
|
+
format.marketPercentPortfolio = formatPercent(actual.marketPercentPortfolio, 2);
|
|
2970
2937
|
}
|
|
2971
2938
|
}
|
|
2972
2939
|
|