@barchart/portfolio-api-common 1.27.1 → 1.28.0
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 +11 -5
- package/lib/processing/PositionContainer.js +22 -10
- package/lib/processing/PositionGroup.js +5 -3
- package/lib/processing/PositionItem.js +41 -4
- package/lib/providers/InstrumentProvider.js +13 -27
- package/lib/providers/InstrumentProviderCache.js +1 -1
- package/package.json +1 -1
|
@@ -14,6 +14,7 @@ module.exports = (() => {
|
|
|
14
14
|
* @extends {Enum}
|
|
15
15
|
* @param {String} code
|
|
16
16
|
* @param {String} description
|
|
17
|
+
* @param {Boolean} unique
|
|
17
18
|
* @param {Function} rangeCalculator
|
|
18
19
|
* @param {Function} startDateCalculator
|
|
19
20
|
* @param {Function} descriptionCalculator
|
|
@@ -91,7 +92,7 @@ module.exports = (() => {
|
|
|
91
92
|
*
|
|
92
93
|
* @public
|
|
93
94
|
* @param {Day} date
|
|
94
|
-
* @
|
|
95
|
+
* @returns {PositionSummaryRange[]}
|
|
95
96
|
*/
|
|
96
97
|
getRangesFromDate(date) {
|
|
97
98
|
assert.argumentIsRequired(date, 'date', Day, 'Day');
|
|
@@ -108,6 +109,7 @@ module.exports = (() => {
|
|
|
108
109
|
* @public
|
|
109
110
|
* @param {Day} date
|
|
110
111
|
* @param {Number} periods
|
|
112
|
+
* @returns {PositionSummaryRange[]}
|
|
111
113
|
*/
|
|
112
114
|
getPriorRanges(date, periods) {
|
|
113
115
|
assert.argumentIsRequired(date, 'date', Day, 'Day');
|
|
@@ -138,6 +140,7 @@ module.exports = (() => {
|
|
|
138
140
|
* A summary for a calendar year.
|
|
139
141
|
*
|
|
140
142
|
* @public
|
|
143
|
+
* @static
|
|
141
144
|
* @returns {PositionSummaryFrame}
|
|
142
145
|
*/
|
|
143
146
|
static get YEARLY() {
|
|
@@ -148,6 +151,7 @@ module.exports = (() => {
|
|
|
148
151
|
* A summary for a quarter.
|
|
149
152
|
*
|
|
150
153
|
* @public
|
|
154
|
+
* @static
|
|
151
155
|
* @returns {PositionSummaryFrame}
|
|
152
156
|
*/
|
|
153
157
|
static get QUARTERLY() {
|
|
@@ -158,6 +162,7 @@ module.exports = (() => {
|
|
|
158
162
|
* A summary for a calendar month.
|
|
159
163
|
*
|
|
160
164
|
* @public
|
|
165
|
+
* @static
|
|
161
166
|
* @returns {PositionSummaryFrame}
|
|
162
167
|
*/
|
|
163
168
|
static get MONTHLY() {
|
|
@@ -168,6 +173,7 @@ module.exports = (() => {
|
|
|
168
173
|
* A summary the current year (to date).
|
|
169
174
|
*
|
|
170
175
|
* @public
|
|
176
|
+
* @static
|
|
171
177
|
* @returns {PositionSummaryFrame}
|
|
172
178
|
*/
|
|
173
179
|
static get YTD() {
|
|
@@ -175,6 +181,9 @@ module.exports = (() => {
|
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
/**
|
|
184
|
+
* Returns the {@link PositionSummaryFrame} instance that matches the code
|
|
185
|
+
* provided.
|
|
186
|
+
*
|
|
178
187
|
* @public
|
|
179
188
|
* @static
|
|
180
189
|
* @param {String} code
|
|
@@ -214,10 +223,7 @@ module.exports = (() => {
|
|
|
214
223
|
*/
|
|
215
224
|
|
|
216
225
|
function getRange(start, end) {
|
|
217
|
-
return {
|
|
218
|
-
start: start,
|
|
219
|
-
end: end
|
|
220
|
-
};
|
|
226
|
+
return { start, end };
|
|
221
227
|
}
|
|
222
228
|
|
|
223
229
|
function getYearlyRanges(transactions) {
|
|
@@ -87,6 +87,8 @@ module.exports = (() => {
|
|
|
87
87
|
}, { });
|
|
88
88
|
|
|
89
89
|
if (reportFrame) {
|
|
90
|
+
this._referenceDate = reportDate;
|
|
91
|
+
|
|
90
92
|
this._currentSummaryFrame = reportFrame;
|
|
91
93
|
this._currentSummaryRange = array.last(this._currentSummaryFrame.getPriorRanges(reportDate, 0));
|
|
92
94
|
|
|
@@ -95,6 +97,8 @@ module.exports = (() => {
|
|
|
95
97
|
|
|
96
98
|
this._previousSummaryRanges.pop();
|
|
97
99
|
} else {
|
|
100
|
+
this._referenceDate = Day.getToday();
|
|
101
|
+
|
|
98
102
|
this._currentSummaryFrame = PositionSummaryFrame.YTD;
|
|
99
103
|
this._currentSummaryRange = array.first(this._currentSummaryFrame.getRecentRanges(0));
|
|
100
104
|
|
|
@@ -507,7 +511,7 @@ module.exports = (() => {
|
|
|
507
511
|
*
|
|
508
512
|
* @public
|
|
509
513
|
* @param {Object} position
|
|
510
|
-
* @
|
|
514
|
+
* @returns {Boolean}
|
|
511
515
|
*/
|
|
512
516
|
getPositionLock(position) {
|
|
513
517
|
assert.argumentIsRequired(position, 'position', Object);
|
|
@@ -542,7 +546,7 @@ module.exports = (() => {
|
|
|
542
546
|
*
|
|
543
547
|
* @public
|
|
544
548
|
* @param {Object} position
|
|
545
|
-
* @
|
|
549
|
+
* @returns {Boolean}
|
|
546
550
|
*/
|
|
547
551
|
getPositionCalculating(position) {
|
|
548
552
|
assert.argumentIsRequired(position, 'position', Object);
|
|
@@ -583,7 +587,14 @@ module.exports = (() => {
|
|
|
583
587
|
|
|
584
588
|
if (symbol) {
|
|
585
589
|
const rate = Rate.fromPair(quote.lastPrice, symbol);
|
|
586
|
-
|
|
590
|
+
|
|
591
|
+
let index = this._forexQuotes.findIndex(existing => existing.formatPair() === rate.formatPair());
|
|
592
|
+
|
|
593
|
+
if (index < 0) {
|
|
594
|
+
const inverted = rate.invert();
|
|
595
|
+
|
|
596
|
+
index = this._forexQuotes.findIndex(existing => existing.formatPair() === inverted.formatPair());
|
|
597
|
+
}
|
|
587
598
|
|
|
588
599
|
if (index < 0) {
|
|
589
600
|
this._forexQuotes.push(rate);
|
|
@@ -591,13 +602,13 @@ module.exports = (() => {
|
|
|
591
602
|
this._forexQuotes[index] = rate;
|
|
592
603
|
}
|
|
593
604
|
|
|
594
|
-
Object.keys(this._trees).forEach((key) => {
|
|
595
|
-
this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false);
|
|
596
|
-
});
|
|
597
|
-
|
|
598
605
|
recalculatePercentages.call(this);
|
|
599
606
|
}
|
|
600
607
|
});
|
|
608
|
+
|
|
609
|
+
Object.keys(this._trees).forEach((key) => {
|
|
610
|
+
this._trees[key].walk(group => group.setForexRates(this._forexQuotes), true, false);
|
|
611
|
+
});
|
|
601
612
|
}
|
|
602
613
|
|
|
603
614
|
if (positionQuotes.length !== 0 || forexQuotes.length !== 0) {
|
|
@@ -608,8 +619,9 @@ module.exports = (() => {
|
|
|
608
619
|
/**
|
|
609
620
|
* Returns current price for symbol provided.
|
|
610
621
|
*
|
|
622
|
+
* @public
|
|
611
623
|
* @param {String} symbol
|
|
612
|
-
* @
|
|
624
|
+
* @returns {null|Number}
|
|
613
625
|
*/
|
|
614
626
|
getCurrentPrice(symbol) {
|
|
615
627
|
assert.argumentIsRequired(symbol, 'symbol', String);
|
|
@@ -1096,7 +1108,7 @@ module.exports = (() => {
|
|
|
1096
1108
|
const previousSummaries = this._summariesPrevious[ position.position ] || getSummaryArray(this._previousSummaryRanges);
|
|
1097
1109
|
|
|
1098
1110
|
if (!requireCurrentSummary || currentSummary !== null) {
|
|
1099
|
-
returnRef = new PositionItem(portfolio, position, currentSummary, previousSummaries, this._reporting);
|
|
1111
|
+
returnRef = new PositionItem(portfolio, position, currentSummary, previousSummaries, this._reporting, this._referenceDate);
|
|
1100
1112
|
} else {
|
|
1101
1113
|
returnRef = null;
|
|
1102
1114
|
}
|
|
@@ -1158,4 +1170,4 @@ module.exports = (() => {
|
|
|
1158
1170
|
}
|
|
1159
1171
|
|
|
1160
1172
|
return PositionContainer;
|
|
1161
|
-
})();
|
|
1173
|
+
})();
|
|
@@ -76,6 +76,7 @@ module.exports = (() => {
|
|
|
76
76
|
this._dataFormat.invalid = false;
|
|
77
77
|
this._dataFormat.locked = false;
|
|
78
78
|
this._dataFormat.calculating = false;
|
|
79
|
+
this._dataFormat.expired = false;
|
|
79
80
|
this._dataFormat.newsExists = false;
|
|
80
81
|
this._dataFormat.quantity = null;
|
|
81
82
|
this._dataFormat.quantityPrevious = null;
|
|
@@ -292,7 +293,7 @@ module.exports = (() => {
|
|
|
292
293
|
}
|
|
293
294
|
|
|
294
295
|
/**
|
|
295
|
-
* Indicates if the group will only contain one {@link PositionItem}
|
|
296
|
+
* Indicates if the group will only contain one {@link PositionItem} — that is,
|
|
296
297
|
* indicates if the group represents a single position.
|
|
297
298
|
*
|
|
298
299
|
* @public
|
|
@@ -585,7 +586,7 @@ module.exports = (() => {
|
|
|
585
586
|
if (summary.count > 0) {
|
|
586
587
|
averageFormat = formatPercent(new Decimal(summary.total / summary.count), 2, true);
|
|
587
588
|
} else {
|
|
588
|
-
averageFormat = '
|
|
589
|
+
averageFormat = '—';
|
|
589
590
|
}
|
|
590
591
|
|
|
591
592
|
summary.averageFormat = averageFormat;
|
|
@@ -595,7 +596,7 @@ module.exports = (() => {
|
|
|
595
596
|
|
|
596
597
|
return sums;
|
|
597
598
|
}, fundamentalFields.reduce((sums, fieldName) => {
|
|
598
|
-
sums[fieldName] = { total: 0, count: 0, averageFormat: '
|
|
599
|
+
sums[fieldName] = { total: 0, count: 0, averageFormat: '—' };
|
|
599
600
|
|
|
600
601
|
return sums;
|
|
601
602
|
}, { }));
|
|
@@ -876,6 +877,7 @@ module.exports = (() => {
|
|
|
876
877
|
format.invalid = definition.type === PositionLevelType.POSITION && item.invalid;
|
|
877
878
|
format.locked = definition.type === PositionLevelType.POSITION && item.data.locked;
|
|
878
879
|
format.calculating = definition.type === PositionLevelType.POSITION && item.data.calculating;
|
|
880
|
+
format.expired = definition.type === PositionLevelType.POSITION && item.data.expired;
|
|
879
881
|
}
|
|
880
882
|
|
|
881
883
|
let portfolioType = null;
|
|
@@ -25,9 +25,10 @@ module.exports = (() => {
|
|
|
25
25
|
* @param {Object} currentSummary
|
|
26
26
|
* @param {Object[]} previousSummaries
|
|
27
27
|
* @param {Boolean} reporting
|
|
28
|
+
* @param {Day} referenceDate
|
|
28
29
|
*/
|
|
29
30
|
class PositionItem extends Disposable {
|
|
30
|
-
constructor(portfolio, position, currentSummary, previousSummaries, reporting) {
|
|
31
|
+
constructor(portfolio, position, currentSummary, previousSummaries, reporting, referenceDate) {
|
|
31
32
|
super();
|
|
32
33
|
|
|
33
34
|
this._portfolio = portfolio;
|
|
@@ -107,6 +108,7 @@ module.exports = (() => {
|
|
|
107
108
|
this._data.fundamental = { };
|
|
108
109
|
this._data.calculating = getIsCalculating(position);
|
|
109
110
|
this._data.locked = getIsLocked(position);
|
|
111
|
+
this._data.expired = getIsExpired(position, referenceDate);
|
|
110
112
|
|
|
111
113
|
this._quoteChangedEvent = new Event(this);
|
|
112
114
|
this._newsExistsChangedEvent = new Event(this);
|
|
@@ -505,6 +507,13 @@ module.exports = (() => {
|
|
|
505
507
|
|
|
506
508
|
const data = item._data;
|
|
507
509
|
|
|
510
|
+
// 2023/11/28, BRI. Futures contracts do not have their value set to zero
|
|
511
|
+
// after expiration. At expiration, the contract would have been closed
|
|
512
|
+
// (but the price would not have been zero). On the other hand, option
|
|
513
|
+
// contracts can expire worthless and we attempt to represent that here.
|
|
514
|
+
|
|
515
|
+
const worthless = data.expired && (position.instrument.type === InstrumentType.EQUITY_OPTION || position.instrument.type === InstrumentType.FUTURE_OPTION);
|
|
516
|
+
|
|
508
517
|
let market;
|
|
509
518
|
|
|
510
519
|
if (position.instrument.type === InstrumentType.OTHER) {
|
|
@@ -512,7 +521,15 @@ module.exports = (() => {
|
|
|
512
521
|
} else if (position.instrument.type === InstrumentType.CASH) {
|
|
513
522
|
market = snapshot.open;
|
|
514
523
|
} else {
|
|
515
|
-
|
|
524
|
+
let priceToUse;
|
|
525
|
+
|
|
526
|
+
if (worthless) {
|
|
527
|
+
priceToUse = Decimal.ZERO;
|
|
528
|
+
} else {
|
|
529
|
+
priceToUse = price;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
market = ValuationCalculator.calculate(position.instrument, priceToUse, snapshot.open) || snapshot.value;
|
|
516
533
|
}
|
|
517
534
|
|
|
518
535
|
let marketChange;
|
|
@@ -565,7 +582,9 @@ module.exports = (() => {
|
|
|
565
582
|
if (currentSummary && position.instrument.type !== InstrumentType.CASH) {
|
|
566
583
|
let priceToUse;
|
|
567
584
|
|
|
568
|
-
if (
|
|
585
|
+
if (worthless) {
|
|
586
|
+
priceToUse = Decimal.ZERO;
|
|
587
|
+
} else if (price) {
|
|
569
588
|
priceToUse = price;
|
|
570
589
|
} else if (data.previousPrice) {
|
|
571
590
|
priceToUse = new Decimal(data.previousPrice);
|
|
@@ -762,11 +781,29 @@ module.exports = (() => {
|
|
|
762
781
|
}
|
|
763
782
|
|
|
764
783
|
function getIsCalculating(position) {
|
|
765
|
-
assert.argumentIsRequired(position, 'position'
|
|
784
|
+
assert.argumentIsRequired(position, 'position');
|
|
766
785
|
|
|
767
786
|
return is.object(position.system) && is.object(position.system.calculate) && is.number(position.system.calculate.processors) && position.system.calculate.processors > 0;
|
|
768
787
|
}
|
|
769
788
|
|
|
789
|
+
function getIsExpired(position, day) {
|
|
790
|
+
assert.argumentIsRequired(position, 'position');
|
|
791
|
+
|
|
792
|
+
const type = position.instrument.type;
|
|
793
|
+
|
|
794
|
+
let expiration;
|
|
795
|
+
|
|
796
|
+
if (type === InstrumentType.FUTURE) {
|
|
797
|
+
expiration = position.instrument.future.expiration;
|
|
798
|
+
} else if (type === InstrumentType.FUTURE_OPTION || type === InstrumentType.EQUITY_OPTION) {
|
|
799
|
+
expiration = position.instrument.option.expiration;
|
|
800
|
+
} else {
|
|
801
|
+
expiration = null;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return expiration !== null && expiration.getIsBefore(day);
|
|
805
|
+
}
|
|
806
|
+
|
|
770
807
|
function getSnapshot(position, currentSummary, reporting) {
|
|
771
808
|
let snapshot;
|
|
772
809
|
|
|
@@ -37,7 +37,7 @@ module.exports = (() => {
|
|
|
37
37
|
.then(() => {
|
|
38
38
|
assert.argumentIsRequired(symbol, 'symbol', String);
|
|
39
39
|
|
|
40
|
-
return promise.timeout(Gateway.invoke(
|
|
40
|
+
return promise.timeout(Gateway.invoke(instrumentLookupEndpoint, { symbol }), MAXIMUM_WAIT_BEFORE_TIMEOUT_IN_MILLISECONDS, 'instrument lookup')
|
|
41
41
|
.catch((e) => {
|
|
42
42
|
let message;
|
|
43
43
|
|
|
@@ -63,32 +63,18 @@ module.exports = (() => {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.endpoint;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const instrumentLookupEndpoints = new Map();
|
|
82
|
-
|
|
83
|
-
function getInstrumentLookupEndpoint() {
|
|
84
|
-
const host = 'instruments-prod.aws.barchart.com';
|
|
85
|
-
|
|
86
|
-
if (!instrumentLookupEndpoints.has(host)) {
|
|
87
|
-
instrumentLookupEndpoints.set(host, buildInstrumentLookupEndpoint(host));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return instrumentLookupEndpoints.get(host);
|
|
91
|
-
}
|
|
66
|
+
const instrumentLookupEndpoint = EndpointBuilder.for('query-instrument', 'query instrument')
|
|
67
|
+
.withVerb(VerbType.GET)
|
|
68
|
+
.withProtocol(ProtocolType.HTTPS)
|
|
69
|
+
.withHost('instruments-prod.aws.barchart.com')
|
|
70
|
+
.withPort(443)
|
|
71
|
+
.withPathBuilder((pb) => {
|
|
72
|
+
pb.withLiteralParameter('instruments', 'instruments')
|
|
73
|
+
.withVariableParameter('symbol', 'symbol', 'symbol');
|
|
74
|
+
})
|
|
75
|
+
.withResponseInterceptor(ResponseInterceptor.DATA)
|
|
76
|
+
.withErrorInterceptor(ErrorInterceptor.GENERAL)
|
|
77
|
+
.endpoint;
|
|
92
78
|
|
|
93
79
|
return InstrumentProvider;
|
|
94
80
|
})();
|
|
@@ -13,7 +13,7 @@ module.exports = (() => {
|
|
|
13
13
|
*
|
|
14
14
|
* @public
|
|
15
15
|
* @param {InstrumentProvider} provider
|
|
16
|
-
* @param {Number} cacheDuration - The maximum number of milliseconds to cache an instrument.
|
|
16
|
+
* @param {Number=} cacheDuration - The maximum number of milliseconds to cache an instrument.
|
|
17
17
|
*/
|
|
18
18
|
class InstrumentProviderCache {
|
|
19
19
|
constructor(provider, cacheDuration) {
|