@fintekkers/ledger-models 0.4.19 → 0.4.20

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,18 @@
1
+ import Decimal from 'decimal.js';
2
+ export interface Fill {
3
+ /** Signed quantity: positive is a buy. */
4
+ directedQuantity: Decimal;
5
+ /** Price in the security's price convention. */
6
+ price: Decimal;
7
+ }
8
+ /**
9
+ * Quantity-weighted mean price of the buys in `fills`. Sells never change it.
10
+ *
11
+ * A buy has directedQuantity > 0; sells, short sales and zero-quantity fills
12
+ * are skipped. The result is sum(quantity * price) / sum(quantity) over the
13
+ * buys, rounded to 12 decimal places half-even with trailing zeros removed.
14
+ * Returns null when there are no buys; it never divides by zero or returns NaN.
15
+ *
16
+ * Not the same as Java's TaxLotSummary.getAverageCostBasis, which nets sells.
17
+ */
18
+ export declare function weightedAveragePurchasePrice(fills: ReadonlyArray<Fill>): Decimal | null;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.weightedAveragePurchasePrice = void 0;
7
+ const decimal_js_1 = __importDefault(require("decimal.js"));
8
+ /** Decimal places of the result. Same in Java, Python and Rust; see docs/adr/measure_semantics_and_purchase_price.md. */
9
+ const SCALE = 12;
10
+ // Local constructor: wide enough that sums and the quotient stay exact before
11
+ // the final 12-place rounding. The global Decimal settings are never touched.
12
+ const LocalDecimal = decimal_js_1.default.clone({ precision: 60, rounding: decimal_js_1.default.ROUND_HALF_EVEN });
13
+ /**
14
+ * Quantity-weighted mean price of the buys in `fills`. Sells never change it.
15
+ *
16
+ * A buy has directedQuantity > 0; sells, short sales and zero-quantity fills
17
+ * are skipped. The result is sum(quantity * price) / sum(quantity) over the
18
+ * buys, rounded to 12 decimal places half-even with trailing zeros removed.
19
+ * Returns null when there are no buys; it never divides by zero or returns NaN.
20
+ *
21
+ * Not the same as Java's TaxLotSummary.getAverageCostBasis, which nets sells.
22
+ */
23
+ function weightedAveragePurchasePrice(fills) {
24
+ let quantity = new LocalDecimal(0);
25
+ let totalCost = new LocalDecimal(0);
26
+ for (const { directedQuantity, price } of fills) {
27
+ if (!directedQuantity.greaterThan(0)) {
28
+ continue;
29
+ }
30
+ quantity = quantity.plus(directedQuantity);
31
+ totalCost = totalCost.plus(new LocalDecimal(directedQuantity).times(price));
32
+ }
33
+ if (quantity.isZero()) {
34
+ return null;
35
+ }
36
+ return totalCost.dividedBy(quantity).toDecimalPlaces(SCALE, decimal_js_1.default.ROUND_HALF_EVEN);
37
+ }
38
+ exports.weightedAveragePurchasePrice = weightedAveragePurchasePrice;
39
+ //# sourceMappingURL=cost_basis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost_basis.js","sourceRoot":"","sources":["cost_basis.ts"],"names":[],"mappings":";;;;;;AAAA,4DAAiC;AAEjC,yHAAyH;AACzH,MAAM,KAAK,GAAG,EAAE,CAAC;AAEjB,8EAA8E;AAC9E,8EAA8E;AAC9E,MAAM,YAAY,GAAG,oBAAO,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,oBAAO,CAAC,eAAe,EAAE,CAAC,CAAC;AASzF;;;;;;;;;GASG;AACH,SAAgB,4BAA4B,CAAC,KAA0B;IACnE,IAAI,QAAQ,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,SAAS,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;IAEpC,KAAK,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE;QAC7C,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE;YAClC,SAAS;SACZ;QACD,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC3C,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,gBAAgB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;KAC/E;IAED,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE;QACnB,OAAO,IAAI,CAAC;KACf;IAED,OAAO,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,KAAK,EAAE,oBAAO,CAAC,eAAe,CAAC,CAAC;AACzF,CAAC;AAjBD,oEAiBC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const decimal_js_1 = __importDefault(require("decimal.js"));
7
+ const cost_basis_1 = require("./cost_basis");
8
+ const fill = (directedQuantity, price) => ({ directedQuantity: new decimal_js_1.default(directedQuantity), price: new decimal_js_1.default(price) });
9
+ test('sells do not change the buys-only average', () => {
10
+ const result = (0, cost_basis_1.weightedAveragePurchasePrice)([fill('100', '10'), fill('100', '20'), fill('-50', '25')]);
11
+ expect(result).not.toBeNull();
12
+ expect(result.equals(15)).toBe(true);
13
+ expect(result.toFixed()).toBe('15');
14
+ });
15
+ test('no buys returns null', () => {
16
+ expect((0, cost_basis_1.weightedAveragePurchasePrice)([fill('-50', '25')])).toBeNull();
17
+ });
18
+ test('repeating result rounds to 12 places half-even', () => {
19
+ const result = (0, cost_basis_1.weightedAveragePurchasePrice)([fill('1', '1'), fill('2', '2')]);
20
+ expect(result.toFixed()).toBe('1.666666666667');
21
+ });
22
+ //# sourceMappingURL=cost_basis.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost_basis.test.js","sourceRoot":"","sources":["cost_basis.test.ts"],"names":[],"mappings":";;;;;AAAA,4DAAiC;AACjC,6CAA4D;AAE5D,MAAM,IAAI,GAAG,CAAC,gBAAwB,EAAE,KAAa,EAAE,EAAE,CACrD,CAAC,EAAE,gBAAgB,EAAE,IAAI,oBAAO,CAAC,gBAAgB,CAAC,EAAE,KAAK,EAAE,IAAI,oBAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAErF,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACnD,MAAM,MAAM,GAAG,IAAA,yCAA4B,EAAC,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAEvG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC9B,MAAM,CAAC,MAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,CAAC,MAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE;IAC9B,MAAM,CAAC,IAAA,yCAA4B,EAAC,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AACzE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;IACxD,MAAM,MAAM,GAAG,IAAA,yCAA4B,EAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAE9E,MAAM,CAAC,MAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;AACrD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ import Decimal from 'decimal.js';
2
+ import { weightedAveragePurchasePrice } from './cost_basis';
3
+
4
+ const fill = (directedQuantity: string, price: string) =>
5
+ ({ directedQuantity: new Decimal(directedQuantity), price: new Decimal(price) });
6
+
7
+ test('sells do not change the buys-only average', () => {
8
+ const result = weightedAveragePurchasePrice([fill('100', '10'), fill('100', '20'), fill('-50', '25')]);
9
+
10
+ expect(result).not.toBeNull();
11
+ expect(result!.equals(15)).toBe(true);
12
+ expect(result!.toFixed()).toBe('15');
13
+ });
14
+
15
+ test('no buys returns null', () => {
16
+ expect(weightedAveragePurchasePrice([fill('-50', '25')])).toBeNull();
17
+ });
18
+
19
+ test('repeating result rounds to 12 places half-even', () => {
20
+ const result = weightedAveragePurchasePrice([fill('1', '1'), fill('2', '2')]);
21
+
22
+ expect(result!.toFixed()).toBe('1.666666666667');
23
+ });
@@ -0,0 +1,44 @@
1
+ import Decimal from 'decimal.js';
2
+
3
+ /** Decimal places of the result. Same in Java, Python and Rust; see docs/adr/measure_semantics_and_purchase_price.md. */
4
+ const SCALE = 12;
5
+
6
+ // Local constructor: wide enough that sums and the quotient stay exact before
7
+ // the final 12-place rounding. The global Decimal settings are never touched.
8
+ const LocalDecimal = Decimal.clone({ precision: 60, rounding: Decimal.ROUND_HALF_EVEN });
9
+
10
+ export interface Fill {
11
+ /** Signed quantity: positive is a buy. */
12
+ directedQuantity: Decimal;
13
+ /** Price in the security's price convention. */
14
+ price: Decimal;
15
+ }
16
+
17
+ /**
18
+ * Quantity-weighted mean price of the buys in `fills`. Sells never change it.
19
+ *
20
+ * A buy has directedQuantity > 0; sells, short sales and zero-quantity fills
21
+ * are skipped. The result is sum(quantity * price) / sum(quantity) over the
22
+ * buys, rounded to 12 decimal places half-even with trailing zeros removed.
23
+ * Returns null when there are no buys; it never divides by zero or returns NaN.
24
+ *
25
+ * Not the same as Java's TaxLotSummary.getAverageCostBasis, which nets sells.
26
+ */
27
+ export function weightedAveragePurchasePrice(fills: ReadonlyArray<Fill>): Decimal | null {
28
+ let quantity = new LocalDecimal(0);
29
+ let totalCost = new LocalDecimal(0);
30
+
31
+ for (const { directedQuantity, price } of fills) {
32
+ if (!directedQuantity.greaterThan(0)) {
33
+ continue;
34
+ }
35
+ quantity = quantity.plus(directedQuantity);
36
+ totalCost = totalCost.plus(new LocalDecimal(directedQuantity).times(price));
37
+ }
38
+
39
+ if (quantity.isZero()) {
40
+ return null;
41
+ }
42
+
43
+ return totalCost.dividedBy(quantity).toDecimalPlaces(SCALE, Decimal.ROUND_HALF_EVEN);
44
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fintekkers/ledger-models",
3
3
  "todo": "Replace the version with build script version number",
4
- "version": "0.4.19",
4
+ "version": "0.4.20",
5
5
  "description": "ledger model protos ",
6
6
  "authors": [
7
7
  "David Doherty",