@cranberry-money/shared-utils 8.1.0 → 8.2.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,79 @@
1
+ import type { AssetHolding } from '@cranberry-money/shared-types';
2
+ import { type SortDirection } from '@cranberry-money/shared-constants';
3
+ /**
4
+ * Calculates the total portfolio value based on holdings and instrument prices
5
+ *
6
+ * @param holdings - Array of asset holdings
7
+ * @param prices - Map of instrument UUIDs to their current prices
8
+ * @returns Total portfolio value
9
+ *
10
+ * @example
11
+ * const holdings = [
12
+ * { instrument: { uuid: '123' }, quantity: 100 },
13
+ * { instrument: { uuid: '456' }, quantity: 50 }
14
+ * ];
15
+ * const prices = { '123': 10.50, '456': 25.00 };
16
+ * calculatePortfolioValue(holdings, prices); // returns 2300
17
+ */
18
+ export declare const calculatePortfolioValue: (holdings: readonly AssetHolding[], prices: Record<string, number>) => number;
19
+ /**
20
+ * Calculates holding allocation percentages within a portfolio
21
+ *
22
+ * @param holdings - Array of asset holdings
23
+ * @returns Map of instrument UUIDs to their percentage allocations
24
+ *
25
+ * @example
26
+ * const holdings = [
27
+ * { instrument: { uuid: '123' }, quantity: 100 },
28
+ * { instrument: { uuid: '456' }, quantity: 100 }
29
+ * ];
30
+ * calculateHoldingAllocations(holdings); // returns { '123': 50, '456': 50 }
31
+ */
32
+ export declare const calculateHoldingAllocations: (holdings: readonly AssetHolding[]) => Record<string, number>;
33
+ /**
34
+ * Groups holdings by instrument UUID for aggregated view
35
+ *
36
+ * @param holdings - Array of asset holdings to group
37
+ * @returns Map of instrument UUIDs to their holdings arrays
38
+ *
39
+ * @example
40
+ * const holdings = [
41
+ * { instrument: { uuid: '123' }, quantity: 100 },
42
+ * { instrument: { uuid: '123' }, quantity: 50 },
43
+ * { instrument: { uuid: '456' }, quantity: 200 }
44
+ * ];
45
+ * groupHoldingsByInstrument(holdings);
46
+ * // returns { '123': [holding1, holding2], '456': [holding3] }
47
+ */
48
+ export declare const groupHoldingsByInstrument: (holdings: readonly AssetHolding[]) => Record<string, AssetHolding[]>;
49
+ /**
50
+ * Filters holdings with quantities above a threshold
51
+ *
52
+ * @param holdings - Array of asset holdings
53
+ * @param minQuantity - Minimum quantity threshold
54
+ * @returns Filtered array of holdings
55
+ *
56
+ * @example
57
+ * const holdings = [
58
+ * { quantity: 100 },
59
+ * { quantity: 50 },
60
+ * { quantity: 200 }
61
+ * ];
62
+ * filterHoldingsByMinQuantity(holdings, 75); // returns holdings with quantity >= 75
63
+ */
64
+ export declare const filterHoldingsByMinQuantity: (holdings: readonly AssetHolding[], minQuantity: number) => AssetHolding[];
65
+ /**
66
+ * Sorts holdings by various criteria
67
+ *
68
+ * @param holdings - Array of asset holdings to sort
69
+ * @param sortBy - Field to sort by
70
+ * @param direction - Sort direction (ascending or descending)
71
+ * @returns Sorted array of holdings
72
+ *
73
+ * @example
74
+ * const holdings = [...];
75
+ * sortHoldings(holdings, 'quantity', 'desc'); // Sort by quantity descending
76
+ * sortHoldings(holdings, 'symbol', 'asc'); // Sort by symbol ascending
77
+ */
78
+ export declare const sortHoldings: (holdings: readonly AssetHolding[], sortBy: "quantity" | "symbol" | "name" | "createdAt", direction?: SortDirection) => AssetHolding[];
79
+ //# sourceMappingURL=holdings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"holdings.d.ts","sourceRoot":"","sources":["../src/holdings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAiE,KAAK,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGtI;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uBAAuB,GAAI,UAAU,SAAS,YAAY,EAAE,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,MAK3G,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GAAI,UAAU,SAAS,YAAY,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAWpG,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GAAI,UAAU,SAAS,YAAY,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,CAS1G,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,2BAA2B,GAAI,UAAU,SAAS,YAAY,EAAE,EAAE,aAAa,MAAM,KAAG,YAAY,EAEhH,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,SAAS,YAAY,EAAE,EACjC,QAAQ,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,EACpD,YAAW,aAAmC,KAC7C,YAAY,EA0Cd,CAAC"}
@@ -0,0 +1,139 @@
1
+ import { DEFAULT_NUMERIC_ZERO, SORT_DIRECTION_ASC, SORT_DIRECTION_DESC } from '@cranberry-money/shared-constants';
2
+ import { sortByStringField } from './collections';
3
+ /**
4
+ * Calculates the total portfolio value based on holdings and instrument prices
5
+ *
6
+ * @param holdings - Array of asset holdings
7
+ * @param prices - Map of instrument UUIDs to their current prices
8
+ * @returns Total portfolio value
9
+ *
10
+ * @example
11
+ * const holdings = [
12
+ * { instrument: { uuid: '123' }, quantity: 100 },
13
+ * { instrument: { uuid: '456' }, quantity: 50 }
14
+ * ];
15
+ * const prices = { '123': 10.50, '456': 25.00 };
16
+ * calculatePortfolioValue(holdings, prices); // returns 2300
17
+ */
18
+ export const calculatePortfolioValue = (holdings, prices) => {
19
+ return holdings.reduce((total, holding) => {
20
+ const price = prices[holding.instrument.uuid] || DEFAULT_NUMERIC_ZERO;
21
+ return total + holding.quantity * price;
22
+ }, 0);
23
+ };
24
+ /**
25
+ * Calculates holding allocation percentages within a portfolio
26
+ *
27
+ * @param holdings - Array of asset holdings
28
+ * @returns Map of instrument UUIDs to their percentage allocations
29
+ *
30
+ * @example
31
+ * const holdings = [
32
+ * { instrument: { uuid: '123' }, quantity: 100 },
33
+ * { instrument: { uuid: '456' }, quantity: 100 }
34
+ * ];
35
+ * calculateHoldingAllocations(holdings); // returns { '123': 50, '456': 50 }
36
+ */
37
+ export const calculateHoldingAllocations = (holdings) => {
38
+ const totalQuantity = holdings.reduce((sum, holding) => sum + holding.quantity, 0);
39
+ if (totalQuantity === 0)
40
+ return {};
41
+ const allocations = {};
42
+ holdings.forEach((holding) => {
43
+ allocations[holding.instrument.uuid] = (holding.quantity / totalQuantity) * 100;
44
+ });
45
+ return allocations;
46
+ };
47
+ /**
48
+ * Groups holdings by instrument UUID for aggregated view
49
+ *
50
+ * @param holdings - Array of asset holdings to group
51
+ * @returns Map of instrument UUIDs to their holdings arrays
52
+ *
53
+ * @example
54
+ * const holdings = [
55
+ * { instrument: { uuid: '123' }, quantity: 100 },
56
+ * { instrument: { uuid: '123' }, quantity: 50 },
57
+ * { instrument: { uuid: '456' }, quantity: 200 }
58
+ * ];
59
+ * groupHoldingsByInstrument(holdings);
60
+ * // returns { '123': [holding1, holding2], '456': [holding3] }
61
+ */
62
+ export const groupHoldingsByInstrument = (holdings) => {
63
+ return holdings.reduce((groups, holding) => {
64
+ const instrumentUuid = holding.instrument.uuid;
65
+ if (!groups[instrumentUuid]) {
66
+ groups[instrumentUuid] = [];
67
+ }
68
+ groups[instrumentUuid].push(holding);
69
+ return groups;
70
+ }, {});
71
+ };
72
+ /**
73
+ * Filters holdings with quantities above a threshold
74
+ *
75
+ * @param holdings - Array of asset holdings
76
+ * @param minQuantity - Minimum quantity threshold
77
+ * @returns Filtered array of holdings
78
+ *
79
+ * @example
80
+ * const holdings = [
81
+ * { quantity: 100 },
82
+ * { quantity: 50 },
83
+ * { quantity: 200 }
84
+ * ];
85
+ * filterHoldingsByMinQuantity(holdings, 75); // returns holdings with quantity >= 75
86
+ */
87
+ export const filterHoldingsByMinQuantity = (holdings, minQuantity) => {
88
+ return holdings.filter((holding) => holding.quantity >= minQuantity);
89
+ };
90
+ /**
91
+ * Sorts holdings by various criteria
92
+ *
93
+ * @param holdings - Array of asset holdings to sort
94
+ * @param sortBy - Field to sort by
95
+ * @param direction - Sort direction (ascending or descending)
96
+ * @returns Sorted array of holdings
97
+ *
98
+ * @example
99
+ * const holdings = [...];
100
+ * sortHoldings(holdings, 'quantity', 'desc'); // Sort by quantity descending
101
+ * sortHoldings(holdings, 'symbol', 'asc'); // Sort by symbol ascending
102
+ */
103
+ export const sortHoldings = (holdings, sortBy, direction = SORT_DIRECTION_DESC) => {
104
+ // Use shared utility for string field sorting
105
+ if (sortBy === 'symbol') {
106
+ const sorted = sortByStringField([...holdings], 'instrumentSymbol');
107
+ return direction === SORT_DIRECTION_ASC ? sorted : sorted.reverse();
108
+ }
109
+ if (sortBy === 'name') {
110
+ const sorted = sortByStringField([...holdings], 'instrumentName');
111
+ return direction === SORT_DIRECTION_ASC ? sorted : sorted.reverse();
112
+ }
113
+ // Custom logic for non-string fields
114
+ return [...holdings].sort((a, b) => {
115
+ let valueA;
116
+ let valueB;
117
+ switch (sortBy) {
118
+ case 'quantity':
119
+ valueA = a.quantity;
120
+ valueB = b.quantity;
121
+ break;
122
+ case 'createdAt':
123
+ valueA = a.createdAt;
124
+ valueB = b.createdAt;
125
+ break;
126
+ default:
127
+ return 0;
128
+ }
129
+ if (typeof valueA === 'string' && typeof valueB === 'string') {
130
+ const comparison = valueA.localeCompare(valueB);
131
+ return direction === SORT_DIRECTION_ASC ? comparison : -comparison;
132
+ }
133
+ if (typeof valueA === 'number' && typeof valueB === 'number') {
134
+ const comparison = valueA - valueB;
135
+ return direction === SORT_DIRECTION_ASC ? comparison : -comparison;
136
+ }
137
+ return 0;
138
+ });
139
+ };
package/dist/index.d.ts CHANGED
@@ -20,4 +20,5 @@ export { downloadBlob, downloadTextFile, downloadJsonFile, generateTimestampedFi
20
20
  export { capitalizeFirstLetter, formatStatus, formatRiskLevel, capitalizeFirst, formatLabel, camelToTitle, } from './formatting';
21
21
  export { formatQuantityWithSuffix, formatLargeNumber, formatAsPercentage, formatWithSeparators, roundToDecimals, clampNumber, } from './numbers';
22
22
  export { validateAllocations } from './allocations';
23
+ export { calculatePortfolioValue, calculateHoldingAllocations, groupHoldingsByInstrument, filterHoldingsByMinQuantity, sortHoldings, } from './holdings';
23
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAGjF,OAAO,EACL,8BAA8B,EAC9B,qCAAqC,EACrC,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGnE,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhH,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,4BAA4B,EAC5B,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,EACvB,yBAAyB,EACzB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,QAAQ,EACR,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,mBAAmB,EACnB,UAAU,EAEV,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,qBAAqB,EAErB,0BAA0B,EAC1B,4BAA4B,EAC5B,qBAAqB,EACrB,uBAAuB,EACvB,2BAA2B,EAC3B,6BAA6B,EAC7B,2BAA2B,EAC3B,6BAA6B,GAC9B,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,QAAQ,CAAC;AAGhB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,2BAA2B,EAC3B,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,qBAAqB,EACrB,YAAY,EACZ,eAAe,EACf,eAAe,EACf,WAAW,EACX,YAAY,GACb,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,WAAW,GACZ,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -40,3 +40,5 @@ export { capitalizeFirstLetter, formatStatus, formatRiskLevel, capitalizeFirst,
40
40
  export { formatQuantityWithSuffix, formatLargeNumber, formatAsPercentage, formatWithSeparators, roundToDecimals, clampNumber, } from './numbers';
41
41
  // Allocation utilities
42
42
  export { validateAllocations } from './allocations';
43
+ // Holdings utilities
44
+ export { calculatePortfolioValue, calculateHoldingAllocations, groupHoldingsByInstrument, filterHoldingsByMinQuantity, sortHoldings, } from './holdings';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cranberry-money/shared-utils",
3
- "version": "8.1.0",
3
+ "version": "8.2.0",
4
4
  "description": "Shared utility functions for MyPortfolio platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",