@barchart/portfolio-api-common 1.7.0 → 1.10.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.
@@ -0,0 +1,3 @@
1
+ **New Features**
2
+
3
+ * Added a new `PortfolioFailureType` for negative quantity.
@@ -0,0 +1,3 @@
1
+ **New Features**
2
+
3
+ * Added a new `PortfolioFailureType` for attempts to switch to dividend reinvestment when the position is short.
@@ -0,0 +1,4 @@
1
+ **New Features**
2
+
3
+ * Added `close` field to the `Sell` transaction schema.
4
+ * Added `close` field to the `Buy Short` transaction schema.
@@ -0,0 +1,4 @@
1
+ **Bug Fixes**
2
+
3
+ * Made `quantity` field optional for the `Sell` transaction schema.
4
+ * Made `quantity` field optional for the `Buy Short` transaction schema.
@@ -154,6 +154,17 @@ module.exports = (() => {
154
154
  return transactionCreateFailedInvalidInitialType;
155
155
  }
156
156
 
157
+ /**
158
+ * A transaction quantity cannot have a negative amount.
159
+ *
160
+ * @public
161
+ * @static
162
+ * @returns {FailureType}
163
+ */
164
+ static get TRANSACTION_CREATE_FAILED_QUANTITY_NEGATIVE() {
165
+ return transactionCreateFailedQuantityNegative;
166
+ }
167
+
157
168
  /**
158
169
  * A valuation transaction cannot have a negative rate (or amount).
159
170
  *
@@ -349,6 +360,28 @@ module.exports = (() => {
349
360
  return transactionEditFailedTypeChanged;
350
361
  }
351
362
 
363
+ /**
364
+ * Conversion of transaction type is unsupported.
365
+ *
366
+ * @public
367
+ * @static
368
+ * @returns {FailureType}
369
+ */
370
+ static get TRANSACTION_SWITCH_FAILED_INVALID_CONVERSION() {
371
+ return transactionSwitchFailedInvalidConversion;
372
+ }
373
+
374
+ /**
375
+ * Conversion of transaction type is not allowed. Dividends (or distributions)
376
+ * cannot be reinvested when the position is short.
377
+ *
378
+ * @public
379
+ * @static
380
+ * @returns {FailureType}
381
+ */
382
+ static get TRANSACTION_SWITCH_FAILED_INVALID_REINVEST() {
383
+ return transactionSwitchFailedInvalidReinvest;
384
+ }
352
385
 
353
386
  toString() {
354
387
  return '[PortfolioFailureType]';
@@ -370,6 +403,7 @@ module.exports = (() => {
370
403
  const transactionCreateFailedTypeInvalidForDirection = new FailureType('TRANSACTION_CREATE_FAILED_TYPE_INVALID_FOR_DIRECTION', 'Unable to process transaction, a {L|positionDirection.description} position would be created (i.e. you would have {L|positionDirection.sign} shares/units). {u|instrumentType.description} positions cannot have {L|positionDirection.description} positions.', false);
371
404
  const transactionCreateFailedInvalidDirectionSwitch = new FailureType('TRANSACTION_CREATE_FAILED_INVALID_DIRECTION_SWITCH', 'Unable to process transaction, the transaction would switch the position from {L|currentDirection.description} to {L|proposedDirection.description} (i.e. {L|currentDirection.sign} to {L|proposedDirection.sign} shares/units). This is not allowed. Please close the current position (i.e. zero it out) and then enter a second transaction.', false);
372
405
  const transactionCreateFailedInvalidInitialType = new FailureType('TRANSACTION_CREATE_FAILED_INVALID_INITIAL_TYPE', 'Unable to process operation because the first transaction would to be a {U|transactionType.description}, which is not allowed -- since {U|transactionType.description} transactions cannot open a position.', false);
406
+ const transactionCreateFailedQuantityNegative = new FailureType('TRANSACTION_CREATE_FAILED_QUANTITY_NEGATIVE', 'Unable to process transaction, quantity cannot be negative.');
373
407
  const transactionCreateFailedValuationNegative = new FailureType('TRANSACTION_CREATE_FAILED_VALUATION_NEGATIVE', 'Unable to process operation, valuations cannot be negative.', false);
374
408
  const transactionCreateFailedInvalidTermination = new FailureType('TRANSACTION_CREATE_FAILED_INVALID_TERMINATION', 'Unable to process operation, a {U|transactionType.description} must be the final transaction in the position history.', false);
375
409
  const transactionCreateFailedAfterTermination = new FailureType('TRANSACTION_CREATE_FAILED_AFTER_TERMINATION', 'Unable to process operation, one or more transactions would exist after {L|termination}, the final day of trading for this instrument', false);
@@ -389,7 +423,10 @@ module.exports = (() => {
389
423
  const transactionEditFailedInvalidDate = new FailureType('TRANSACTION_EDIT_FAILED_INVALID_DATE', 'Unable to edit transaction with given date.');
390
424
  const transactionEditFailedNoTransaction = new FailureType('TRANSACTION_EDIT_FAILED_NO_TRANSACTION', 'Unable to edit transaction. The referenced transaction does not exist.', false);
391
425
  const transactionEditFailedTypeReserved = new FailureType('TRANSACTION_EDIT_FAILED_TYPE_RESERVED', 'Unable to edit {U|type.description} transaction, this type of transaction is managed by the system.');
392
- const transactionEditFailedTypeChanged = new FailureType('TRANSACTION_EDIT_FAILED_TYPE_CHANGED', 'Changing a transaction type is forbidden. You must delete the existing transaction then recreate it.');
426
+ const transactionEditFailedTypeChanged = new FailureType('TRANSACTION_EDIT_FAILED_TYPE_CHANGED', 'Changing a transaction type is forbidden. You must delete the existing transaction and then create a new transaction.');
427
+
428
+ const transactionSwitchFailedInvalidConversion = new FailureType('TRANSACTION_SWITCH_FAILED_INVALID_CONVERSION', 'Unable to convert transaction from {U|existing.description} to {U|desired.description}. This conversion is not supported.');
429
+ const transactionSwitchFailedInvalidReinvest = new FailureType('TRANSACTION_SWITCH_FAILED_INVALID_REINVEST', 'Unable to convert transaction from {U|existing.description} to {U|desired.description}. Reinvestment is not supported for short positions.');
393
430
 
394
431
  return PortfolioFailureType;
395
432
  })();
@@ -247,9 +247,10 @@ module.exports = (() => {
247
247
  .withField('type', DataType.forEnum(TransactionType, 'TransactionType'))
248
248
  .withField('date', DataType.DAY)
249
249
  .withField('price', DataType.DECIMAL, true)
250
- .withField('quantity', DataType.DECIMAL)
250
+ .withField('quantity', DataType.DECIMAL, true)
251
251
  .withField('fee', DataType.DECIMAL, true)
252
252
  .withField('force', DataType.BOOLEAN, true)
253
+ .withField('close', DataType.BOOLEAN, true)
253
254
  .schema
254
255
  );
255
256
 
@@ -260,9 +261,10 @@ module.exports = (() => {
260
261
  .withField('type', DataType.forEnum(TransactionType, 'TransactionType'))
261
262
  .withField('date', DataType.DAY)
262
263
  .withField('price', DataType.DECIMAL)
263
- .withField('quantity', DataType.DECIMAL)
264
+ .withField('quantity', DataType.DECIMAL, true)
264
265
  .withField('fee', DataType.DECIMAL, true)
265
266
  .withField('force', DataType.BOOLEAN, true)
267
+ .withField('close', DataType.BOOLEAN, true)
266
268
  .schema
267
269
  );
268
270
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barchart/portfolio-api-common",
3
- "version": "1.7.0",
3
+ "version": "1.10.0",
4
4
  "description": "Common code used by the Portfolio system",
5
5
  "author": {
6
6
  "name": "Bryan Ingle",
@@ -5456,9 +5456,10 @@ module.exports = (() => {
5456
5456
  .withField('type', DataType.forEnum(TransactionType, 'TransactionType'))
5457
5457
  .withField('date', DataType.DAY)
5458
5458
  .withField('price', DataType.DECIMAL, true)
5459
- .withField('quantity', DataType.DECIMAL)
5459
+ .withField('quantity', DataType.DECIMAL, true)
5460
5460
  .withField('fee', DataType.DECIMAL, true)
5461
5461
  .withField('force', DataType.BOOLEAN, true)
5462
+ .withField('close', DataType.BOOLEAN, true)
5462
5463
  .schema
5463
5464
  );
5464
5465
 
@@ -5469,9 +5470,10 @@ module.exports = (() => {
5469
5470
  .withField('type', DataType.forEnum(TransactionType, 'TransactionType'))
5470
5471
  .withField('date', DataType.DAY)
5471
5472
  .withField('price', DataType.DECIMAL)
5472
- .withField('quantity', DataType.DECIMAL)
5473
+ .withField('quantity', DataType.DECIMAL, true)
5473
5474
  .withField('fee', DataType.DECIMAL, true)
5474
5475
  .withField('force', DataType.BOOLEAN, true)
5476
+ .withField('close', DataType.BOOLEAN, true)
5475
5477
  .schema
5476
5478
  );
5477
5479
 
@@ -5692,7 +5694,7 @@ module.exports = (() => {
5692
5694
 
5693
5695
 
5694
5696
  push(item) {
5695
- this._array.unshift(item);
5697
+ this._array.push(item);
5696
5698
 
5697
5699
  return item;
5698
5700
  }
@@ -5709,7 +5711,7 @@ module.exports = (() => {
5709
5711
  throw new Error('Stack is empty');
5710
5712
  }
5711
5713
 
5712
- return this._array.shift();
5714
+ return this._array.pop();
5713
5715
  }
5714
5716
  /**
5715
5717
  * Returns the next item in the stack (without removing it). Throws if the stack is empty.
@@ -5724,7 +5726,7 @@ module.exports = (() => {
5724
5726
  throw new Error('Stack is empty');
5725
5727
  }
5726
5728
 
5727
- return this._array[0];
5729
+ return this._array[this._array.length - 1];
5728
5730
  }
5729
5731
  /**
5730
5732
  * Returns true if the queue is empty; otherwise false.
@@ -5748,10 +5750,12 @@ module.exports = (() => {
5748
5750
  scan(action) {
5749
5751
  assert.argumentIsRequired(action, 'action', Function);
5750
5752
 
5751
- this._array.forEach(x => action(x));
5753
+ for (let i = this._array.length - 1; i >= 0; i--) {
5754
+ action(this._array[i]);
5755
+ }
5752
5756
  }
5753
5757
  /**
5754
- * Outputs an array of the stacks's items; without affecting the
5758
+ * Outputs an array of the stack's items; without affecting the
5755
5759
  * queue's internal state;
5756
5760
  *
5757
5761
  * @public
@@ -5760,7 +5764,7 @@ module.exports = (() => {
5760
5764
 
5761
5765
 
5762
5766
  toArray() {
5763
- return this._array.slice(0);
5767
+ return this._array.slice(0).reverse();
5764
5768
  }
5765
5769
 
5766
5770
  toString() {
@@ -8203,8 +8207,11 @@ const moment = require('moment-timezone');
8203
8207
 
8204
8208
  module.exports = (() => {
8205
8209
  'use strict';
8210
+
8211
+ const MILLISECONDS_PER_SECOND = 1000;
8206
8212
  /**
8207
- * A data structure encapsulates (and lazy loads) a moment (see https://momentjs.com/).
8213
+ * An immutable data structure that encapsulates (and lazy loads)
8214
+ * a moment (see https://momentjs.com/).
8208
8215
  *
8209
8216
  * @public
8210
8217
  * @param {Number} timestamp
@@ -8220,7 +8227,7 @@ module.exports = (() => {
8220
8227
  this._moment = null;
8221
8228
  }
8222
8229
  /**
8223
- * The timestamp.
8230
+ * The timestamp (milliseconds since epoch).
8224
8231
  *
8225
8232
  * @public
8226
8233
  * @returns {Number}
@@ -8249,6 +8256,34 @@ module.exports = (() => {
8249
8256
 
8250
8257
  return this._moment;
8251
8258
  }
8259
+ /**
8260
+ * Returns a new {@link Timestamp} instance shifted forward (or backward)
8261
+ * by a specific number of seconds.
8262
+ *
8263
+ * @public
8264
+ * @param {Number} milliseconds
8265
+ * @returns {Timestamp}
8266
+ */
8267
+
8268
+
8269
+ add(milliseconds) {
8270
+ assert.argumentIsRequired(milliseconds, 'seconds', Number);
8271
+ return new Timestamp(this._timestamp + milliseconds, this._timezone);
8272
+ }
8273
+ /**
8274
+ * Returns a new {@link Timestamp} instance shifted forward (or backward)
8275
+ * by a specific number of seconds.
8276
+ *
8277
+ * @public
8278
+ * @param {Number} seconds
8279
+ * @returns {Timestamp}
8280
+ */
8281
+
8282
+
8283
+ addSeconds(seconds) {
8284
+ assert.argumentIsRequired(seconds, 'seconds', Number);
8285
+ return this.add(seconds * MILLISECONDS_PER_SECOND);
8286
+ }
8252
8287
  /**
8253
8288
  * Returns the JSON representation.
8254
8289
  *
@@ -8550,7 +8585,8 @@ module.exports = (() => {
8550
8585
  },
8551
8586
 
8552
8587
  /**
8553
- * Set difference operation (using strict equality).
8588
+ * Set difference operation, returning any item in "a" that is not
8589
+ * contained in "b" (using strict equality).
8554
8590
  *
8555
8591
  * @static
8556
8592
  * @param {Array} a
@@ -8562,7 +8598,8 @@ module.exports = (() => {
8562
8598
  },
8563
8599
 
8564
8600
  /**
8565
- * Set difference operation, where the uniqueness is determined by a delegate.
8601
+ * Set difference operation, returning any item in "a" that is not
8602
+ * contained in "b" (where the uniqueness is determined by a delegate).
8566
8603
  *
8567
8604
  * @static
8568
8605
  * @param {Array} a
@@ -9465,6 +9502,7 @@ module.exports = (() => {
9465
9502
  /**
9466
9503
  * An implementation of the observer pattern.
9467
9504
  *
9505
+ * @public
9468
9506
  * @param {*} sender - The object which owns the event.
9469
9507
  * @extends {Disposable}
9470
9508
  */
@@ -9538,6 +9576,7 @@ module.exports = (() => {
9538
9576
  /**
9539
9577
  * Returns true, if no handlers are currently registered.
9540
9578
  *
9579
+ * @public
9541
9580
  * @returns {boolean}
9542
9581
  */
9543
9582
 
@@ -17584,8 +17623,8 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
17584
17623
  ranges = PositionSummaryFrame.YEARLY.getRanges(transactions);
17585
17624
  });
17586
17625
 
17587
- it('should have four ranges (assuming the current year is 2020)', () => {
17588
- expect(ranges.length).toEqual(5);
17626
+ it('should have six ranges (assuming the current year is 2021)', () => {
17627
+ expect(ranges.length).toEqual(6);
17589
17628
  });
17590
17629
 
17591
17630
  it('the first range should be from 12-31-2014 to 12-31-2015', () => {
@@ -17971,7 +18010,7 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
17971
18010
  });
17972
18011
  });
17973
18012
 
17974
- describe('and a year-to-date position summary ranges are processed for a transaction set that opened last year and has not yet closed (assuming its 2020)', () => {
18013
+ describe('and a year-to-date position summary ranges are processed for a transaction set that opened last year and has not yet closed (assuming its 2021)', () => {
17975
18014
  let ranges;
17976
18015
 
17977
18016
  beforeEach(() => {
@@ -17992,9 +18031,9 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
17992
18031
  expect(ranges.length).toEqual(1);
17993
18032
  });
17994
18033
 
17995
- it('the first range should be from 12-31-2019 to 12-31-2020', () => {
17996
- expect(ranges[0].start.format()).toEqual('2019-12-31');
17997
- expect(ranges[0].end.format()).toEqual('2020-12-31');
18034
+ it('the first range should be from 12-31-2020 to 12-31-2021', () => {
18035
+ expect(ranges[0].start.format()).toEqual('2020-12-31');
18036
+ expect(ranges[0].end.format()).toEqual('2021-12-31');
17998
18037
  });
17999
18038
  });
18000
18039
 
@@ -18004,14 +18043,14 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
18004
18043
  beforeEach(() => {
18005
18044
  const transactions = [
18006
18045
  {
18007
- date: new Day(2020, 1, 1),
18046
+ date: new Day(2021, 1, 1),
18008
18047
  snapshot: {
18009
18048
  open: new Decimal(1)
18010
18049
  },
18011
18050
  type: TransactionType.BUY
18012
18051
  },
18013
18052
  {
18014
- date: new Day(2020, 1, 2),
18053
+ date: new Day(2021, 1, 2),
18015
18054
  snapshot: {
18016
18055
  open: new Decimal(0)
18017
18056
  },
@@ -18026,9 +18065,9 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
18026
18065
  expect(ranges.length).toEqual(1);
18027
18066
  });
18028
18067
 
18029
- it('the first range should be from 12-31-2019 to 12-31-2020', () => {
18030
- expect(ranges[0].start.format()).toEqual('2019-12-31');
18031
- expect(ranges[0].end.format()).toEqual('2020-12-31');
18068
+ it('the first range should be from 12-31-2020 to 12-31-2021', () => {
18069
+ expect(ranges[0].start.format()).toEqual('2020-12-31');
18070
+ expect(ranges[0].end.format()).toEqual('2021-12-31');
18032
18071
  });
18033
18072
  });
18034
18073
 
@@ -31,8 +31,8 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
31
31
  ranges = PositionSummaryFrame.YEARLY.getRanges(transactions);
32
32
  });
33
33
 
34
- it('should have four ranges (assuming the current year is 2020)', () => {
35
- expect(ranges.length).toEqual(5);
34
+ it('should have six ranges (assuming the current year is 2021)', () => {
35
+ expect(ranges.length).toEqual(6);
36
36
  });
37
37
 
38
38
  it('the first range should be from 12-31-2014 to 12-31-2015', () => {
@@ -418,7 +418,7 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
418
418
  });
419
419
  });
420
420
 
421
- describe('and a year-to-date position summary ranges are processed for a transaction set that opened last year and has not yet closed (assuming its 2020)', () => {
421
+ describe('and a year-to-date position summary ranges are processed for a transaction set that opened last year and has not yet closed (assuming its 2021)', () => {
422
422
  let ranges;
423
423
 
424
424
  beforeEach(() => {
@@ -439,9 +439,9 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
439
439
  expect(ranges.length).toEqual(1);
440
440
  });
441
441
 
442
- it('the first range should be from 12-31-2019 to 12-31-2020', () => {
443
- expect(ranges[0].start.format()).toEqual('2019-12-31');
444
- expect(ranges[0].end.format()).toEqual('2020-12-31');
442
+ it('the first range should be from 12-31-2020 to 12-31-2021', () => {
443
+ expect(ranges[0].start.format()).toEqual('2020-12-31');
444
+ expect(ranges[0].end.format()).toEqual('2021-12-31');
445
445
  });
446
446
  });
447
447
 
@@ -451,14 +451,14 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
451
451
  beforeEach(() => {
452
452
  const transactions = [
453
453
  {
454
- date: new Day(2020, 1, 1),
454
+ date: new Day(2021, 1, 1),
455
455
  snapshot: {
456
456
  open: new Decimal(1)
457
457
  },
458
458
  type: TransactionType.BUY
459
459
  },
460
460
  {
461
- date: new Day(2020, 1, 2),
461
+ date: new Day(2021, 1, 2),
462
462
  snapshot: {
463
463
  open: new Decimal(0)
464
464
  },
@@ -473,9 +473,9 @@ describe('After the PositionSummaryFrame enumeration is initialized', () => {
473
473
  expect(ranges.length).toEqual(1);
474
474
  });
475
475
 
476
- it('the first range should be from 12-31-2019 to 12-31-2020', () => {
477
- expect(ranges[0].start.format()).toEqual('2019-12-31');
478
- expect(ranges[0].end.format()).toEqual('2020-12-31');
476
+ it('the first range should be from 12-31-2020 to 12-31-2021', () => {
477
+ expect(ranges[0].start.format()).toEqual('2020-12-31');
478
+ expect(ranges[0].end.format()).toEqual('2021-12-31');
479
479
  });
480
480
  });
481
481