lib-fints 1.4.5 → 1.4.7
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.
|
@@ -13,7 +13,7 @@ export class TanMediaInteraction extends CustomerOrderInteraction {
|
|
|
13
13
|
}
|
|
14
14
|
const hktab = {
|
|
15
15
|
header: { segId: HKTAB.Id, segNr: 0, version: version },
|
|
16
|
-
mediaType: TanMediaType.
|
|
16
|
+
mediaType: TanMediaType.All,
|
|
17
17
|
mediaClass: TanMediaClass.All,
|
|
18
18
|
};
|
|
19
19
|
return [hktab];
|
package/dist/mt535parser.js
CHANGED
|
@@ -1,194 +1,151 @@
|
|
|
1
|
-
export var TokenType535;
|
|
2
|
-
(function (TokenType535) {
|
|
3
|
-
TokenType535["DepotValueBlock"] = "DepotValueBlock";
|
|
4
|
-
TokenType535["DepotValueCurrency"] = "DepotValueCurrency";
|
|
5
|
-
TokenType535["FinBlock"] = "FinBlock";
|
|
6
|
-
TokenType535["SecurityIdentification"] = "SecurityIdentification";
|
|
7
|
-
TokenType535["AcquisitionPrice"] = "AcquisitionPrice";
|
|
8
|
-
TokenType535["PriceBlock"] = "PriceBlock";
|
|
9
|
-
TokenType535["AmountBlock"] = "AmountBlock";
|
|
10
|
-
TokenType535["DateTimeBlock"] = "DateTimeBlock";
|
|
11
|
-
TokenType535["DateString"] = "DateString";
|
|
12
|
-
TokenType535["TimeString"] = "TimeString";
|
|
13
|
-
})(TokenType535 || (TokenType535 = {}));
|
|
14
|
-
const tokens535 = {
|
|
15
|
-
[TokenType535.DepotValueBlock]: /:16R:ADDINFO(.*?):16S:ADDINFO/ms,
|
|
16
|
-
[TokenType535.DepotValueCurrency]: /EUR(.*)/ms,
|
|
17
|
-
[TokenType535.FinBlock]: /:16R:FIN(.*?):16S:FIN/gms,
|
|
18
|
-
[TokenType535.SecurityIdentification]: /:35B:(.*?):/ms,
|
|
19
|
-
[TokenType535.AcquisitionPrice]: /:70E::HOLD\/\/\d*STK2(\d*),(\d*)\+([A-Z]{3})/ms,
|
|
20
|
-
[TokenType535.PriceBlock]: /:90([AB])::(.*?):/ms,
|
|
21
|
-
[TokenType535.AmountBlock]: /:93B::(.*?):/ms,
|
|
22
|
-
[TokenType535.DateTimeBlock]: /:98([AC])::(.*?):/ms,
|
|
23
|
-
[TokenType535.DateString]: /(\d{4})(\d{2})(\d{2})/,
|
|
24
|
-
[TokenType535.TimeString]: /^.{14}(\d{2})(\d{2})(\d{2})/ms,
|
|
25
|
-
};
|
|
26
1
|
export class Mt535Parser {
|
|
27
|
-
|
|
2
|
+
rawData;
|
|
28
3
|
constructor(rawData) {
|
|
29
|
-
|
|
30
|
-
const crlfCount = (rawData.match(/\r\n-/g) || []).length;
|
|
31
|
-
const atAtCount = (rawData.match(/@@-/g) || []).length;
|
|
32
|
-
const divider = crlfCount > atAtCount ? '\r\n' : '@@';
|
|
33
|
-
// Remove dividers that are not followed by a colon (tag indicator)
|
|
34
|
-
const regex = new RegExp(`${divider}([^:])`, 'gms');
|
|
35
|
-
this.cleanedRawData = rawData.replace(regex, '$1');
|
|
4
|
+
this.rawData = rawData;
|
|
36
5
|
}
|
|
37
6
|
parse() {
|
|
38
7
|
const result = {
|
|
39
8
|
holdings: [],
|
|
40
9
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
parseDepotValue() {
|
|
48
|
-
const addInfoMatch = this.cleanedRawData.match(tokens535[TokenType535.DepotValueBlock]);
|
|
49
|
-
if (addInfoMatch) {
|
|
50
|
-
const eurMatch = addInfoMatch[1].match(tokens535[TokenType535.DepotValueCurrency]);
|
|
51
|
-
if (eurMatch) {
|
|
52
|
-
return parseFloat(eurMatch[1].replace(',', '.'));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
parseHoldings() {
|
|
58
|
-
const holdings = [];
|
|
59
|
-
const finBlocks = this.cleanedRawData.match(tokens535[TokenType535.FinBlock]);
|
|
60
|
-
if (!finBlocks) {
|
|
61
|
-
return holdings;
|
|
62
|
-
}
|
|
63
|
-
for (const block of finBlocks) {
|
|
64
|
-
const holding = this.parseHolding(block);
|
|
65
|
-
if (holding) {
|
|
66
|
-
holdings.push(holding);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return holdings;
|
|
70
|
-
}
|
|
71
|
-
parseHolding(block) {
|
|
72
|
-
const holding = {};
|
|
73
|
-
// Parse ISIN, WKN & Name from :35B:
|
|
74
|
-
// :35B:ISIN DE0005190003/DE/519000BAY.MOTOREN WERKE AG ST
|
|
75
|
-
this.parseSecurityIdentification(block, holding);
|
|
76
|
-
// Parse acquisition price from :70E::HOLD//
|
|
77
|
-
this.parseAcquisitionPrice(block, holding);
|
|
78
|
-
// Parse current price from :90B: or :90A:
|
|
79
|
-
this.parsePrice(block, holding);
|
|
80
|
-
// Parse amount from :93B:
|
|
81
|
-
this.parseAmount(block, holding);
|
|
82
|
-
// Parse date/time from :98A: or :98C:
|
|
83
|
-
this.parseDateTime(block, holding);
|
|
84
|
-
// Calculate value if we have price and amount
|
|
85
|
-
if (holding.amount !== undefined && holding.price !== undefined) {
|
|
86
|
-
// For all currencies, value is price multiplied by amount
|
|
87
|
-
holding.value = holding.price * holding.amount;
|
|
88
|
-
}
|
|
89
|
-
return holding;
|
|
90
|
-
}
|
|
91
|
-
parseSecurityIdentification(block, holding) {
|
|
92
|
-
const match = block.match(tokens535[TokenType535.SecurityIdentification]);
|
|
93
|
-
if (match) {
|
|
94
|
-
const content = match[1];
|
|
95
|
-
// ISIN: characters 5-16 (12 chars)
|
|
96
|
-
const isinMatch = content.match(/^.{5}(.{12})/ms);
|
|
97
|
-
if (isinMatch) {
|
|
98
|
-
holding.isin = isinMatch[1];
|
|
99
|
-
}
|
|
100
|
-
// WKN: characters 21-26 (6 chars)
|
|
101
|
-
const wknMatch = content.match(/^.{21}(.{6})/ms);
|
|
102
|
-
if (wknMatch) {
|
|
103
|
-
holding.wkn = wknMatch[1];
|
|
104
|
-
}
|
|
105
|
-
// Name: everything from character 27 onwards
|
|
106
|
-
const nameMatch = content.match(/^.{27}(.*)/ms);
|
|
107
|
-
if (nameMatch) {
|
|
108
|
-
holding.name = nameMatch[1].trim();
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
parseAcquisitionPrice(block, holding) {
|
|
113
|
-
const match = block.match(tokens535[TokenType535.AcquisitionPrice]);
|
|
114
|
-
if (match) {
|
|
115
|
-
holding.acquisitionPrice = parseFloat(`${match[1]}.${match[2]}`);
|
|
116
|
-
if (!holding.currency) {
|
|
117
|
-
holding.currency = match[3];
|
|
118
|
-
}
|
|
10
|
+
const tokens = this.rawData.split(/^:(\d{2}[A-Z]?):/m);
|
|
11
|
+
const fields = [];
|
|
12
|
+
for (let i = 1; i < tokens.length; i += 2) {
|
|
13
|
+
const tag = tokens[i];
|
|
14
|
+
const value = tokens[i + 1]?.trim() || '';
|
|
15
|
+
fields.push({ tag, value });
|
|
119
16
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// Price from character 14 onwards
|
|
133
|
-
const priceMatch = content.match(/^.{14}(.*)/ms);
|
|
134
|
-
if (priceMatch) {
|
|
135
|
-
holding.price = parseFloat(priceMatch[1].replace(',', '.'));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
else if (type === 'A') {
|
|
139
|
-
holding.currency = '%';
|
|
140
|
-
// Price from character 11 onwards
|
|
141
|
-
const priceMatch = content.match(/^.{11}(.*)/ms);
|
|
142
|
-
if (priceMatch) {
|
|
143
|
-
holding.price = parseFloat(priceMatch[1].replace(',', '.')) / 100;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
parseAmount(block, holding) {
|
|
149
|
-
const match = block.match(tokens535[TokenType535.AmountBlock]);
|
|
150
|
-
if (match) {
|
|
151
|
-
// Amount from character 11 onwards
|
|
152
|
-
const amountMatch = match[1].match(/^.{11}(.*)/ms);
|
|
153
|
-
if (amountMatch) {
|
|
154
|
-
holding.amount = parseFloat(amountMatch[1].replace(',', '.'));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
parseDateTime(block, holding) {
|
|
159
|
-
const match = block.match(tokens535[TokenType535.DateTimeBlock]);
|
|
160
|
-
if (match) {
|
|
161
|
-
const type = match[1];
|
|
162
|
-
const content = match[2];
|
|
163
|
-
// Date from characters 6-13 (8 chars: YYYYMMDD)
|
|
164
|
-
const dateMatch = content.match(tokens535[TokenType535.DateString]);
|
|
165
|
-
if (dateMatch) {
|
|
166
|
-
const parsedDate = this.parseDate(dateMatch[0]);
|
|
167
|
-
if (type === 'C') {
|
|
168
|
-
// :98C: has a time component HHMMSS starting at character 14
|
|
169
|
-
const timeMatch = content.match(tokens535[TokenType535.TimeString]);
|
|
170
|
-
if (timeMatch) {
|
|
171
|
-
parsedDate.setHours(parseInt(timeMatch[1], 10), parseInt(timeMatch[2], 10), parseInt(timeMatch[3], 10));
|
|
17
|
+
const sequences = [];
|
|
18
|
+
let currentSequence = '';
|
|
19
|
+
let currentHolding = null;
|
|
20
|
+
for (let i = 0; i < fields.length; i++) {
|
|
21
|
+
const field = fields[i];
|
|
22
|
+
switch (field.tag) {
|
|
23
|
+
case '16R':
|
|
24
|
+
currentSequence = field.value;
|
|
25
|
+
sequences.push(currentSequence);
|
|
26
|
+
if (currentSequence === 'FIN') {
|
|
27
|
+
currentHolding = {};
|
|
28
|
+
result.holdings.push(currentHolding);
|
|
172
29
|
}
|
|
30
|
+
break;
|
|
31
|
+
case '16S':
|
|
32
|
+
sequences.pop();
|
|
33
|
+
currentSequence = sequences[sequences.length - 1] || '';
|
|
34
|
+
break;
|
|
35
|
+
case '19A': {
|
|
36
|
+
const match = field.value.match(/:HOL[DPS]\/\/([A-Z]{3})(-?\d+(,\d*)?)/);
|
|
37
|
+
if (match) {
|
|
38
|
+
if (currentSequence === 'ADDINFO') {
|
|
39
|
+
result.currency = match[1];
|
|
40
|
+
result.totalValue = parseFloat(match[2].replace(',', '.'));
|
|
41
|
+
}
|
|
42
|
+
else if (currentSequence === 'FIN' && currentHolding) {
|
|
43
|
+
if (currentHolding.currency !== '%') {
|
|
44
|
+
currentHolding.currency = match[1];
|
|
45
|
+
}
|
|
46
|
+
currentHolding.value = parseFloat(match[2].replace(',', '.'));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
173
50
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
51
|
+
case '35B':
|
|
52
|
+
if (currentHolding) {
|
|
53
|
+
let lastIndex = 0;
|
|
54
|
+
let match = field.value.match(/^ISIN (\w{12})/);
|
|
55
|
+
if (match) {
|
|
56
|
+
currentHolding.isin = match[1];
|
|
57
|
+
lastIndex = match[0].length;
|
|
58
|
+
}
|
|
59
|
+
match = field.value.match(/\/[A-Z]{2}\/(\w*?)$/m);
|
|
60
|
+
if (match) {
|
|
61
|
+
currentHolding.wkn = match[1].trim();
|
|
62
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
63
|
+
}
|
|
64
|
+
currentHolding.name = field.value
|
|
65
|
+
.substring(lastIndex)
|
|
66
|
+
.trim()
|
|
67
|
+
.replaceAll('\r\n', '/')
|
|
68
|
+
.trim();
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case '70E':
|
|
72
|
+
if (currentHolding) {
|
|
73
|
+
const match = field.value.match(/:HOLD\/\/\d(.*?)\+(.*?)\+(.*?)\+(.*?)\+(\d{4})(\d{2})(\d{2}).*?\d([\d,]+)\+([A-Z]{3})/s);
|
|
74
|
+
if (match) {
|
|
75
|
+
currentHolding.acquisitionDate = new Date(`${match[5]}-${match[6]}-${match[7]}T12:00`);
|
|
76
|
+
currentHolding.acquisitionPrice = parseFloat(match[8].replace(',', '.'));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
case '93B':
|
|
81
|
+
if (currentHolding) {
|
|
82
|
+
const match = field.value.match(/:AGGR\/\/UNIT\/([\d,]+)/);
|
|
83
|
+
if (match) {
|
|
84
|
+
currentHolding.amount = parseFloat(match[1].replace(',', '.'));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
case '90A':
|
|
89
|
+
if (currentHolding) {
|
|
90
|
+
const match = field.value.match(/:.*?\/\/.*?\/([\d,]+)/);
|
|
91
|
+
if (match) {
|
|
92
|
+
currentHolding.price = parseFloat(match[1].replace(',', '.')) / 100;
|
|
93
|
+
currentHolding.currency = '%';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case '90B':
|
|
98
|
+
if (currentHolding) {
|
|
99
|
+
const match = field.value.match(/:.*?\/\/.*?\/([A-Z]{3})([\d,]+)/);
|
|
100
|
+
if (match) {
|
|
101
|
+
currentHolding.price = parseFloat(match[2].replace(',', '.'));
|
|
102
|
+
currentHolding.currency = match[1];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
case '98A':
|
|
107
|
+
if (currentHolding) {
|
|
108
|
+
const matchDate = field.value.match(/(\d{4})(\d{2})(\d{2})/);
|
|
109
|
+
if (matchDate) {
|
|
110
|
+
currentHolding.date = new Date(`${matchDate[1]}-${matchDate[2]}-${matchDate[3]}T12:00`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case '98C':
|
|
115
|
+
if (currentHolding) {
|
|
116
|
+
const matchDate = field.value.match(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
|
|
117
|
+
if (matchDate) {
|
|
118
|
+
currentHolding.date = new Date(`${matchDate[1]}-${matchDate[2]}-${matchDate[3]}T${matchDate[4]}:${matchDate[5]}:${matchDate[6]}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
case '70C':
|
|
123
|
+
if (currentHolding) {
|
|
124
|
+
const cleanBlock = field.value.replace(/^:SUBB\/\//, '');
|
|
125
|
+
const lines = cleanBlock.split(/\r?\n/);
|
|
126
|
+
let name = '';
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
const trimmed = line.trim();
|
|
129
|
+
if (trimmed.startsWith('1')) {
|
|
130
|
+
name = trimmed.substring(1).trim();
|
|
131
|
+
}
|
|
132
|
+
if (trimmed.startsWith('2')) {
|
|
133
|
+
name += ` /${trimmed.substring(1).trim()}`;
|
|
134
|
+
}
|
|
135
|
+
const matchLine3 = trimmed.match(/^3\s*([A-Z0-9]{3,4})\s+(?<price>[\d.]+)(?<currency>[A-Z]{3})\s+(?<timestamp>[\d\-T:.]+)/);
|
|
136
|
+
if (matchLine3) {
|
|
137
|
+
currentHolding.price = parseFloat(matchLine3.groups?.price || '0');
|
|
138
|
+
currentHolding.currency = matchLine3.groups?.currency || '';
|
|
139
|
+
currentHolding.date = new Date(matchLine3.groups?.timestamp || '');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!currentHolding.name && name) {
|
|
143
|
+
currentHolding.name = name;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
179
147
|
}
|
|
180
148
|
}
|
|
181
|
-
|
|
182
|
-
parseDate(dateString) {
|
|
183
|
-
const match = dateString.match(tokens535[TokenType535.DateString]);
|
|
184
|
-
if (!match) {
|
|
185
|
-
throw new Error(`Invalid date format: ${dateString}`);
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
return new Date(parseInt(match[1], 10), parseInt(match[2], 10) - 1, parseInt(match[3], 10));
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
throw new Error(`Invalid date: ${dateString}`, { cause: error });
|
|
192
|
-
}
|
|
149
|
+
return result;
|
|
193
150
|
}
|
|
194
151
|
}
|
|
@@ -1,155 +1,245 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { Mt535Parser } from '../mt535parser.js';
|
|
3
3
|
describe('Mt535Parser', () => {
|
|
4
|
-
it('
|
|
5
|
-
const
|
|
6
|
-
'EUR125000,50\r\n' +
|
|
7
|
-
':16S:ADDINFO\r\n' +
|
|
8
|
-
// Holding 1 (BMW)
|
|
9
|
-
':16R:FIN\r\n' +
|
|
10
|
-
':35B:ISIN DE0005190003/DE/519000BAY.MOTOREN WERKE AG ST:\r\n' +
|
|
11
|
-
':70E::HOLD//100STK275,30+EUR\r\n' +
|
|
12
|
-
':90B::PREFIX_11_CEUR100,50:\r\n' +
|
|
13
|
-
':93B::QTY/AVALBLX100,:\r\n' +
|
|
14
|
-
':98A::SETT//DTE20231101:\r\n' +
|
|
15
|
-
':16S:FIN\r\n' +
|
|
16
|
-
// Holding 2 (Apple)
|
|
17
|
-
':16R:FIN\r\n' +
|
|
18
|
-
':35B:ISIN US0378331005/US/037833Apple Inc. Common Stock:\r\n' +
|
|
19
|
-
':70E::HOLD//50STK2150,75+USD\r\n' +
|
|
20
|
-
':90A::PRIC/RATE_X85,50:\r\n' +
|
|
21
|
-
':93B::QTY/AVALBLX50,:\r\n' +
|
|
22
|
-
':98C::TRADTE20231101143000:\r\n' +
|
|
23
|
-
':16S:FIN\r\n';
|
|
24
|
-
const parser = new Mt535Parser(input);
|
|
4
|
+
it('handles empty input', () => {
|
|
5
|
+
const parser = new Mt535Parser('');
|
|
25
6
|
const statement = parser.parse();
|
|
26
|
-
|
|
27
|
-
expect(statement.
|
|
28
|
-
expect(statement.holdings).toHaveLength(2);
|
|
29
|
-
// Test first holding (BMW)
|
|
30
|
-
const bmwHolding = statement.holdings[0];
|
|
31
|
-
expect(bmwHolding.isin).toBe('DE0005190003');
|
|
32
|
-
expect(bmwHolding.wkn).toBe('519000');
|
|
33
|
-
expect(bmwHolding.name).toBe('BAY.MOTOREN WERKE AG ST');
|
|
34
|
-
expect(bmwHolding.acquisitionPrice).toBe(75.3);
|
|
35
|
-
expect(bmwHolding.price).toBe(100.5);
|
|
36
|
-
expect(bmwHolding.currency).toBe('EUR');
|
|
37
|
-
expect(bmwHolding.amount).toBe(100);
|
|
38
|
-
expect(bmwHolding.value).toBe(10050);
|
|
39
|
-
expect(bmwHolding.date).toEqual(new Date(2023, 10, 1, 0, 0, 0));
|
|
40
|
-
// Test second holding (Apple)
|
|
41
|
-
const appleHolding = statement.holdings[1];
|
|
42
|
-
expect(appleHolding.isin).toBe('US0378331005');
|
|
43
|
-
expect(appleHolding.wkn).toBe('037833');
|
|
44
|
-
expect(appleHolding.name).toBe('Apple Inc. Common Stock');
|
|
45
|
-
expect(appleHolding.acquisitionPrice).toBe(150.75);
|
|
46
|
-
expect(appleHolding.price).toBe(0.855);
|
|
47
|
-
expect(appleHolding.currency).toBe('%');
|
|
48
|
-
expect(appleHolding.amount).toBe(50);
|
|
49
|
-
expect(appleHolding.value).toBe(42.75);
|
|
50
|
-
expect(appleHolding.date).toEqual(new Date(2023, 10, 1, 14, 30, 0));
|
|
7
|
+
expect(statement.totalValue).toBeUndefined();
|
|
8
|
+
expect(statement.holdings).toHaveLength(0);
|
|
51
9
|
});
|
|
52
|
-
it('parses
|
|
53
|
-
const input = ':16R:
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
':
|
|
58
|
-
':
|
|
59
|
-
':
|
|
60
|
-
':
|
|
61
|
-
':98A::REGD//DTE20231215:@@' +
|
|
62
|
-
':16S:FIN@@';
|
|
10
|
+
it('parses holding with :90A:: percentage price (bonds)', () => {
|
|
11
|
+
const input = ':16R:FIN\r\n' +
|
|
12
|
+
':35B:ISIN DE0001102580\r\n' +
|
|
13
|
+
'/DE/110258\r\n' +
|
|
14
|
+
'BUNDESREP.DEUTSCHLAND ANL.V.2021\r\n' +
|
|
15
|
+
':90A::MRKT//PRCT/98,50\r\n' +
|
|
16
|
+
':93B::AGGR//UNIT/10000,\r\n' +
|
|
17
|
+
':19A::HOLD//EUR9850,00\r\n' +
|
|
18
|
+
':16S:FIN\r\n';
|
|
63
19
|
const parser = new Mt535Parser(input);
|
|
64
20
|
const statement = parser.parse();
|
|
65
|
-
expect(statement.totalValue).toBe(50000.25);
|
|
66
21
|
expect(statement.holdings).toHaveLength(1);
|
|
67
22
|
const holding = statement.holdings[0];
|
|
68
|
-
expect(holding.isin).toBe('
|
|
69
|
-
expect(holding.wkn).toBe('
|
|
70
|
-
expect(holding.
|
|
71
|
-
expect(holding.
|
|
72
|
-
expect(holding.
|
|
73
|
-
expect(holding.
|
|
74
|
-
expect(holding.amount).toBe(25);
|
|
75
|
-
expect(holding.value).toBe(2893.75);
|
|
76
|
-
expect(holding.date).toEqual(new Date(2023, 11, 15));
|
|
23
|
+
expect(holding.isin).toBe('DE0001102580');
|
|
24
|
+
expect(holding.wkn).toBe('110258');
|
|
25
|
+
expect(holding.currency).toBe('%');
|
|
26
|
+
expect(holding.price).toBe(0.985);
|
|
27
|
+
expect(holding.amount).toBe(10000);
|
|
28
|
+
expect(holding.value).toBe(9850);
|
|
77
29
|
});
|
|
78
|
-
it('
|
|
79
|
-
const
|
|
30
|
+
it('parses holding with :90B:: absolute price', () => {
|
|
31
|
+
const input = ':16R:FIN\r\n' +
|
|
32
|
+
':35B:ISIN DE0005190003\r\n' +
|
|
33
|
+
'/DE/519000\r\n' +
|
|
34
|
+
'BAY.MOTOREN WERKE AG ST\r\n' +
|
|
35
|
+
':90B::MRKT//ACTU/EUR89,50\r\n' +
|
|
36
|
+
':93B::AGGR//UNIT/50,\r\n' +
|
|
37
|
+
':19A::HOLD//EUR4475,00\r\n' +
|
|
38
|
+
':16S:FIN\r\n';
|
|
39
|
+
const parser = new Mt535Parser(input);
|
|
80
40
|
const statement = parser.parse();
|
|
81
|
-
|
|
82
|
-
expect(
|
|
41
|
+
const holding = statement.holdings[0];
|
|
42
|
+
expect(holding.isin).toBe('DE0005190003');
|
|
43
|
+
expect(holding.currency).toBe('EUR');
|
|
44
|
+
expect(holding.price).toBe(89.5);
|
|
45
|
+
expect(holding.amount).toBe(50);
|
|
46
|
+
expect(holding.value).toBe(4475);
|
|
83
47
|
});
|
|
84
|
-
it('
|
|
48
|
+
it('parses 98A date-only field', () => {
|
|
85
49
|
const input = ':16R:FIN\r\n' +
|
|
86
|
-
':35B:ISIN
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
':98A::SETT//
|
|
50
|
+
':35B:ISIN DE0007164600\r\n' +
|
|
51
|
+
'/DE/716460\r\n' +
|
|
52
|
+
'SAP SE\r\n' +
|
|
53
|
+
':98A::SETT//20231215\r\n' +
|
|
90
54
|
':16S:FIN\r\n';
|
|
91
55
|
const parser = new Mt535Parser(input);
|
|
92
56
|
const statement = parser.parse();
|
|
93
|
-
|
|
94
|
-
expect(
|
|
95
|
-
expect(statement.holdings[0].isin).toBe('DE0005190003');
|
|
96
|
-
expect(statement.holdings[0].name).toBe('SOME OTHER AG ST');
|
|
97
|
-
expect(statement.holdings[0].price).toBe(100.5);
|
|
98
|
-
expect(statement.holdings[0].amount).toBe(100);
|
|
57
|
+
const holding = statement.holdings[0];
|
|
58
|
+
expect(holding.date).toEqual(new Date('2023-12-15T12:00'));
|
|
99
59
|
});
|
|
100
|
-
it('
|
|
101
|
-
const input = ':16R:
|
|
60
|
+
it('parses 98C date-time field', () => {
|
|
61
|
+
const input = ':16R:FIN\r\n' +
|
|
62
|
+
':35B:ISIN DE0007164600\r\n' +
|
|
63
|
+
'/DE/716460\r\n' +
|
|
64
|
+
'SAP SE\r\n' +
|
|
65
|
+
':98C::PRIC//20240115093045\r\n' +
|
|
66
|
+
':16S:FIN\r\n';
|
|
102
67
|
const parser = new Mt535Parser(input);
|
|
103
68
|
const statement = parser.parse();
|
|
104
|
-
|
|
105
|
-
expect(
|
|
69
|
+
const holding = statement.holdings[0];
|
|
70
|
+
expect(holding.date).toEqual(new Date(2024, 0, 15, 9, 30, 45));
|
|
106
71
|
});
|
|
107
|
-
it('
|
|
108
|
-
const input = ':16R:FIN\r\n' + ':35B:ISIN FR0000120271
|
|
72
|
+
it('parses holding with minimal data (only ISIN)', () => {
|
|
73
|
+
const input = ':16R:FIN\r\n' + ':35B:ISIN FR0000120271\r\n' + 'AIR LIQUIDE SA\r\n' + ':16S:FIN\r\n';
|
|
109
74
|
const parser = new Mt535Parser(input);
|
|
110
75
|
const statement = parser.parse();
|
|
111
76
|
expect(statement.holdings).toHaveLength(1);
|
|
112
77
|
const holding = statement.holdings[0];
|
|
113
78
|
expect(holding.isin).toBe('FR0000120271');
|
|
114
|
-
expect(holding.wkn).toBe('120271');
|
|
115
79
|
expect(holding.name).toBe('AIR LIQUIDE SA');
|
|
116
80
|
expect(holding.price).toBeUndefined();
|
|
117
81
|
expect(holding.amount).toBeUndefined();
|
|
118
|
-
expect(holding.value).toBeUndefined();
|
|
119
|
-
expect(holding.date).toBeUndefined();
|
|
120
82
|
});
|
|
121
|
-
it('
|
|
122
|
-
const input = ':16R:
|
|
123
|
-
':
|
|
124
|
-
':
|
|
125
|
-
':
|
|
126
|
-
':
|
|
83
|
+
it('parses DKB statement correctly', () => {
|
|
84
|
+
const input = ':16R:GENL\r\n' +
|
|
85
|
+
':28E:1/ONLY\r\n' +
|
|
86
|
+
':20C::SEME//NONREF\r\n' +
|
|
87
|
+
':23G:NEWM\r\n' +
|
|
88
|
+
':98C::PREP//20260110153747\r\n' +
|
|
89
|
+
':98A::STAT//20260110\r\n' +
|
|
90
|
+
':22F::STTY//CUST\r\n' +
|
|
91
|
+
':97A::SAFE//12030000/123456789\r\n' +
|
|
92
|
+
':17B::ACTI//Y\r\n' +
|
|
93
|
+
':16S:GENL\r\n' +
|
|
94
|
+
':16R:FIN\r\n' +
|
|
95
|
+
':35B:ISIN LU0950674332\r\n' +
|
|
96
|
+
'/DE/A1W3CQ\r\n' +
|
|
97
|
+
'UBS MSCI WORLD SOC.RES. NAMENS-ANTE\r\n' +
|
|
98
|
+
'ILE A ACC. USD O.N.\r\n' +
|
|
99
|
+
':90B::MRKT//ACTU/EUR33,22\r\n' +
|
|
100
|
+
':98C::PRIC//20260109210244\r\n' +
|
|
101
|
+
':93B::AGGR//UNIT/652,\r\n' +
|
|
102
|
+
':16R:SUBBAL\r\n' +
|
|
103
|
+
':93C::TAVI//UNIT/AVAI/652,\r\n' +
|
|
104
|
+
':16S:SUBBAL\r\n' +
|
|
105
|
+
':19A::HOLD//EUR18669,64\r\n' +
|
|
106
|
+
':70E::HOLD//1STK++++20220902\r\n' +
|
|
107
|
+
'221,2012438+EUR\r\n' +
|
|
108
|
+
':16S:FIN\r\n' +
|
|
109
|
+
':16R:ADDINFO\r\n' +
|
|
110
|
+
':19A::HOLP//EUR18669,64\r\n' +
|
|
111
|
+
':16S:ADDINFO\r\n';
|
|
127
112
|
const parser = new Mt535Parser(input);
|
|
128
113
|
const statement = parser.parse();
|
|
114
|
+
expect(statement.totalValue).toBe(18669.64);
|
|
115
|
+
expect(statement.currency).toBe('EUR');
|
|
116
|
+
expect(statement.holdings).toHaveLength(1);
|
|
129
117
|
const holding = statement.holdings[0];
|
|
130
|
-
expect(holding.
|
|
131
|
-
expect(holding.
|
|
132
|
-
expect(holding.
|
|
133
|
-
expect(holding.
|
|
118
|
+
expect(holding.isin).toBe('LU0950674332');
|
|
119
|
+
expect(holding.wkn).toBe('A1W3CQ');
|
|
120
|
+
expect(holding.name).toBe('UBS MSCI WORLD SOC.RES. NAMENS-ANTE/ILE A ACC. USD O.N.');
|
|
121
|
+
expect(holding.acquisitionDate).toEqual(new Date('2022-09-02T12:00'));
|
|
122
|
+
expect(holding.acquisitionPrice).toBe(21.2012438);
|
|
123
|
+
expect(holding.amount).toBe(652);
|
|
124
|
+
expect(holding.price).toBe(33.22);
|
|
125
|
+
expect(holding.currency).toBe('EUR');
|
|
126
|
+
expect(holding.value).toBe(18669.64);
|
|
127
|
+
expect(holding.date).toEqual(new Date(2026, 0, 9, 21, 2, 44));
|
|
134
128
|
});
|
|
135
|
-
it('
|
|
136
|
-
const input = ':16R:
|
|
137
|
-
':
|
|
138
|
-
':
|
|
139
|
-
':
|
|
129
|
+
it('parses Baader statement correctly', () => {
|
|
130
|
+
const input = ':16R:GENL\r\n' +
|
|
131
|
+
':28E:1/ONLY\r\n' +
|
|
132
|
+
':20C::SEME//NONREF\r\n' +
|
|
133
|
+
':23G:NEWM\r\n' +
|
|
134
|
+
':98A::PREP//20260108\r\n' +
|
|
135
|
+
':98A::STAT//20260108\r\n' +
|
|
136
|
+
':22F::STTY//CUST\r\n' +
|
|
137
|
+
':97A::SAFE//70033100/12345678\r\n' +
|
|
138
|
+
':17B::ACTI//Y\r\n' +
|
|
139
|
+
':16S:GENL\r\n' +
|
|
140
|
+
':16R:FIN\r\n' +
|
|
141
|
+
':35B:ISIN IE000UQND7H4\r\n' +
|
|
142
|
+
'HSBC ETF- WORLD DLA\r\n' +
|
|
143
|
+
'HSBC MSCI WORLD UCITS ETF\r\n' +
|
|
144
|
+
':93B::AGGR//UNIT/680\r\n' +
|
|
145
|
+
':16R:SUBBAL\r\n' +
|
|
146
|
+
':93C::TAVI//UNIT/AVAI/680\r\n' +
|
|
147
|
+
':70C::SUBB//1 HSBC ETF- WORLD DLA\r\n' +
|
|
148
|
+
'2\r\n' +
|
|
149
|
+
'3 EDE 37.200000000EUR 2026-01-08T19:08:31.7\r\n' +
|
|
150
|
+
'4 25875.56EUR IE000UQND7H4, 1/SO\r\n' +
|
|
151
|
+
':16S:SUBBAL\r\n' +
|
|
152
|
+
':19A::HOLD//EUR25296\r\n' +
|
|
153
|
+
':70E::HOLD//1STK++++20260107\r\n' +
|
|
154
|
+
'237,267+EUR\r\n' +
|
|
155
|
+
':16S:FIN\r\n' +
|
|
156
|
+
':16R:ADDINFO\r\n' +
|
|
157
|
+
':19A::HOLP//EUR25296\r\n' +
|
|
158
|
+
':16S:ADDINFO\r\n';
|
|
140
159
|
const parser = new Mt535Parser(input);
|
|
141
160
|
const statement = parser.parse();
|
|
161
|
+
expect(statement.totalValue).toBe(25296);
|
|
162
|
+
expect(statement.currency).toBe('EUR');
|
|
163
|
+
expect(statement.holdings).toHaveLength(1);
|
|
142
164
|
const holding = statement.holdings[0];
|
|
143
|
-
expect(holding.
|
|
165
|
+
expect(holding.isin).toBe('IE000UQND7H4');
|
|
166
|
+
expect(holding.wkn).toBeUndefined();
|
|
167
|
+
expect(holding.name).toBe('HSBC ETF- WORLD DLA/HSBC MSCI WORLD UCITS ETF');
|
|
168
|
+
expect(holding.acquisitionDate).toEqual(new Date('2026-01-07T12:00'));
|
|
169
|
+
expect(holding.acquisitionPrice).toBe(37.267);
|
|
170
|
+
expect(holding.amount).toBe(680);
|
|
171
|
+
expect(holding.price).toBe(37.2);
|
|
172
|
+
expect(holding.currency).toBe('EUR');
|
|
173
|
+
expect(holding.value).toBe(25296);
|
|
174
|
+
expect(holding.date).toEqual(new Date(2026, 0, 8, 19, 8, 31, 700));
|
|
144
175
|
});
|
|
145
|
-
it('
|
|
146
|
-
const input = ':16R:
|
|
147
|
-
':
|
|
148
|
-
':
|
|
149
|
-
':
|
|
176
|
+
it('parses ING statement correctly', () => {
|
|
177
|
+
const input = ':16R:GENL\r\n' +
|
|
178
|
+
':28E:1/ONLY\r\n' +
|
|
179
|
+
':20C::SEME//NONREF\r\n' +
|
|
180
|
+
':23G:NEWM\r\n' +
|
|
181
|
+
':98C::STAT//20260125221440\r\n' +
|
|
182
|
+
':22F::STTY//CUST\r\n' +
|
|
183
|
+
':97A::SAFE//12345678/1234567890\r\n' +
|
|
184
|
+
':17B::ACTI//Y\r\n' +
|
|
185
|
+
':16S:GENL\r\n' +
|
|
186
|
+
':16R:FIN\r\n' +
|
|
187
|
+
':35B:ISIN IE00B5BMR087\r\n' +
|
|
188
|
+
'/DE/A0YEDG\r\n' +
|
|
189
|
+
'ISHSVII-CORE S+P500 DLACC\r\n' +
|
|
190
|
+
':90B::MRKT//ACTU/EUR627,17\r\n' +
|
|
191
|
+
':94B::PRIC//LMAR/XGAT\r\n' +
|
|
192
|
+
':98A::PRIC//20260123\r\n' +
|
|
193
|
+
':93B::AGGR//UNIT/15,5\r\n' +
|
|
194
|
+
':16R:SUBBAL\r\n' +
|
|
195
|
+
':93C::TAVI//UNIT/AVAI/15,5\r\n' +
|
|
196
|
+
':16S:SUBBAL\r\n' +
|
|
197
|
+
':19A::HOLD//EUR9721,14\r\n' +
|
|
198
|
+
':70E::HOLD//1STK\r\n' +
|
|
199
|
+
'2456,114666+EUR\r\n' +
|
|
200
|
+
':16S:FIN\r\n' +
|
|
201
|
+
':16R:FIN\r\n' +
|
|
202
|
+
':35B:ISIN IE00B3WJKG14\r\n' +
|
|
203
|
+
'/DE/A142N1\r\n' +
|
|
204
|
+
'ISHSV-S+500INF.T.SECT.DLA\r\n' +
|
|
205
|
+
':90B::MRKT//ACTU/EUR34,975\r\n' +
|
|
206
|
+
':94B::PRIC//LMAR/XGAT\r\n' +
|
|
207
|
+
':98A::PRIC//20260123\r\n' +
|
|
208
|
+
':93B::AGGR//UNIT/85,\r\n' +
|
|
209
|
+
':16R:SUBBAL\r\n' +
|
|
210
|
+
':93C::TAVI//UNIT/AVAI/85,\r\n' +
|
|
211
|
+
':16S:SUBBAL\r\n' +
|
|
212
|
+
':19A::HOLD//EUR2972,88\r\n' +
|
|
213
|
+
':70E::HOLD//1STK\r\n' +
|
|
214
|
+
'225,208135+EUR\r\n' +
|
|
215
|
+
':16S:FIN\r\n' +
|
|
216
|
+
':16R:ADDINFO\r\n' +
|
|
217
|
+
':19A::HOLP//EUR12694,02\r\n' +
|
|
218
|
+
':16S:ADDINFO\r\n';
|
|
150
219
|
const parser = new Mt535Parser(input);
|
|
151
220
|
const statement = parser.parse();
|
|
152
|
-
|
|
153
|
-
expect(
|
|
221
|
+
expect(statement.totalValue).toBe(12694.02);
|
|
222
|
+
expect(statement.currency).toBe('EUR');
|
|
223
|
+
expect(statement.holdings).toHaveLength(2);
|
|
224
|
+
// First holding
|
|
225
|
+
const holding1 = statement.holdings[0];
|
|
226
|
+
expect(holding1.isin).toBe('IE00B5BMR087');
|
|
227
|
+
expect(holding1.wkn).toBe('A0YEDG');
|
|
228
|
+
expect(holding1.name).toBe('ISHSVII-CORE S+P500 DLACC');
|
|
229
|
+
expect(holding1.amount).toBe(15.5);
|
|
230
|
+
expect(holding1.price).toBe(627.17);
|
|
231
|
+
expect(holding1.currency).toBe('EUR');
|
|
232
|
+
expect(holding1.value).toBe(9721.14);
|
|
233
|
+
expect(holding1.date).toEqual(new Date('2026-01-23T12:00'));
|
|
234
|
+
// Second holding
|
|
235
|
+
const holding2 = statement.holdings[1];
|
|
236
|
+
expect(holding2.isin).toBe('IE00B3WJKG14');
|
|
237
|
+
expect(holding2.wkn).toBe('A142N1');
|
|
238
|
+
expect(holding2.name).toBe('ISHSV-S+500INF.T.SECT.DLA');
|
|
239
|
+
expect(holding2.amount).toBe(85);
|
|
240
|
+
expect(holding2.price).toBe(34.975);
|
|
241
|
+
expect(holding2.currency).toBe('EUR');
|
|
242
|
+
expect(holding2.value).toBe(2972.88);
|
|
243
|
+
expect(holding2.date).toEqual(new Date('2026-01-23T12:00'));
|
|
154
244
|
});
|
|
155
245
|
});
|
|
@@ -7,37 +7,17 @@ export interface Holding {
|
|
|
7
7
|
isin?: string;
|
|
8
8
|
wkn?: string;
|
|
9
9
|
name?: string;
|
|
10
|
+
date?: Date;
|
|
10
11
|
amount?: number;
|
|
11
12
|
price?: number;
|
|
12
|
-
currency?: string;
|
|
13
13
|
value?: number;
|
|
14
|
+
currency?: string;
|
|
15
|
+
acquisitionDate?: Date;
|
|
14
16
|
acquisitionPrice?: number;
|
|
15
|
-
date?: Date;
|
|
16
|
-
}
|
|
17
|
-
export declare enum TokenType535 {
|
|
18
|
-
DepotValueBlock = "DepotValueBlock",
|
|
19
|
-
DepotValueCurrency = "DepotValueCurrency",
|
|
20
|
-
FinBlock = "FinBlock",
|
|
21
|
-
SecurityIdentification = "SecurityIdentification",
|
|
22
|
-
AcquisitionPrice = "AcquisitionPrice",
|
|
23
|
-
PriceBlock = "PriceBlock",
|
|
24
|
-
AmountBlock = "AmountBlock",
|
|
25
|
-
DateTimeBlock = "DateTimeBlock",
|
|
26
|
-
DateString = "DateString",
|
|
27
|
-
TimeString = "TimeString"
|
|
28
17
|
}
|
|
29
18
|
export declare class Mt535Parser {
|
|
30
|
-
private
|
|
19
|
+
private rawData;
|
|
31
20
|
constructor(rawData: string);
|
|
32
21
|
parse(): StatementOfHoldings;
|
|
33
|
-
private parseDepotValue;
|
|
34
|
-
private parseHoldings;
|
|
35
|
-
private parseHolding;
|
|
36
|
-
private parseSecurityIdentification;
|
|
37
|
-
private parseAcquisitionPrice;
|
|
38
|
-
private parsePrice;
|
|
39
|
-
private parseAmount;
|
|
40
|
-
private parseDateTime;
|
|
41
|
-
private parseDate;
|
|
42
22
|
}
|
|
43
23
|
//# sourceMappingURL=mt535parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mt535parser.d.ts","sourceRoot":"","sources":["../../src/mt535parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,OAAO;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,
|
|
1
|
+
{"version":3,"file":"mt535parser.d.ts","sourceRoot":"","sources":["../../src/mt535parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,OAAO;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAOD,qBAAa,WAAW;IACX,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,MAAM;IAEnC,KAAK,IAAI,mBAAmB;CA2K5B"}
|