sec_id 5.2.0 → 6.0.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/CHANGELOG.md +24 -0
  4. data/MIGRATION.md +101 -0
  5. data/README.md +200 -17
  6. data/lib/sec_id/active_model.rb +144 -0
  7. data/lib/sec_id/base.rb +33 -1
  8. data/lib/sec_id/bic/country_codes.rb +46 -0
  9. data/lib/sec_id/bic.rb +123 -0
  10. data/lib/sec_id/cei.rb +19 -7
  11. data/lib/sec_id/cfi/attribute_set.rb +49 -0
  12. data/lib/sec_id/cfi/classification.rb +107 -0
  13. data/lib/sec_id/cfi/field.rb +56 -0
  14. data/lib/sec_id/cfi/tables.rb +1033 -0
  15. data/lib/sec_id/cfi.rb +138 -208
  16. data/lib/sec_id/cik.rb +13 -0
  17. data/lib/sec_id/concerns/checkable.rb +2 -2
  18. data/lib/sec_id/concerns/generatable.rb +71 -0
  19. data/lib/sec_id/concerns/normalizable.rb +1 -0
  20. data/lib/sec_id/concerns/validatable.rb +14 -3
  21. data/lib/sec_id/cusip.rb +18 -7
  22. data/lib/sec_id/deep_freeze.rb +18 -0
  23. data/lib/sec_id/detector.rb +28 -7
  24. data/lib/sec_id/dti.rb +104 -0
  25. data/lib/sec_id/figi.rb +18 -0
  26. data/lib/sec_id/fisn.rb +31 -0
  27. data/lib/sec_id/iban/country_rules.rb +7 -6
  28. data/lib/sec_id/iban.rb +19 -1
  29. data/lib/sec_id/isin.rb +20 -15
  30. data/lib/sec_id/lei.rb +13 -0
  31. data/lib/sec_id/occ.rb +21 -0
  32. data/lib/sec_id/railtie.rb +14 -0
  33. data/lib/sec_id/scanner.rb +2 -6
  34. data/lib/sec_id/sedol.rb +16 -0
  35. data/lib/sec_id/valoren.rb +13 -0
  36. data/lib/sec_id/version.rb +2 -1
  37. data/lib/sec_id/wkn.rb +16 -0
  38. data/lib/sec_id.rb +29 -4
  39. data/sec_id.gemspec +11 -5
  40. data/sig/manifest.yaml +8 -0
  41. data/sig/sec_id/base.rbs +72 -0
  42. data/sig/sec_id/bic/country_codes.rbs +5 -0
  43. data/sig/sec_id/bic.rbs +30 -0
  44. data/sig/sec_id/cei.rbs +26 -0
  45. data/sig/sec_id/cfi/attribute_set.rbs +68 -0
  46. data/sig/sec_id/cfi/classification.rbs +23 -0
  47. data/sig/sec_id/cfi/field.rbs +337 -0
  48. data/sig/sec_id/cfi/tables.rbs +68 -0
  49. data/sig/sec_id/cfi.rbs +56 -0
  50. data/sig/sec_id/cik.rbs +19 -0
  51. data/sig/sec_id/concerns/checkable.rbs +41 -0
  52. data/sig/sec_id/concerns/generatable.rbs +26 -0
  53. data/sig/sec_id/concerns/normalizable.rbs +34 -0
  54. data/sig/sec_id/concerns/validatable.rbs +42 -0
  55. data/sig/sec_id/cusip.rbs +29 -0
  56. data/sig/sec_id/deep_freeze.rbs +6 -0
  57. data/sig/sec_id/detector.rbs +35 -0
  58. data/sig/sec_id/dti.rbs +27 -0
  59. data/sig/sec_id/errors.rbs +28 -0
  60. data/sig/sec_id/figi.rbs +31 -0
  61. data/sig/sec_id/fisn.rbs +24 -0
  62. data/sig/sec_id/iban/country_rules.rbs +10 -0
  63. data/sig/sec_id/iban.rbs +54 -0
  64. data/sig/sec_id/isin.rbs +40 -0
  65. data/sig/sec_id/lei.rbs +33 -0
  66. data/sig/sec_id/occ.rbs +45 -0
  67. data/sig/sec_id/scanner.rbs +46 -0
  68. data/sig/sec_id/sedol.rbs +34 -0
  69. data/sig/sec_id/valoren.rbs +29 -0
  70. data/sig/sec_id/version.rbs +3 -0
  71. data/sig/sec_id/wkn.rbs +15 -0
  72. data/sig/sec_id.rbs +58 -0
  73. metadata +55 -7
  74. data/lib/sec_id/concerns/identifier_metadata.rb +0 -56
@@ -0,0 +1,1033 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecID
4
+ class CFI < Base
5
+ # ISO 10962:2021 CFI code tables: the 14 categories, 78 groups, and the
6
+ # per-position attribute matrix — each cell carrying both a symbol and the
7
+ # authoritative ISO/SIX label string.
8
+ #
9
+ # Single source of truth for CFI validation, decoding, labels, predicates, and
10
+ # generation. Transcribed from docs/research/iso-10962-2021-cfi-tables.md
11
+ # (iotafinance's 2021 rendering; derivative tables single-sourced per R11).
12
+ #
13
+ # Shape:
14
+ # CATEGORIES[letter] => [symbol, label]
15
+ # GROUPS[cat_letter][grp_letter] => { symbol:, label:, attributes: [p3, p4, p5, p6] }
16
+ #
17
+ # Each of the four position entries is either {NA} (a pure not-applicable
18
+ # position — accepts only +X+, omitted from decode) or a
19
+ # +[meaning_symbol, { letter => [value_symbol, value_label] }]+ pair. The
20
+ # letter +X+ is universally accepted in every position and decodes to
21
+ # +:not_applicable+, so it is never listed in a value map.
22
+ #
23
+ # @api private
24
+ # rubocop:disable Metrics/ModuleLength
25
+ module Tables
26
+ # Marker for a pure not-applicable attribute position (accepts only +X+).
27
+ NA = nil
28
+
29
+ # --- Shared value lists (mirror the standard's building blocks) ---------
30
+
31
+ # Equity form (position 6 across E/D/R/M form positions).
32
+ FORM = {
33
+ 'B' => [:bearer, 'Bearer'],
34
+ 'R' => [:registered, 'Registered'],
35
+ 'N' => [:bearer_registered, 'Bearer/Registered'],
36
+ 'M' => [:others, 'Others (miscellaneous)']
37
+ }.freeze
38
+
39
+ # Equity voting right (position 3 across E share groups).
40
+ VOTING = {
41
+ 'V' => [:voting, 'Voting'],
42
+ 'N' => [:non_voting, 'Non-voting'],
43
+ 'R' => [:restricted_voting, 'Restricted voting'],
44
+ 'E' => [:enhanced_voting, 'Enhanced voting']
45
+ }.freeze
46
+
47
+ # Ownership/transfer/sales restrictions.
48
+ OWNERSHIP = {
49
+ 'T' => [:restrictions, 'Restrictions'],
50
+ 'U' => [:free_of_restrictions, 'Free of restrictions']
51
+ }.freeze
52
+
53
+ # Equity payment status.
54
+ PAYMENT = {
55
+ 'F' => [:fully_paid, 'Fully paid'],
56
+ 'O' => [:nil_paid, 'Nil paid'],
57
+ 'P' => [:partly_paid, 'Partly paid']
58
+ }.freeze
59
+
60
+ # Preference-share redemption (EP/EF position 4).
61
+ PREF_REDEMPTION = {
62
+ 'R' => [:redeemable, 'Redeemable'],
63
+ 'E' => [:extendible, 'Extendible'],
64
+ 'T' => [:redeemable_extendible, 'Redeemable/Extendible'],
65
+ 'G' => [:exchangeable, 'Exchangeable'],
66
+ 'A' => [:redeemable_exchangeable_extendible, 'Redeemable/Exchangeable/Extendible'],
67
+ 'C' => [:redeemable_exchangeable, 'Redeemable/Exchangeable'],
68
+ 'N' => [:perpetual, 'Perpetual']
69
+ }.freeze
70
+
71
+ # Preference-share income (EP/EF position 5).
72
+ PREF_INCOME = {
73
+ 'F' => [:fixed_rate, 'Fixed rate'],
74
+ 'C' => [:cumulative_fixed_rate, 'Cumulative, fixed rate'],
75
+ 'P' => [:participating, 'Participating'],
76
+ 'Q' => [:cumulative_participating, 'Cumulative, participating'],
77
+ 'A' => [:adjustable_rate, 'Adjustable/Variable rate'],
78
+ 'N' => [:normal_rate, 'Normal rate'],
79
+ 'U' => [:auction_rate, 'Auction rate']
80
+ }.freeze
81
+
82
+ # Debt guarantee (position 4 across most D groups).
83
+ DEBT_GUARANTEE = {
84
+ 'T' => [:government_guarantee, 'Government/State guarantee'],
85
+ 'G' => [:joint_guarantee, 'Joint guarantee'],
86
+ 'S' => [:secured, 'Secured'],
87
+ 'U' => [:unsecured, 'Unsecured/Unguaranteed'],
88
+ 'P' => [:negative_pledge, 'Negative pledge'],
89
+ 'N' => [:senior, 'Senior'],
90
+ 'O' => [:senior_subordinated, 'Senior subordinated'],
91
+ 'Q' => [:junior, 'Junior'],
92
+ 'J' => [:junior_subordinated, 'Junior subordinated'],
93
+ 'C' => [:supranational, 'Supranational']
94
+ }.freeze
95
+
96
+ # Debt redemption/reimbursement (position 5 across most D groups).
97
+ DEBT_REDEMPTION = {
98
+ 'F' => [:fixed_maturity, 'Fixed maturity'],
99
+ 'G' => [:fixed_maturity_with_call, 'Fixed maturity with call'],
100
+ 'C' => [:fixed_maturity_with_put, 'Fixed maturity with put'],
101
+ 'D' => [:fixed_maturity_with_put_and_call, 'Fixed maturity with put and call'],
102
+ 'A' => [:amortization_plan, 'Amortization plan'],
103
+ 'B' => [:amortization_with_call, 'Amortization plan with call'],
104
+ 'T' => [:amortization_with_put, 'Amortization plan with put'],
105
+ 'L' => [:amortization_with_put_and_call, 'Amortization plan with put and call'],
106
+ 'P' => [:perpetual, 'Perpetual'],
107
+ 'Q' => [:perpetual_with_call, 'Perpetual with call'],
108
+ 'R' => [:perpetual_with_put, 'Perpetual with put'],
109
+ 'E' => [:extendible, 'Extendible']
110
+ }.freeze
111
+
112
+ # Debt type of interest — DC/DW/DT/DY (F/Z/V/K, no cash payment).
113
+ DEBT_INTEREST_FZVK = {
114
+ 'F' => [:fixed_rate, 'Fixed rate'],
115
+ 'Z' => [:zero_rate, 'Zero rate/discounted'],
116
+ 'V' => [:variable, 'Variable'],
117
+ 'K' => [:payment_in_kind, 'Payment in kind']
118
+ }.freeze
119
+
120
+ # Debt type of interest — DG/DA/DN (F/Z/V only).
121
+ DEBT_INTEREST_FZV = {
122
+ 'F' => [:fixed_rate, 'Fixed rate'],
123
+ 'Z' => [:zero_rate, 'Zero rate/discounted'],
124
+ 'V' => [:variable, 'Variable']
125
+ }.freeze
126
+
127
+ # Structured-product distribution (DS/DE position 4).
128
+ STRUCTURED_DISTRIBUTION = {
129
+ 'F' => [:fixed_interest, 'Fixed interest'],
130
+ 'D' => [:dividend, 'Dividend'],
131
+ 'V' => [:variable_interest, 'Variable interest'],
132
+ 'Y' => [:no_payments, 'No payments'],
133
+ 'M' => [:others, 'Others (miscellaneous)']
134
+ }.freeze
135
+
136
+ # Structured-product underlying assets (DS/DE position 6).
137
+ STRUCTURED_UNDERLYING = {
138
+ 'B' => [:baskets, 'Baskets'],
139
+ 'S' => [:equities, 'Equities'],
140
+ 'D' => [:debt_instruments, 'Debt instruments'],
141
+ 'T' => [:commodities, 'Commodities'],
142
+ 'C' => [:currencies, 'Currencies'],
143
+ 'I' => [:indices, 'Indices'],
144
+ 'N' => [:interest_rates, 'Interest rates'],
145
+ 'M' => [:others, 'Others (miscellaneous)']
146
+ }.freeze
147
+
148
+ # Warrant/mini-future underlying assets (RW/RF position 3).
149
+ WARRANT_UNDERLYING = {
150
+ 'B' => [:baskets, 'Baskets'],
151
+ 'S' => [:equities, 'Equities'],
152
+ 'D' => [:debt_instruments, 'Debt instruments/Interest rates'],
153
+ 'T' => [:commodities, 'Commodities'],
154
+ 'C' => [:currencies, 'Currencies'],
155
+ 'I' => [:indices, 'Indices'],
156
+ 'M' => [:others, 'Others (miscellaneous)']
157
+ }.freeze
158
+
159
+ # Exercise option style with Others (RW/RF position 6).
160
+ EXERCISE_STYLE = {
161
+ 'A' => [:american, 'American'],
162
+ 'E' => [:european, 'European'],
163
+ 'B' => [:bermudan, 'Bermudan'],
164
+ 'M' => [:others, 'Others (miscellaneous)']
165
+ }.freeze
166
+
167
+ # Collective investment: closed/open-end (position 3).
168
+ CIV_CLOSED_OPEN = {
169
+ 'O' => [:open_end, 'Open-end'],
170
+ 'C' => [:closed_end, 'Closed-end'],
171
+ 'M' => [:others, 'Others (miscellaneous)']
172
+ }.freeze
173
+
174
+ # Collective investment: distribution policy (position 4).
175
+ CIV_DISTRIBUTION = {
176
+ 'I' => [:income_funds, 'Income funds'],
177
+ 'G' => [:accumulation_funds, 'Accumulation funds'],
178
+ 'J' => [:mixed_funds, 'Mixed funds']
179
+ }.freeze
180
+
181
+ # Collective investment: assets (position 5).
182
+ CIV_ASSETS = {
183
+ 'R' => [:real_estate, 'Real estate'],
184
+ 'B' => [:debt_instruments, 'Debt instruments'],
185
+ 'E' => [:equities, 'Equities'],
186
+ 'V' => [:convertible_securities, 'Convertible securities'],
187
+ 'L' => [:mixed, 'Mixed'],
188
+ 'C' => [:commodities, 'Commodities'],
189
+ 'D' => [:derivatives, 'Derivatives'],
190
+ 'F' => [:referential_instruments, 'Referential instruments'],
191
+ 'K' => [:credits, 'Credits'],
192
+ 'M' => [:others, 'Others (miscellaneous)']
193
+ }.freeze
194
+
195
+ # Collective investment: security type & investor restrictions (S/Q/U/Y).
196
+ CIV_SECURITY_FULL = {
197
+ 'S' => [:shares, 'Shares'],
198
+ 'Q' => [:shares_for_qualified_investors, 'Shares for qualified investors'],
199
+ 'U' => [:units, 'Units'],
200
+ 'Y' => [:units_for_qualified_investors, 'Units for qualified investors']
201
+ }.freeze
202
+
203
+ # Collective investment: security type (S/U only).
204
+ CIV_SECURITY_SHORT = {
205
+ 'S' => [:shares, 'Shares'],
206
+ 'U' => [:units, 'Units']
207
+ }.freeze
208
+
209
+ # Listed-option exercise style (A/E/B, no Others).
210
+ OPTION_STYLE = {
211
+ 'A' => [:american, 'American'],
212
+ 'E' => [:european, 'European'],
213
+ 'B' => [:bermudan, 'Bermudan']
214
+ }.freeze
215
+
216
+ # Listed-option underlying assets (OC/OP position 4).
217
+ OPTION_UNDERLYING = {
218
+ 'B' => [:baskets, 'Baskets'],
219
+ 'S' => [:equities, 'Stock-Equities'],
220
+ 'D' => [:debt_instruments, 'Debt instruments'],
221
+ 'T' => [:commodities, 'Commodities'],
222
+ 'C' => [:currencies, 'Currencies'],
223
+ 'I' => [:indices, 'Indices'],
224
+ 'O' => [:options, 'Options'],
225
+ 'F' => [:futures, 'Futures'],
226
+ 'W' => [:swaps, 'Swaps'],
227
+ 'N' => [:interest_rates, 'Interest rates'],
228
+ 'M' => [:others, 'Others (miscellaneous)']
229
+ }.freeze
230
+
231
+ # Standardized flag (S/N).
232
+ STANDARDIZED = {
233
+ 'S' => [:standardized, 'Standardized'],
234
+ 'N' => [:non_standardized, 'Non-standardized']
235
+ }.freeze
236
+
237
+ # Commodity underlying — extraction/agriculture form (FC/TT position 3).
238
+ COMMODITY_ASSETS = {
239
+ 'E' => [:extraction_resources, 'Extraction resources'],
240
+ 'A' => [:agriculture, 'Agriculture'],
241
+ 'I' => [:industrial_products, 'Industrial products'],
242
+ 'S' => [:services, 'Services'],
243
+ 'N' => [:environmental, 'Environmental'],
244
+ 'P' => [:polypropylene_products, 'Polypropylene products'],
245
+ 'H' => [:generated_resources, 'Generated resources'],
246
+ 'M' => [:others, 'Others (miscellaneous)']
247
+ }.freeze
248
+
249
+ # Swap return/payout trigger (ST/SE position 4).
250
+ RETURN_TRIGGER = {
251
+ 'P' => [:price, 'Price'],
252
+ 'D' => [:dividend, 'Dividend'],
253
+ 'V' => [:variance, 'Variance'],
254
+ 'L' => [:volatility, 'Volatility'],
255
+ 'T' => [:total_return, 'Total return'],
256
+ 'C' => [:contract_for_difference, 'Contract for difference'],
257
+ 'M' => [:others, 'Others (miscellaneous)']
258
+ }.freeze
259
+
260
+ # Non-listed option style and type (H position 4).
261
+ H_OPTION_STYLE = {
262
+ 'A' => [:european_call, 'European call'],
263
+ 'B' => [:american_call, 'American call'],
264
+ 'C' => [:bermudan_call, 'Bermudan call'],
265
+ 'D' => [:european_put, 'European put'],
266
+ 'E' => [:american_put, 'American put'],
267
+ 'F' => [:bermudan_put, 'Bermudan put'],
268
+ 'G' => [:european_chooser, 'European chooser'],
269
+ 'H' => [:american_chooser, 'American chooser'],
270
+ 'I' => [:bermudan_chooser, 'Bermudan chooser']
271
+ }.freeze
272
+
273
+ # Non-listed option valuation method or trigger (H position 5).
274
+ H_VALUATION = {
275
+ 'V' => [:vanilla, 'Vanilla'],
276
+ 'A' => [:asian, 'Asian'],
277
+ 'D' => [:digital, 'Digital (binary)'],
278
+ 'B' => [:barrier, 'Barrier'],
279
+ 'G' => [:digital_barrier, 'Digital barrier'],
280
+ 'L' => [:lookback, 'Lookback'],
281
+ 'P' => [:other_path_dependent, 'Other path-dependent'],
282
+ 'M' => [:others, 'Others (miscellaneous)']
283
+ }.freeze
284
+
285
+ # Delivery variants.
286
+ DELIVERY_CP = { 'C' => [:cash, 'Cash'], 'P' => [:physical, 'Physical'] }.freeze
287
+ # Delivery variants: physical or non-deliverable.
288
+ DELIVERY_PN = { 'P' => [:physical, 'Physical'], 'N' => [:non_deliverable, 'Non-deliverable'] }.freeze
289
+ # Delivery variant: physical only.
290
+ DELIVERY_P = { 'P' => [:physical, 'Physical'] }.freeze
291
+ # Delivery variants: physical, cash, or non-deliverable.
292
+ DELIVERY_PCN = {
293
+ 'P' => [:physical, 'Physical'],
294
+ 'C' => [:cash, 'Cash'],
295
+ 'N' => [:non_deliverable, 'Non-deliverable']
296
+ }.freeze
297
+ # Delivery variants: cash, physical, or elect-at-settlement.
298
+ DELIVERY_CPE = {
299
+ 'C' => [:cash, 'Cash'],
300
+ 'P' => [:physical, 'Physical'],
301
+ 'E' => [:elect_at_settlement, 'Elect at settlement']
302
+ }.freeze
303
+ # Non-listed option delivery variants: cash, physical, or elect-at-exercise.
304
+ H_DELIVERY = {
305
+ 'C' => [:cash, 'Cash'],
306
+ 'P' => [:physical, 'Physical'],
307
+ 'E' => [:elect_at_exercise, 'Elect at exercise']
308
+ }.freeze
309
+ # H_DELIVERY variants plus non-deliverable.
310
+ H_DELIVERY_N = H_DELIVERY.merge('N' => [:non_deliverable, 'Non-deliverable']).freeze
311
+ # H_DELIVERY_N variants plus auction.
312
+ H_DELIVERY_NA = H_DELIVERY_N.merge('A' => [:auction, 'Auction']).freeze
313
+
314
+ # Forward return/payout trigger variants.
315
+ FWD_TRIGGER_CSF = {
316
+ 'C' => [:contract_for_difference, 'Contract for difference'],
317
+ 'S' => [:spread_bet, 'Spread-bet'],
318
+ 'F' => [:forward_price, 'Forward price of underlying']
319
+ }.freeze
320
+ # Forward trigger variants: spread-bet or forward price.
321
+ FWD_TRIGGER_SF = {
322
+ 'S' => [:spread_bet, 'Spread-bet'],
323
+ 'F' => [:forward_price, 'Forward price of underlying']
324
+ }.freeze
325
+ # Forward trigger variants: contract-for-difference or forward price.
326
+ FWD_TRIGGER_CF = {
327
+ 'C' => [:contract_for_difference, 'Contract for difference'],
328
+ 'F' => [:forward_price, 'Forward price of underlying']
329
+ }.freeze
330
+
331
+ # --- Categories (position 1) --------------------------------------------
332
+
333
+ CATEGORIES = {
334
+ 'E' => [:equity, 'Equities'],
335
+ 'C' => [:collective_investment_vehicles, 'Collective investment vehicles'],
336
+ 'D' => [:debt_instruments, 'Debt instruments'],
337
+ 'R' => [:entitlements, 'Entitlements (rights)'],
338
+ 'O' => [:listed_options, 'Listed options'],
339
+ 'F' => [:futures, 'Futures'],
340
+ 'S' => [:swaps, 'Swaps'],
341
+ 'H' => [:non_listed_options, 'Non-listed and complex listed options'],
342
+ 'I' => [:spot, 'Spot'],
343
+ 'J' => [:forwards, 'Forwards'],
344
+ 'K' => [:strategies, 'Strategies'],
345
+ 'L' => [:financing, 'Financing'],
346
+ 'T' => [:referential_instruments, 'Referential instruments'],
347
+ 'M' => [:miscellaneous, 'Others (miscellaneous)']
348
+ }.freeze
349
+
350
+ # --- Groups + attribute matrix (positions 2-6) --------------------------
351
+
352
+ GROUPS = {
353
+ 'E' => {
354
+ 'S' => { symbol: :common_shares, label: 'Common/Ordinary shares',
355
+ attributes: [[:voting_right, VOTING], [:ownership_restrictions, OWNERSHIP],
356
+ [:payment_status, PAYMENT], [:form, FORM]] },
357
+ 'P' => { symbol: :preferred_shares, label: 'Preferred/Preference shares',
358
+ attributes: [[:voting_right, VOTING], [:redemption, PREF_REDEMPTION],
359
+ [:income, PREF_INCOME], [:form, FORM]] },
360
+ 'C' => { symbol: :convertible_common_shares, label: 'Common/Ordinary convertible shares',
361
+ attributes: [[:voting_right, VOTING], [:ownership_restrictions, OWNERSHIP],
362
+ [:payment_status, PAYMENT], [:form, FORM]] },
363
+ 'F' => { symbol: :convertible_preferred_shares, label: 'Preferred/Preference convertible shares',
364
+ attributes: [[:voting_right, VOTING], [:redemption, PREF_REDEMPTION],
365
+ [:income, PREF_INCOME], [:form, FORM]] },
366
+ 'L' => { symbol: :limited_partnership_units, label: 'Limited partnership units',
367
+ attributes: [[:voting_right, VOTING], [:ownership_restrictions, OWNERSHIP],
368
+ [:payment_status, PAYMENT], [:form, FORM]] },
369
+ 'D' => { symbol: :depositary_receipts, label: 'Depositary receipts on equities',
370
+ attributes: [[:underlying, {
371
+ 'S' => [:common_shares, 'Common/Ordinary shares'],
372
+ 'P' => [:preferred_shares, 'Preferred/Preference shares'],
373
+ 'C' => [:convertible_common_shares, 'Common/Ordinary convertible shares'],
374
+ 'F' => [:convertible_preferred_shares, 'Preferred/Preference convertible shares'],
375
+ 'L' => [:limited_partnership_units, 'Limited partnership units'],
376
+ 'M' => [:others, 'Others (miscellaneous)']
377
+ }], [:redemption, {
378
+ 'R' => [:redeemable, 'Redeemable'],
379
+ 'N' => [:perpetual, 'Perpetual'],
380
+ 'B' => [:convertible, 'Convertible'],
381
+ 'D' => [:convertible_redeemable, 'Convertible/Redeemable']
382
+ }], [:income, PREF_INCOME.merge('D' => [:dividends, 'Dividends'])],
383
+ [:form, FORM]] },
384
+ 'Y' => { symbol: :structured_instruments, label: 'Structured instruments (participation)',
385
+ attributes: [[:type, {
386
+ 'A' => [:tracker_certificate, 'Tracker certificate'],
387
+ 'B' => [:outperforming_certificate, 'Outperforming certificate'],
388
+ 'C' => [:bonus_certificate, 'Bonus certificate'],
389
+ 'D' => [:outperformance_bonus_certificate, 'Outperformance bonus certificate'],
390
+ 'E' => [:twin_win_certificate, 'Twin-win certificate'],
391
+ 'M' => [:others, 'Others (miscellaneous)']
392
+ }], [:distribution, {
393
+ 'D' => [:dividend_payments, 'Dividend payments'],
394
+ 'Y' => [:no_payments, 'No payments'],
395
+ 'M' => [:others, 'Others (miscellaneous)']
396
+ }], [:repayment, {
397
+ 'F' => [:cash_repayment, 'Cash repayment'],
398
+ 'V' => [:physical_repayment, 'Physical repayment'],
399
+ 'E' => [:elect_at_settlement, 'Elect at settlement'],
400
+ 'M' => [:others, 'Others (miscellaneous)']
401
+ }], [:underlying_assets, {
402
+ 'B' => [:baskets, 'Baskets'],
403
+ 'S' => [:equities, 'Equities'],
404
+ 'D' => [:debt_instruments, 'Debt instruments'],
405
+ 'G' => [:derivatives, 'Derivatives'],
406
+ 'T' => [:commodities, 'Commodities'],
407
+ 'C' => [:currencies, 'Currencies'],
408
+ 'I' => [:indices, 'Indices'],
409
+ 'N' => [:interest_rates, 'Interest rates'],
410
+ 'M' => [:others, 'Others (miscellaneous)']
411
+ }]] },
412
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)',
413
+ attributes: [NA, NA, NA, [:form, FORM]] }
414
+ },
415
+ 'C' => {
416
+ 'I' => { symbol: :standard_investment_funds, label: 'Standard (vanilla) funds',
417
+ attributes: [[:closed_open, CIV_CLOSED_OPEN], [:distribution_policy, CIV_DISTRIBUTION],
418
+ [:assets, CIV_ASSETS], [:security_type, CIV_SECURITY_FULL]] },
419
+ 'H' => { symbol: :hedge_funds, label: 'Hedge funds',
420
+ attributes: [[:investment_strategy, {
421
+ 'D' => [:directional, 'Directional'],
422
+ 'R' => [:relative_value, 'Relative value'],
423
+ 'S' => [:security_selection, 'Security selection'],
424
+ 'E' => [:event_driven, 'Event-driven'],
425
+ 'A' => [:arbitrage, 'Arbitrage'],
426
+ 'N' => [:multi_strategy, 'Multi-strategy'],
427
+ 'L' => [:asset_based_lending, 'Asset-based lending'],
428
+ 'M' => [:others, 'Others (miscellaneous)']
429
+ }], NA, NA, NA] },
430
+ 'B' => { symbol: :real_estate_investment_trusts, label: 'Real estate investment trusts (REITs)',
431
+ attributes: [[:closed_open, CIV_CLOSED_OPEN], [:distribution_policy, CIV_DISTRIBUTION],
432
+ NA, [:security_type, CIV_SECURITY_FULL]] },
433
+ 'E' => { symbol: :exchange_traded_funds, label: 'Exchange-traded funds (ETFs)',
434
+ attributes: [[:closed_open, CIV_CLOSED_OPEN], [:distribution_policy, CIV_DISTRIBUTION],
435
+ [:assets, CIV_ASSETS], [:security_type, CIV_SECURITY_SHORT]] },
436
+ 'S' => { symbol: :pension_funds, label: 'Pension funds',
437
+ attributes: [[:closed_open, CIV_CLOSED_OPEN], [:strategy_style, {
438
+ 'B' => [:balanced_conservative, 'Balanced/Conservative'],
439
+ 'G' => [:growth, 'Growth'],
440
+ 'L' => [:life_style, 'Life style'],
441
+ 'M' => [:others, 'Others (miscellaneous)']
442
+ }], [:type, {
443
+ 'R' => [:defined_benefit, 'Defined benefit'],
444
+ 'B' => [:defined_contribution, 'Defined contribution'],
445
+ 'M' => [:others, 'Others (miscellaneous)']
446
+ }], [:security_type, CIV_SECURITY_SHORT]] },
447
+ 'F' => { symbol: :funds_of_funds, label: 'Funds of funds',
448
+ attributes: [[:closed_open, CIV_CLOSED_OPEN], [:distribution_policy, CIV_DISTRIBUTION],
449
+ [:underlying_fund_type, {
450
+ 'I' => [:standard_funds, 'Standard (vanilla)'],
451
+ 'H' => [:hedge_funds, 'Hedge funds'],
452
+ 'B' => [:reit, 'REIT'],
453
+ 'E' => [:etf, 'ETF'],
454
+ 'P' => [:private_equity, 'Private equity'],
455
+ 'M' => [:others, 'Others (miscellaneous)']
456
+ }], [:security_type, CIV_SECURITY_FULL]] },
457
+ 'P' => { symbol: :private_equity_funds, label: 'Private equity funds',
458
+ attributes: [[:closed_open, CIV_CLOSED_OPEN], [:distribution_policy, CIV_DISTRIBUTION],
459
+ [:assets, CIV_ASSETS], [:security_type, CIV_SECURITY_FULL]] },
460
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)',
461
+ attributes: [NA, NA, NA, [:security_type, CIV_SECURITY_FULL]] }
462
+ },
463
+ 'D' => {
464
+ 'B' => { symbol: :bonds, label: 'Bonds',
465
+ attributes: [[:interest_type, {
466
+ 'F' => [:fixed_rate, 'Fixed rate'],
467
+ 'Z' => [:zero_rate, 'Zero rate/discounted'],
468
+ 'V' => [:variable, 'Variable'],
469
+ 'C' => [:cash_payment, 'Cash payment'],
470
+ 'K' => [:payment_in_kind, 'Payment in kind']
471
+ }], [:guarantee, DEBT_GUARANTEE], [:redemption, DEBT_REDEMPTION], [:form, FORM]] },
472
+ 'C' => { symbol: :convertible_bonds, label: 'Convertible bonds',
473
+ attributes: [[:interest_type, DEBT_INTEREST_FZVK], [:guarantee, DEBT_GUARANTEE],
474
+ [:redemption, DEBT_REDEMPTION], [:form, FORM]] },
475
+ 'W' => { symbol: :bonds_with_warrants, label: 'Bonds with warrants attached',
476
+ attributes: [[:interest_type, DEBT_INTEREST_FZVK], [:guarantee, DEBT_GUARANTEE],
477
+ [:redemption, DEBT_REDEMPTION], [:form, FORM]] },
478
+ 'T' => { symbol: :medium_term_notes, label: 'Medium-term notes',
479
+ attributes: [[:interest_type, DEBT_INTEREST_FZVK], [:guarantee, DEBT_GUARANTEE],
480
+ [:redemption, DEBT_REDEMPTION], [:form, FORM]] },
481
+ 'S' => { symbol: :structured_products_with_protection,
482
+ label: 'Structured products (with capital protection)',
483
+ attributes: [[:type, {
484
+ 'A' => [:capital_protection_with_participation,
485
+ 'Capital protection certificate with participation'],
486
+ 'B' => [:capital_protection_convertible, 'Capital protection convertible certificate'],
487
+ 'C' => [:barrier_capital_protection, 'Barrier capital protection certificate'],
488
+ 'D' => [:capital_protection_with_coupons, 'Capital protection certificate with coupons'],
489
+ 'M' => [:others, 'Others (miscellaneous)']
490
+ }], [:distribution, STRUCTURED_DISTRIBUTION], [:repayment, {
491
+ 'F' => [:fixed_cash_repayment, 'Fixed cash repayment (protected level only)'],
492
+ 'V' => [:variable_cash_repayment, 'Variable cash repayment'],
493
+ 'M' => [:others, 'Others (miscellaneous)']
494
+ }], [:underlying_assets, STRUCTURED_UNDERLYING]] },
495
+ 'E' => { symbol: :structured_products_without_protection,
496
+ label: 'Structured products (without capital protection)',
497
+ attributes: [[:type, {
498
+ 'A' => [:discount_certificate, 'Discount certificate'],
499
+ 'B' => [:barrier_discount_certificate, 'Barrier discount certificate'],
500
+ 'C' => [:reverse_convertible, 'Reverse convertible'],
501
+ 'D' => [:barrier_reverse_convertible, 'Barrier reverse convertible'],
502
+ 'E' => [:express_certificate, 'Express certificate'],
503
+ 'M' => [:others, 'Others (miscellaneous)']
504
+ }], [:distribution, STRUCTURED_DISTRIBUTION], [:repayment, {
505
+ 'R' => [:repayment_in_cash, 'Repayment in cash'],
506
+ 'S' => [:repayment_in_assets, 'Repayment in assets'],
507
+ 'C' => [:repayment_in_assets_and_cash, 'Repayment in assets and cash'],
508
+ 'T' => [:repayment_in_assets_or_cash, 'Repayment in assets or cash'],
509
+ 'M' => [:others, 'Others (miscellaneous)']
510
+ }], [:underlying_assets, STRUCTURED_UNDERLYING]] },
511
+ 'G' => { symbol: :mortgage_backed_securities, label: 'Mortgage-backed securities (MBS)',
512
+ attributes: [[:interest_type, DEBT_INTEREST_FZV], [:guarantee, DEBT_GUARANTEE],
513
+ [:redemption, DEBT_REDEMPTION], [:form, FORM]] },
514
+ 'A' => { symbol: :asset_backed_securities, label: 'Asset-backed securities (ABS)',
515
+ attributes: [[:interest_type, DEBT_INTEREST_FZV], [:guarantee, DEBT_GUARANTEE],
516
+ [:redemption, DEBT_REDEMPTION], [:form, FORM]] },
517
+ 'N' => { symbol: :municipal_bonds, label: 'Municipal bonds',
518
+ attributes: [[:interest_type, DEBT_INTEREST_FZV], [:guarantee, DEBT_GUARANTEE],
519
+ [:redemption, DEBT_REDEMPTION], [:form, FORM]] },
520
+ 'D' => { symbol: :depositary_receipts, label: 'Depositary receipts on debt instruments',
521
+ attributes: [[:underlying, {
522
+ 'B' => [:bonds, 'Bonds'],
523
+ 'C' => [:convertible_bonds, 'Convertible bonds'],
524
+ 'W' => [:bonds_with_warrants, 'Bonds with warrants attached'],
525
+ 'T' => [:medium_term_notes, 'Medium-term notes'],
526
+ 'Y' => [:money_market, 'Money-market instruments'],
527
+ 'G' => [:mortgage_backed, 'Mortgage-backed securities (MBS)'],
528
+ 'Q' => [:asset_backed, 'Asset-backed securities (ABS)'],
529
+ 'N' => [:municipal_bonds, 'Municipal bonds'],
530
+ 'M' => [:others, 'Others (miscellaneous)']
531
+ }], [:interest_type, {
532
+ 'F' => [:fixed_rate, 'Fixed rate'],
533
+ 'Z' => [:zero_rate, 'Zero rate/discounted'],
534
+ 'V' => [:variable, 'Variable'],
535
+ 'C' => [:cash_payment, 'Cash payment']
536
+ }], [:guarantee, DEBT_GUARANTEE], [:redemption, DEBT_REDEMPTION]] },
537
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)',
538
+ attributes: [[:type, {
539
+ 'B' => [:bank_loan, 'Bank loan'],
540
+ 'P' => [:promissory_note, 'Promissory note'],
541
+ 'M' => [:others, 'Others (miscellaneous)']
542
+ }], NA, NA, [:form, FORM]] },
543
+ 'Y' => { symbol: :money_market_instruments, label: 'Money-market instruments',
544
+ attributes: [[:interest_type, DEBT_INTEREST_FZVK], [:guarantee, DEBT_GUARANTEE],
545
+ NA, [:form, FORM]] }
546
+ },
547
+ 'R' => {
548
+ 'A' => { symbol: :allotment_rights, label: 'Allotments (bonus rights)',
549
+ attributes: [NA, NA, NA, [:form, FORM]] },
550
+ 'S' => { symbol: :subscription_rights, label: 'Subscription rights',
551
+ attributes: [[:underlying_assets, {
552
+ 'S' => [:common_shares, 'Common/Ordinary shares'],
553
+ 'P' => [:preferred_shares, 'Preferred/Preference shares'],
554
+ 'C' => [:convertible_common_shares, 'Common/Ordinary convertible shares'],
555
+ 'F' => [:convertible_preferred_shares, 'Preferred/Preference convertible shares'],
556
+ 'B' => [:bonds, 'Bonds'],
557
+ 'I' => [:combined_instruments, 'Combined instruments'],
558
+ 'M' => [:others, 'Others (miscellaneous)']
559
+ }], NA, NA, [:form, FORM]] },
560
+ 'P' => { symbol: :purchase_rights, label: 'Purchase rights',
561
+ attributes: [[:underlying_assets, {
562
+ 'S' => [:common_shares, 'Common/Ordinary shares'],
563
+ 'P' => [:preferred_shares, 'Preferred/Preference shares'],
564
+ 'C' => [:convertible_common_shares, 'Common/Ordinary convertible shares'],
565
+ 'F' => [:convertible_preferred_shares, 'Preferred/Preference convertible shares'],
566
+ 'B' => [:bonds, 'Bonds'],
567
+ 'I' => [:combined_instruments, 'Combined instruments'],
568
+ 'M' => [:others, 'Others (miscellaneous)']
569
+ }], NA, NA, [:form, FORM]] },
570
+ 'W' => { symbol: :warrants, label: 'Warrants',
571
+ attributes: [[:underlying_assets, WARRANT_UNDERLYING], [:warrant_type, {
572
+ 'T' => [:traditional, 'Traditional'],
573
+ 'N' => [:naked, 'Naked'],
574
+ 'C' => [:covered, 'Covered']
575
+ }], [:call_put, {
576
+ 'C' => [:call, 'Call'],
577
+ 'P' => [:put, 'Put'],
578
+ 'B' => [:call_and_put, 'Call and put']
579
+ }], [:exercise_style, EXERCISE_STYLE]] },
580
+ 'F' => { symbol: :mini_future_certificates, label: 'Mini-future / constant-leverage certificates',
581
+ attributes: [[:underlying_assets, WARRANT_UNDERLYING], [:barrier_dependency, {
582
+ 'T' => [:barrier_underlying_based, 'Barrier underlying based'],
583
+ 'N' => [:barrier_instrument_based, 'Barrier instrument based'],
584
+ 'M' => [:others, 'Others (miscellaneous)']
585
+ }], [:long_short, {
586
+ 'C' => [:long, 'Long'],
587
+ 'P' => [:short, 'Short'],
588
+ 'M' => [:others, 'Others (miscellaneous)']
589
+ }], [:exercise_style, EXERCISE_STYLE]] },
590
+ 'D' => { symbol: :depositary_receipts, label: 'Depositary receipts on entitlements',
591
+ attributes: [[:underlying, {
592
+ 'A' => [:allotment_rights, 'Allotment (bonus) rights'],
593
+ 'S' => [:subscription_rights, 'Subscription rights'],
594
+ 'P' => [:purchase_rights, 'Purchase rights'],
595
+ 'W' => [:warrants, 'Warrants'],
596
+ 'M' => [:others, 'Others (miscellaneous)']
597
+ }], NA, NA, [:form, FORM]] },
598
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)',
599
+ attributes: [NA, NA, NA, NA] }
600
+ },
601
+ 'O' => {
602
+ 'C' => { symbol: :call_options, label: 'Call options',
603
+ attributes: [[:exercise_style, OPTION_STYLE], [:underlying_assets, OPTION_UNDERLYING],
604
+ [:delivery, {
605
+ 'P' => [:physical, 'Physical'],
606
+ 'C' => [:cash, 'Cash'],
607
+ 'N' => [:non_deliverable, 'Non-deliverable'],
608
+ 'E' => [:elect_at_exercise, 'Elect at exercise']
609
+ }], [:standardized, STANDARDIZED]] },
610
+ 'P' => { symbol: :put_options, label: 'Put options',
611
+ attributes: [[:exercise_style, OPTION_STYLE], [:underlying_assets, OPTION_UNDERLYING],
612
+ [:delivery, {
613
+ 'P' => [:physical, 'Physical'],
614
+ 'C' => [:cash, 'Cash'],
615
+ 'N' => [:non_deliverable, 'Non-deliverable'],
616
+ 'E' => [:elect_at_exercise, 'Elect at exercise']
617
+ }], [:standardized, STANDARDIZED]] },
618
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)',
619
+ attributes: [NA, NA, NA, NA] }
620
+ },
621
+ 'F' => {
622
+ 'F' => { symbol: :financial_futures, label: 'Financial futures',
623
+ attributes: [[:underlying_assets, {
624
+ 'B' => [:baskets, 'Baskets'],
625
+ 'S' => [:equities, 'Stock-Equities'],
626
+ 'D' => [:debt_instruments, 'Debt instruments'],
627
+ 'C' => [:currencies, 'Currencies'],
628
+ 'I' => [:indices, 'Indices'],
629
+ 'O' => [:options, 'Options'],
630
+ 'F' => [:futures, 'Futures'],
631
+ 'W' => [:swaps, 'Swaps'],
632
+ 'N' => [:interest_rates, 'Interest rates'],
633
+ 'V' => [:stock_dividend, 'Stock dividend'],
634
+ 'M' => [:others, 'Others (miscellaneous)']
635
+ }], [:delivery, DELIVERY_PCN], [:standardized, STANDARDIZED], NA] },
636
+ 'C' => { symbol: :commodities_futures, label: 'Commodities futures',
637
+ attributes: [[:underlying_assets, COMMODITY_ASSETS], [:delivery, DELIVERY_PCN],
638
+ [:standardized, STANDARDIZED], NA] }
639
+ },
640
+ 'S' => {
641
+ 'R' => { symbol: :rates, label: 'Rates',
642
+ attributes: [[:underlying_assets, {
643
+ 'A' => [:basis_swap, 'Basis swap'],
644
+ 'C' => [:fixed_floating, 'Fixed-floating'],
645
+ 'D' => [:fixed_fixed, 'Fixed-fixed'],
646
+ 'G' => [:inflation_rate_index, 'Inflation rate index'],
647
+ 'H' => [:overnight_index_swap, 'Overnight index swap (OIS)'],
648
+ 'Z' => [:zero_coupon, 'Zero coupon'],
649
+ 'M' => [:others, 'Others (miscellaneous)']
650
+ }], [:notional, {
651
+ 'C' => [:constant, 'Constant'],
652
+ 'D' => [:accreting, 'Accreting'],
653
+ 'I' => [:amortizing, 'Amortizing'],
654
+ 'Y' => [:custom, 'Custom']
655
+ }], [:currency, {
656
+ 'S' => [:single_currency, 'Single-currency'],
657
+ 'C' => [:cross_currency, 'Cross-currency']
658
+ }], [:delivery, DELIVERY_CP]] },
659
+ 'T' => { symbol: :commodities, label: 'Commodities',
660
+ attributes: [[:underlying_assets, {
661
+ 'J' => [:energy, 'Energy'],
662
+ 'K' => [:metals, 'Metals'],
663
+ 'A' => [:agriculture, 'Agriculture'],
664
+ 'N' => [:environmental, 'Environmental'],
665
+ 'G' => [:freight, 'Freight'],
666
+ 'P' => [:polypropylene_products, 'Polypropylene products'],
667
+ 'S' => [:fertilizer, 'Fertilizer'],
668
+ 'T' => [:paper, 'Paper'],
669
+ 'I' => [:index, 'Index'],
670
+ 'Q' => [:multi_commodity, 'Multi-commodity'],
671
+ 'M' => [:others, 'Others (miscellaneous)']
672
+ }], [:return_trigger, RETURN_TRIGGER], NA, [:delivery, DELIVERY_CPE]] },
673
+ 'E' => { symbol: :equity, label: 'Equity',
674
+ attributes: [[:underlying_assets, {
675
+ 'S' => [:single_stock, 'Single stock'],
676
+ 'I' => [:index, 'Index'],
677
+ 'B' => [:basket, 'Basket'],
678
+ 'M' => [:others, 'Others (miscellaneous)']
679
+ }], [:return_trigger, RETURN_TRIGGER], NA, [:delivery, DELIVERY_CPE]] },
680
+ 'C' => { symbol: :credit, label: 'Credit',
681
+ attributes: [[:underlying_assets, {
682
+ 'U' => [:single_name, 'Single name'],
683
+ 'V' => [:index_tranche, 'Index tranche'],
684
+ 'I' => [:index, 'Index'],
685
+ 'B' => [:basket, 'Basket'],
686
+ 'M' => [:others, 'Others (miscellaneous)']
687
+ }], [:return_trigger, {
688
+ 'C' => [:credit_default, 'Credit default'],
689
+ 'T' => [:total_return, 'Total return'],
690
+ 'M' => [:others, 'Others (miscellaneous)']
691
+ }], [:issuer_type, {
692
+ 'C' => [:corporate, 'Corporate'],
693
+ 'S' => [:sovereign, 'Sovereign'],
694
+ 'L' => [:local, 'Local']
695
+ }], [:delivery, {
696
+ 'C' => [:cash, 'Cash'],
697
+ 'P' => [:physical, 'Physical'],
698
+ 'A' => [:auction, 'Auction']
699
+ }]] },
700
+ 'F' => { symbol: :foreign_exchange, label: 'Foreign exchange',
701
+ attributes: [[:underlying_assets, {
702
+ 'A' => [:spot_forward_swap, 'Spot-forward swap'],
703
+ 'C' => [:forward_forward_swap, 'Forward-forward swap'],
704
+ 'M' => [:others, 'Others (miscellaneous)']
705
+ }], NA, NA, [:delivery, DELIVERY_PN]] },
706
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)',
707
+ attributes: [[:underlying_assets, {
708
+ 'P' => [:commercial_property, 'Commercial property (property derivative)'],
709
+ 'M' => [:others, 'Others (miscellaneous)']
710
+ }], NA, NA, [:delivery, DELIVERY_CP]] }
711
+ },
712
+ 'H' => {
713
+ 'R' => { symbol: :rates, label: 'Rates',
714
+ attributes: [[:underlying_assets, {
715
+ 'A' => [:basis_swap, 'Basis swap'],
716
+ 'C' => [:fixed_floating, 'Fixed-floating'],
717
+ 'D' => [:fixed_fixed, 'Fixed-fixed'],
718
+ 'G' => [:inflation_rate_index, 'Inflation rate index'],
719
+ 'H' => [:overnight_index_swap, 'Overnight index swap (OIS)'],
720
+ 'O' => [:options, 'Options'],
721
+ 'R' => [:forwards, 'Forwards'],
722
+ 'F' => [:futures, 'Futures'],
723
+ 'M' => [:others, 'Others (miscellaneous)']
724
+ }], [:option_style, H_OPTION_STYLE], [:valuation, H_VALUATION], [:delivery, H_DELIVERY]] },
725
+ 'T' => { symbol: :commodities, label: 'Commodities',
726
+ attributes: [[:underlying_assets, {
727
+ 'J' => [:energy, 'Energy'],
728
+ 'K' => [:metals, 'Metals'],
729
+ 'A' => [:agriculture, 'Agriculture'],
730
+ 'N' => [:environmental, 'Environmental'],
731
+ 'G' => [:freight, 'Freight'],
732
+ 'P' => [:polypropylene_products, 'Polypropylene products'],
733
+ 'S' => [:fertilizer, 'Fertilizer'],
734
+ 'T' => [:paper, 'Paper'],
735
+ 'I' => [:index, 'Index'],
736
+ 'Q' => [:multi_commodity, 'Multi-commodity'],
737
+ 'O' => [:options, 'Options'],
738
+ 'R' => [:forwards, 'Forwards'],
739
+ 'F' => [:futures, 'Futures'],
740
+ 'W' => [:swaps, 'Swaps'],
741
+ 'M' => [:others, 'Others (miscellaneous)']
742
+ }], [:option_style, H_OPTION_STYLE], [:valuation, H_VALUATION], [:delivery, H_DELIVERY]] },
743
+ 'E' => { symbol: :equity, label: 'Equity',
744
+ attributes: [[:underlying_assets, {
745
+ 'S' => [:single_stock, 'Single stock'],
746
+ 'I' => [:index, 'Index'],
747
+ 'B' => [:basket, 'Basket'],
748
+ 'O' => [:options, 'Options'],
749
+ 'R' => [:forwards, 'Forwards'],
750
+ 'F' => [:futures, 'Futures'],
751
+ 'M' => [:others, 'Others (miscellaneous)']
752
+ }], [:option_style, H_OPTION_STYLE], [:valuation, H_VALUATION], [:delivery, H_DELIVERY]] },
753
+ 'C' => { symbol: :credit, label: 'Credit',
754
+ attributes: [[:underlying_assets, {
755
+ 'U' => [:cds_single_name, 'CDS on a single name'],
756
+ 'V' => [:cds_index_tranche, 'CDS on an index tranche'],
757
+ 'I' => [:cds_index, 'CDS on an index'],
758
+ 'W' => [:swaps, 'Swaps'],
759
+ 'M' => [:others, 'Others (miscellaneous)']
760
+ }], [:option_style, H_OPTION_STYLE], [:valuation, H_VALUATION], [:delivery, H_DELIVERY]] },
761
+ 'F' => { symbol: :foreign_exchange, label: 'Foreign exchange',
762
+ attributes: [[:underlying_assets, {
763
+ 'R' => [:forwards, 'Forwards'],
764
+ 'F' => [:futures, 'Futures'],
765
+ 'T' => [:spot_forward_swap, 'Spot-forward swap'],
766
+ 'V' => [:volatility, 'Volatility'],
767
+ 'M' => [:others, 'Others (miscellaneous)']
768
+ }], [:option_style, H_OPTION_STYLE], [:valuation, H_VALUATION], [:delivery, H_DELIVERY_N]] },
769
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)',
770
+ attributes: [[:underlying_assets, {
771
+ 'P' => [:commercial_property, 'Commercial property'],
772
+ 'M' => [:others, 'Others (miscellaneous)']
773
+ }], [:option_style, H_OPTION_STYLE], [:valuation, H_VALUATION], [:delivery, H_DELIVERY_NA]] }
774
+ },
775
+ 'I' => {
776
+ 'F' => { symbol: :foreign_exchange, label: 'Foreign exchange',
777
+ attributes: [NA, NA, NA, [:delivery, DELIVERY_P]] },
778
+ 'T' => { symbol: :commodities, label: 'Commodities',
779
+ attributes: [[:underlying_assets, {
780
+ 'A' => [:agriculture, 'Agriculture'],
781
+ 'J' => [:energy, 'Energy'],
782
+ 'K' => [:metals, 'Metals'],
783
+ 'N' => [:environmental, 'Environmental'],
784
+ 'P' => [:polypropylene_products, 'Polypropylene products'],
785
+ 'S' => [:fertilizer, 'Fertilizer'],
786
+ 'T' => [:paper, 'Paper'],
787
+ 'M' => [:others, 'Others (miscellaneous)']
788
+ }], NA, NA, NA] }
789
+ },
790
+ 'J' => {
791
+ 'E' => { symbol: :equity, label: 'Equity',
792
+ attributes: [[:underlying_assets, {
793
+ 'S' => [:single_stock, 'Single stock'],
794
+ 'I' => [:index, 'Index'],
795
+ 'B' => [:basket, 'Basket'],
796
+ 'O' => [:options, 'Options'],
797
+ 'F' => [:futures, 'Futures']
798
+ }], NA, [:return_trigger, FWD_TRIGGER_CSF], [:delivery, DELIVERY_CP]] },
799
+ 'F' => { symbol: :foreign_exchange, label: 'Foreign exchange',
800
+ attributes: [[:underlying_assets, {
801
+ 'T' => [:spot, 'Spot'],
802
+ 'R' => [:forward, 'Forward'],
803
+ 'O' => [:options, 'Options'],
804
+ 'F' => [:futures, 'Futures']
805
+ }], NA, [:return_trigger, FWD_TRIGGER_CSF], [:delivery, DELIVERY_PCN]] },
806
+ 'C' => { symbol: :credit, label: 'Credit',
807
+ attributes: [[:underlying_assets, {
808
+ 'A' => [:single_name, 'Single name'],
809
+ 'I' => [:index, 'Index'],
810
+ 'B' => [:basket, 'Basket'],
811
+ 'C' => [:cds_single_name, 'CDS on a single name'],
812
+ 'D' => [:cds_index, 'CDS on an index'],
813
+ 'G' => [:cds_basket, 'CDS on a basket'],
814
+ 'O' => [:options, 'Options']
815
+ }], NA, [:return_trigger, FWD_TRIGGER_SF], [:delivery, DELIVERY_PCN]] },
816
+ 'R' => { symbol: :rates, label: 'Rates',
817
+ attributes: [[:underlying_assets, {
818
+ 'I' => [:interest_rate_index, 'Interest rate index'],
819
+ 'O' => [:options, 'Options'],
820
+ 'M' => [:others, 'Others (miscellaneous)']
821
+ }], NA, [:return_trigger, FWD_TRIGGER_SF], [:delivery, DELIVERY_PCN]] },
822
+ 'T' => { symbol: :commodities, label: 'Commodities',
823
+ attributes: [[:underlying_assets, {
824
+ 'A' => [:agriculture, 'Agriculture'],
825
+ 'B' => [:basket, 'Basket'],
826
+ 'G' => [:freight, 'Freight'],
827
+ 'I' => [:index, 'Index'],
828
+ 'J' => [:energy, 'Energy'],
829
+ 'K' => [:metals, 'Metals'],
830
+ 'N' => [:environmental, 'Environmental'],
831
+ 'P' => [:polypropylene_products, 'Polypropylene products'],
832
+ 'S' => [:fertilizer, 'Fertilizer'],
833
+ 'T' => [:paper, 'Paper'],
834
+ 'M' => [:others, 'Others (miscellaneous)']
835
+ }], NA, [:return_trigger, FWD_TRIGGER_CF], [:delivery, DELIVERY_PCN]] }
836
+ },
837
+ 'K' => {
838
+ 'R' => { symbol: :rates, label: 'Rates', attributes: [NA, NA, NA, NA] },
839
+ 'T' => { symbol: :commodities, label: 'Commodities', attributes: [NA, NA, NA, NA] },
840
+ 'E' => { symbol: :equity, label: 'Equity', attributes: [NA, NA, NA, NA] },
841
+ 'C' => { symbol: :credit, label: 'Credit', attributes: [NA, NA, NA, NA] },
842
+ 'F' => { symbol: :foreign_exchange, label: 'Foreign exchange', attributes: [NA, NA, NA, NA] },
843
+ 'Y' => { symbol: :mixed, label: 'Mixed assets', attributes: [NA, NA, NA, NA] },
844
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)', attributes: [NA, NA, NA, NA] }
845
+ },
846
+ 'L' => {
847
+ 'L' => { symbol: :loan_lease, label: 'Loan-lease',
848
+ attributes: [[:underlying_assets, {
849
+ 'A' => [:agriculture, 'Agriculture'],
850
+ 'B' => [:baskets, 'Baskets'],
851
+ 'J' => [:energy, 'Energy'],
852
+ 'K' => [:metals, 'Metals'],
853
+ 'N' => [:environmental, 'Environmental'],
854
+ 'P' => [:polypropylene_products, 'Polypropylene products'],
855
+ 'S' => [:fertilizer, 'Fertilizer'],
856
+ 'T' => [:paper, 'Paper'],
857
+ 'M' => [:others, 'Others (miscellaneous)']
858
+ }], NA, NA, [:delivery, DELIVERY_PCN]] },
859
+ 'R' => { symbol: :repurchase_agreements, label: 'Repurchase agreements',
860
+ attributes: [[:underlying_assets, {
861
+ 'G' => [:general_collateral, 'General collateral'],
862
+ 'S' => [:specific_security_collateral, 'Specific security collateral'],
863
+ 'C' => [:cash_collateral, 'Cash collateral']
864
+ }], [:termination, {
865
+ 'F' => [:flexible, 'Flexible'],
866
+ 'N' => [:overnight, 'Overnight'],
867
+ 'O' => [:open, 'Open'],
868
+ 'T' => [:term, 'Term']
869
+ }], NA, [:delivery, {
870
+ 'D' => [:delivery_versus_payment, 'Delivery versus payment'],
871
+ 'H' => [:hold_in_custody, 'Hold-in-custody'],
872
+ 'T' => [:tri_party, 'Tri-party']
873
+ }]] },
874
+ 'S' => { symbol: :securities_lending, label: 'Securities lending',
875
+ attributes: [[:underlying_assets, {
876
+ 'C' => [:cash_collateral, 'Cash collateral'],
877
+ 'G' => [:government_bonds, 'Government bonds'],
878
+ 'P' => [:corporate_bonds, 'Corporate bonds'],
879
+ 'T' => [:convertible_bonds, 'Convertible bonds'],
880
+ 'E' => [:equity, 'Equity'],
881
+ 'L' => [:letter_of_credit, 'Letter of credit'],
882
+ 'D' => [:certificate_of_deposit, 'Certificate of deposit'],
883
+ 'W' => [:warrants, 'Warrants'],
884
+ 'K' => [:money_market_instruments, 'Money-market instruments'],
885
+ 'M' => [:others, 'Others (miscellaneous)']
886
+ }], [:termination, {
887
+ 'N' => [:overnight, 'Overnight'],
888
+ 'O' => [:open, 'Open'],
889
+ 'T' => [:term, 'Term']
890
+ }], NA, [:delivery, {
891
+ 'D' => [:delivery_versus_payment, 'Delivery versus payment'],
892
+ 'F' => [:free_of_payment, 'Free of payment'],
893
+ 'H' => [:hold_in_custody, 'Hold-in-custody'],
894
+ 'T' => [:tri_party, 'Tri-party']
895
+ }]] }
896
+ },
897
+ 'T' => {
898
+ 'C' => { symbol: :currencies, label: 'Currencies',
899
+ attributes: [[:type, {
900
+ 'N' => [:national_currency, 'National currency'],
901
+ 'L' => [:legacy_currency, 'Legacy currency'],
902
+ 'C' => [:bullion_coins, 'Bullion coins'],
903
+ 'M' => [:others, 'Others (miscellaneous)']
904
+ }], NA, NA, NA] },
905
+ 'T' => { symbol: :commodities, label: 'Commodities',
906
+ attributes: [[:underlying_assets, COMMODITY_ASSETS], NA, NA, NA] },
907
+ 'R' => { symbol: :interest_rates, label: 'Interest rates',
908
+ attributes: [[:interest_rate_type, {
909
+ 'N' => [:nominal, 'Nominal'],
910
+ 'V' => [:variable, 'Variable'],
911
+ 'F' => [:fixed, 'Fixed'],
912
+ 'R' => [:real, 'Real'],
913
+ 'M' => [:others, 'Others (miscellaneous)']
914
+ }], [:calculation_frequency, {
915
+ 'D' => [:daily, 'Daily'],
916
+ 'W' => [:weekly, 'Weekly'],
917
+ 'N' => [:monthly, 'Monthly'],
918
+ 'Q' => [:quarterly, 'Quarterly'],
919
+ 'S' => [:semi_annually, 'Semi-annually'],
920
+ 'A' => [:annually, 'Annually'],
921
+ 'M' => [:others, 'Others (miscellaneous)']
922
+ }], NA, NA] },
923
+ 'I' => { symbol: :indices, label: 'Indices',
924
+ attributes: [[:asset_class, {
925
+ 'E' => [:equities, 'Equities'],
926
+ 'D' => [:debt, 'Debt'],
927
+ 'F' => [:collective_investment_vehicles, 'Collective investment vehicles'],
928
+ 'R' => [:real_estate, 'Real estate'],
929
+ 'T' => [:commodities, 'Commodities'],
930
+ 'C' => [:currencies, 'Currencies'],
931
+ 'M' => [:others, 'Others (miscellaneous)']
932
+ }], [:weighting, {
933
+ 'P' => [:price_weighted, 'Price weighted'],
934
+ 'C' => [:capitalization_weighted, 'Capitalization weighted'],
935
+ 'E' => [:equal_weighted, 'Equal weighted'],
936
+ 'F' => [:modified_market_cap_weighted, 'Modified market-cap weighted'],
937
+ 'M' => [:others, 'Others (miscellaneous)']
938
+ }], [:return_type, {
939
+ 'P' => [:price_return, 'Price return'],
940
+ 'N' => [:net_total_return, 'Net total return'],
941
+ 'G' => [:gross_total_return, 'Gross total return'],
942
+ 'M' => [:others, 'Others (miscellaneous)']
943
+ }], NA] },
944
+ 'B' => { symbol: :baskets, label: 'Baskets',
945
+ attributes: [[:composition, {
946
+ 'E' => [:equities, 'Equities'],
947
+ 'D' => [:debt, 'Debt'],
948
+ 'F' => [:collective_investment_vehicles, 'Collective investment vehicles'],
949
+ 'I' => [:indices, 'Indices'],
950
+ 'T' => [:commodities, 'Commodities'],
951
+ 'C' => [:currencies, 'Currencies'],
952
+ 'M' => [:others, 'Others (miscellaneous)']
953
+ }], NA, NA, NA] },
954
+ 'D' => { symbol: :stock_dividends, label: 'Stock dividends',
955
+ attributes: [[:equity_type, {
956
+ 'S' => [:common_shares, 'Common/Ordinary shares'],
957
+ 'P' => [:preferred_shares, 'Preferred/Preference shares'],
958
+ 'C' => [:convertible_common_shares, 'Common/Ordinary convertible shares'],
959
+ 'F' => [:convertible_preferred_shares, 'Preferred/Preference convertible shares'],
960
+ 'L' => [:limited_partnership_units, 'Limited partnership units'],
961
+ 'K' => [:collective_investment_vehicles, 'Collective investment vehicles'],
962
+ 'M' => [:others, 'Others (miscellaneous)']
963
+ }], NA, NA, NA] },
964
+ 'M' => { symbol: :miscellaneous, label: 'Others (miscellaneous)',
965
+ attributes: [NA, NA, NA, NA] }
966
+ },
967
+ 'M' => {
968
+ 'C' => { symbol: :combined_instruments, label: 'Combined instruments',
969
+ attributes: [[:components, {
970
+ 'S' => [:combination_of_shares, 'Combination of shares'],
971
+ 'B' => [:combination_of_bonds, 'Combination of bonds'],
972
+ 'H' => [:share_and_bond, 'Share and bond'],
973
+ 'A' => [:share_and_warrant, 'Share and warrant'],
974
+ 'W' => [:warrant_and_warrant, 'Warrant and warrant'],
975
+ 'U' => [:fund_unit_and_other, 'Fund unit and other components'],
976
+ 'M' => [:others, 'Others (miscellaneous)']
977
+ }], [:ownership_restrictions, OWNERSHIP], NA, [:form, FORM]] },
978
+ 'M' => { symbol: :other_assets, label: 'Other assets',
979
+ attributes: [[:further_grouping, {
980
+ 'R' => [:real_estate_deeds, 'Real estate deeds'],
981
+ 'I' => [:insurance_policies, 'Insurance policies'],
982
+ 'E' => [:escrow_receipts, 'Escrow receipts'],
983
+ 'T' => [:trade_finance_instruments, 'Trade finance instruments'],
984
+ 'N' => [:carbon_credit, 'Carbon credit'],
985
+ 'P' => [:precious_metal_receipts, 'Precious metal receipts'],
986
+ 'S' => [:other_otc_derivatives, 'Other OTC derivative products'],
987
+ 'M' => [:others, 'Others (miscellaneous)']
988
+ }], NA, NA, NA] }
989
+ }
990
+ }.freeze
991
+
992
+ # Cross-position rule for group ED (depositary receipts on equities): when
993
+ # the underlying instrument (position 3) is common/ordinary shares (S) or
994
+ # limited partnership units (L), the redemption/conversion position
995
+ # (position 4) accepts only N (perpetual) or X.
996
+ ED_REDEMPTION_RULE = {
997
+ category: 'E',
998
+ group: 'D',
999
+ underlying_position: 0,
1000
+ redemption_position: 1,
1001
+ restricted_underlyings: %w[S L].freeze,
1002
+ allowed_redemptions: %w[N X].freeze
1003
+ }.freeze
1004
+
1005
+ # Whether the ED (depositary receipts on equities) cross-position redemption
1006
+ # rule applies to the given group and attribute letters — i.e. this is an
1007
+ # E/D code whose underlying triggers the redemption restriction, so the
1008
+ # redemption position must hold one of {ED_REDEMPTION_RULE}'s allowed values.
1009
+ # Shared by generation (enforcement) and validation so the two never drift.
1010
+ #
1011
+ # @param category_code [String]
1012
+ # @param group_code [String]
1013
+ # @param letters [Array<String>] the four attribute letters (positions 3-6)
1014
+ # @return [Boolean]
1015
+ def self.ed_rule_applies?(category_code, group_code, letters)
1016
+ rule = ED_REDEMPTION_RULE
1017
+ category_code == rule[:category] && group_code == rule[:group] &&
1018
+ rule[:restricted_underlyings].include?(letters[rule[:underlying_position]])
1019
+ end
1020
+
1021
+ DeepFreeze.call(CATEGORIES)
1022
+ DeepFreeze.call(GROUPS)
1023
+
1024
+ # @param category_code [String]
1025
+ # @param group_code [String]
1026
+ # @return [Hash, nil] the group definition, or nil if unknown
1027
+ def self.group(category_code, group_code)
1028
+ GROUPS.dig(category_code, group_code)
1029
+ end
1030
+ end
1031
+ # rubocop:enable Metrics/ModuleLength
1032
+ end
1033
+ end